前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring AOP使用入门案例

Spring AOP使用入门案例

原创
作者头像
conanma
修改2021-10-28 16:58:51
7050
修改2021-10-28 16:58:51
举报
文章被收录于专栏:正则

于AOP的原理网上有很多教程,此处不再赘述,只是通过具体的案例来记录如何使用。

一、入门案例

首先从start.spring.io上下载一个Spring Boot工程,只需要引入web依赖即可。然后我们创建一个Controller:

代码语言:javascript
复制
@RestController
public class HelloController {

    /*@AspectAction(name = "loglog")*/
    @GetMapping("/getHello")
    public String getHello(){
        System.out.println("执行getHello!");
        return "hello";
    }

}

然后创建一个切面:

代码语言:javascript
复制
@Aspect
@Component
public class LogAspect {

    /**
     * 前置通知
     * 在方法执行之前执行
     */
    @Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void before() {
        System.out.println("before");
    }

}

这个切面中最重要的就是@Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))"),它说明了这是一个前置通知,并且切入点为HelloController类中的getHello方法。

到这里一个最简单的AOP例子就能运行了,启动程序后,访问入口,打印信息如下:

代码语言:javascript
复制
before
执行getHello!

二、通知类型

总共支持五种通知类型:

代码语言:javascript
复制
@Aspect
@Component
public class LogAspect {

    /**
     * 前置通知
     * 在方法执行之前执行
     */
    @Before("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void before() {
        System.out.println("before");
    }

    /**
     * 后置通知
     * 在方法执行之后
     */
    @After("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void after() {
        System.out.println("after");
    }

    /**
     * 返回通知
     * 在方法执行之后,返回之前执行
     */
    @AfterReturning("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void afterReturing(){
        System.out.println("afterReturning");
    }

    /**
     * 异常通知
     * 在方法抛出异常之后执行
     */
    @AfterThrowing("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

}

执行结果可以表现各个通知的执行顺序:

代码语言:javascript
复制
before
执行getHello!
afterReturning
after

还有一个环绕通知,可以代替其它四个通知,环绕通知可以实现其它四个通知。

代码语言:javascript
复制
    /**
     * 环绕通知
     * 能够代替其它四种通知使用
     */
    @Around("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            // 执行目标方法中的逻辑
            joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("异常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }

假设同时设置了四种通知,还有环绕通知,那么执行的顺序是怎样的呢?

代码语言:javascript
复制
前置通知!
before
执行getHello!
afterReturning
after
返回通知!
后置通知!

需要注意的是,环绕通知中的joinPoint.proceed();如果不写的话,就会跳过切入点目标方法的逻辑执行,直接执行环绕通知后续的通知逻辑:

代码语言:javascript
复制
前置通知!
返回通知!
后置通知!

三、切点表达式

在如上的案例中,我们发现如果定义了很多通知,在定义切入点表达式时写了很多一样的东西:

代码语言:javascript
复制
"execution(* com.example.aopdemo.controller.HelloController.getHello(..))"

其实可以在切面类中先定义一个切点表达式,然后其它通知定义的时候引用这个切点表达式即可:

代码语言:javascript
复制
@Aspect
@Component
public class LogAspect {

    /**
     * 声明切点表达式
     */
    @Pointcut("execution(* com.example.aopdemo.controller.HelloController.getHello(..))")
    public void pointCut(){}


    /**
     * 环绕通知
     * 能够代替其它四种通知使用
     */
    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            //joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("异常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }

}

四、切点指示器

以上面的切点表达式为例:

  • execution表示匹配执行目标方法,还有其它类型的指示器,但用的不多;
  • *表示不关心方法的返回值;
  • ..表示不关心方法的参数;
  • 如果需要多个匹配条件可以使用&& || !来表示且、或、非的关系; @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*) && bean(girl)") 其中within是和execution平行的指示器,表示匹配的切入点类型;bean是实例匹配器,只有遇到girl这个实例的时候才会执行AOP;

五、使自定义注解

有的时候我们希望在声明自定义注解的方法上执行AOP的逻辑,而不是如上指定具体的位置。

首先我们需要先定义自己的注解:

代码语言:javascript
复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAction {
    String name() default "default";
}

然后,在切面中定义切入点为标注这个注解的地方。

代码语言:javascript
复制
@Aspect
@Component
public class LogAspect {

    @Pointcut("@annotation(com.example.aopdemo.annotation.AspectAction)")
    public void pointCut(){}

    @Around("pointCut()")
    public void around(ProceedingJoinPoint joinPoint) {
        System.out.println("前置通知!");
        try {
            joinPoint.proceed();
            System.out.println("返回通知!");
        } catch (Throwable throwable) {
            System.out.println("异常通知!");
        } finally {
            System.out.println("后置通知!");
        }
    }
}

最后,只要在想使用AOP的方法之上加上自定义注解即可:

代码语言:javascript
复制
@AspectAction(name = "loglog")
@GetMapping("/getHello")
public String getHello(){
    System.out.println("执行getHello!");
    return "hello";
}

我们在环绕通知中,可以通过ProceedingJoinPoint来获取如下各种信息,ProceedingJoinPoint继承自JoinPoint,所以在其它类型的通知中也可以获得如下内容:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、入门案例
  • 二、通知类型
  • 三、切点表达式
  • 四、切点指示器
  • 五、使自定义注解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档