Spring AOP 使用

29 阅读3分钟

AOP 是面向切面编程,Spring AOP 是通过 JDK 动态代理和 CGLIB 来实现 AOP 的,如果目标类实现了接口就用 JDK 动态代理,如果没有实现接口就会用 CGLIB 来生成代理类。Spring AOP 中有一些概念需要先知道:

  • 切面(Aspect):在 Spring 表现为一个 bean,用来集中定义切入点
  • 连接点(Join Point):Spring AOP 只支持方法级别的连接点,方法连接点就是你要进行切入或者增强的方法
  • 切入点(Pointcut):在 Spring 中表现为方法上的注解,配合切入点表达式来定义要切入或增强的方法
  • 通知:在 Spring 中表现为一个方法,定义在切面 bean 中

要使用 Spring AOP,除了其他的 Spring 依赖外,你需要引入如下依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.10</version>
</dependency><dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.10</version>
</dependency>

使用 Spring AOP 分为如下几步:

  1. 开启 AspectJ 注解支持

在 @Configuration bean 上标注 @EnableAspectJAutoProxy,它会往 Spring IoC 中注册一个 BeanPostProcessor 的实现类 AnnotationAwareAspectJAutoProxyCreator,Spring 非常多的功能都是通过 BeanPostProcessor 实现类来完成的,它是 Spring 提供的扩展点之一。

@Configuration
@EnableAspectJAutoProxy
public class MyConfig{
    
}
  1. 定义切面

通过 @Aspect 注解来定义切面,通知要将 bean 交给 Spring IoC 容器管理。

@Component
@Aspect
public class MyAspect{
    
}
  1. 定义切入点和通知

定义好切面之后,在切面里面定义切入点和通知。

// 切面
@Aspect
@Component
public class MyAspectJ {
​
    // 可复用的切入点
    @Pointcut("execution(public String org.config.AspectJTest.doSomething(..))")
    private void recordLog() {}
​
    // 前置通知
    @Before("recordLog()")
    public void before(){
        System.out.println("这是一个前置通知");
    }
​
    // 后置通知
    @AfterReturning(pointcut = "recordLog()", returning = "input")
    public void afterReturning(Object input){
        System.out.println("这是一个后置通知,连接点方法的返回值是:" + input);
    }
​
    // 异常通知
    @AfterThrowing(pointcut = "recordLog()", throwing = "ex")
    public void afterThrowing(Exception ex){
        System.out.println("这是一个异常通知,连接点方法抛出的异常是:" + ex.getMessage());
    }
​
    // 最终通知
    @After("recordLog()")
    public void after(){
        System.out.println("这是一个最终通知");
    }
​
    // 环绕通知
    @Around("recordLog()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("这是一个环绕通知,执行方法前业务...");
​
        Object proceed = null;
        try {
            proceed = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
​
        System.out.println("这是一个环绕通知,执行方法后业务...");
​
        return proceed;
    }
​
}

这里使用 execution 类型的切入点表达式,Spring AOP 还支持很多种类型的切入点表达式,但是 execution 类型是最常用的,@Pointcut 定义了一个切入点,里面是一个切入点表达式,它指定了对哪些类的哪些方法进行增强。

Spring AOP 提供了 5 种类型的通知:

  • 前置通知,在目标方法执行执行
  • 后置通知:在目标方法执行结束后执行,如果目标方法异常结束则不会执行,它可以获取到目标方法的返回值
  • 异常通知:在目标方法异常退出后执行,它可以获取到目标方法抛出的异常
  • 最终通知:在目标方法执行结束后执行,它和后置通知的区别是最终通知即使是目标方法异常退出也会执行
  • 环绕通知:环绕通知是最通用的一种通知,它的返回值必须是 Object,方法的第一个参数必须是 ProceedingJoinPoint,你通过调用 ProceedingJoinPoint.processd() 来执行目标方法
  1. 定义连接点

连接点就是你要增强的类。

@Component
public class AspectJTest implements MyComponent {
​
​
    public String doSomething(String input){
        System.out.println("bussiness method...");
        return "output";
    }
}
  1. 运行测试
public static void main(String[] args) {
​
        System.out.println("Hello World!");
​
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan("org.config");
        context.refresh();
​
        AspectJTest aspectJTest = context.getBean(AspectJTest.class);
        String output = aspectJTest.doSomething("input");
        System.out.println(output);
​
}

你会看到如下效果:

这是一个环绕通知,执行方法前业务...
这是一个前置通知
bussiness method...
这是一个后置通知,连接点方法的返回值是:output
这是一个最终通知
这是一个环绕通知,执行方法后业务...
output