Spring AOP使用入门案例

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

一、入门案例

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

@RestController
public class HelloController {

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

}

然后创建一个切面:

@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例子就能运行了,启动程序后,访问入口,打印信息如下:

before
执行getHello!

二、通知类型

总共支持五种通知类型:

@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");
    }

}

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

before
执行getHello!
afterReturning
after

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

    /**
     * 环绕通知
     * 能够代替其它四种通知使用
     */
    @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("后置通知!");
        }
    }

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

前置通知!
before
执行getHello!
afterReturning
after
返回通知!
后置通知!

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

前置通知!
返回通知!
后置通知!

三、切点表达式

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

"execution(* com.example.aopdemo.controller.HelloController.getHello(..))"

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

@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的逻辑,而不是如上指定具体的位置。

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

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAction {
    String name() default "default";
}

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

@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的方法之上加上自定义注解即可:

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

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

  • 方法签名;
  • 方法入参;
  • 等等,参考源代码即可;

你可能感兴趣的:(Spring AOP使用入门案例)