aop

39 阅读7分钟

一、Spring AOP的所有实现方案

1. 基于代理的AOP

1.1 Java动态代理
  • 适用场景:适用于代理接口实例。
  • 实现方式:通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,为接口生成动态代理对象。
  • 优点:实现简单,易于理解。
  • 缺点:只能代理接口,不能代理类。
1.2 CGLIB代理
  • 适用场景:适用于代理类实例。
  • 实现方式:通过CGLIB库(一个强大的、高性能的代码生成库),在运行时动态生成目标类的子类,从而实现对类的代理。
  • 优点:可以代理类,不局限于接口。
  • 缺点:相对于Java动态代理,性能可能略低,且生成的代理类较多,可能会增加类加载器的负担。

2. @AspectJ注解驱动的切面

  • 适用场景:适用于复杂的AOP场景,需要细粒度控制切面和通知。

  • 实现方式:通过定义切面类并使用@Aspect注解标注,在切面类中定义切点(使用AspectJ切点表达式)和通知(使用@Before@After@Around等注解)。

  • 优点:

    • 强大的切点表达式语言,支持复杂的切点定义。
    • 灵活的通知类型,包括前置通知、后置通知、环绕通知等。
    • 易于理解和维护,代码结构清晰。
  • 缺点:需要额外学习AspectJ切点表达式语言。

3. 纯POJO切面

  • 适用场景:适用于简单的AOP需求,不需要复杂的切点定义和通知类型。
  • 实现方式:通过实现Spring AOP的某个通知接口(如MethodBeforeAdviceAfterReturningAdvice等),并将其实例注册为Spring容器中的bean,然后在配置文件中或通过注解指定切点和通知。
  • 优点:实现简单,不需要额外的库或注解。
  • 缺点:灵活性较低,不支持AspectJ的切点表达式和环绕通知等高级特性。

4. 注入式AspectJ切面

  • 适用场景:适用于需要在编译时或加载时织入切面的场景。

  • 实现方式:通过AspectJ编译器或加载时织入器(如LTW - Load Time Weaving),将切面代码直接织入到目标类中。

  • 优点:

    • 支持编译时和加载时织入,性能较高。
    • 完整的AspectJ特性支持,包括切点表达式、环绕通知等。
  • 缺点:

    • 需要额外的配置和依赖项(如AspectJ编译器或加载时织入器)。
    • 增加了项目的复杂度。

5. BeanNameAutoProxyCreator实现AOP

  • 适用场景:适用于需要根据Bean名称自动创建代理的场景。
  • 实现方式:通过配置BeanNameAutoProxyCreator bean,并指定需要代理的bean名称或模式,Spring容器会自动为这些bean创建代理。
  • 优点:自动化程度高,减少了手动配置的工作量。
  • 缺点:灵活性较低,需要事先知道需要代理的bean名称。

总结

Spring AOP提供了多种实现方案,包括基于代理的AOP(Java动态代理和CGLIB代理)、@AspectJ注解驱动的切面、纯POJO切面、注入式AspectJ切面和BeanNameAutoProxyCreator实现AOP。开发者可以根据项目的具体需求和复杂度选择合适的实现方案。对于大多数Spring项目而言,@AspectJ注解驱动的切面因其灵活性和易用性而成为首选。

本文主要讨论纯POJO切面和aspectJ的实现。

二、AOP实现之aspectJ

1、先定义两个个业务接口

@RestController
@RequestMapping("test")
public class TestController {
    
    @GetMapping("/a")
    public ResponseResult test1()  {
        System.out.println("业务A执行业务方法");
        return ResponseResult.sucessResult();
    }
​
    @GetMapping ("/b")
    public ResponseResult test2()  {
        System.out.println("业务B执行业务方法");
        return ResponseResult.sucessResult();
    }
}

2、定义切面类

写法一
@Aspect
@Component
public class MyAspect {
​
    @Around("execution(* com.example.controller.*.*(..))")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("before");
        Object obj = joinPoint.proceed();
        System.out.println("after");
        return obj;
    }
​
​
    @Before("execution(* com.example.controller.*.*(..))")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        System.err.println("这是一个前置通知,在方法调用之前被执行!!!");
    }
​
    @After("execution(* com.example.controller.*.*(..))")
    public void doAfter(JoinPoint joinPoint) throws Throwable {
        System.err.println("这是一个后置通知,在方法调用之后被执行!!!");
    }
​
​
    /**
     * @param result:需要与 returning 指定的值保存一致,两者需要同时使用!
     * result参数的类型需要与目标方法的返回值类型保存一致,如果两者不一致则不会执行该通知方法!
     * 如果不确定目标方法返回的类型,则可以使用 Object
     */
    @AfterReturning(value = "execution(* com.example.controller.*.*(..))",
            returning = "result")
    public void afterReturning(Object result) {
        System.out.println("返回通知:afterReturning = " + result);
    }
​
    /**
     * @param ex:需要与 throwing 指定的值保存一致,两者需要同时使用!
     */
    @AfterThrowing(value = "execution(* com.example.controller.*.*(..))",
            throwing = "ex")
    public void afterThrowing(Throwable ex) {
        if(ex instanceof ArithmeticException) {
            System.out.println("异常通知:afterThrowing");
        }
    }
}
写法二
@Aspect
@Component
public class MyAspect {
​
    @Pointcut("execution(* com.example.controller.*.*(..))")
    private void pointCut() {
​
    }
​
    @Around("pointCut()")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("before");
        Object obj = joinPoint.proceed();
        System.out.println("after");
        return obj;
    }
​
​
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        System.err.println("这是一个前置通知,在方法调用之前被执行!!!");
    }
​
    @After("pointCut()")
    public void doAfter(JoinPoint joinPoint) throws Throwable {
        System.err.println("这是一个后置通知,在方法调用之后被执行!!!");
    }
​
​
    /**
     * @param result:需要与 returning 指定的值保存一致,两者需要同时使用!
     * result参数的类型需要与目标方法的返回值类型保存一致,如果两者不一致则不会执行该通知方法!
     * 如果不确定目标方法返回的类型,则可以使用 Object
     */
    @AfterReturning(value = "pointCut()",
            returning = "result")
    public void afterReturning(Object result) {
        System.out.println("返回通知:afterReturning = " + result);
    }
​
    /**
     * @param ex:需要与 throwing 指定的值保存一致,两者需要同时使用!
     */
    @AfterThrowing(value = "pointCut()",
            throwing = "ex")
    public void afterThrowing(Throwable ex) {
        if(ex instanceof ArithmeticException) {
            System.out.println("异常通知:afterThrowing");
        }
    }
}

上面两种写法效果是一样的,但是推荐第二种写法,将切点PointCut和通知Advice分开书写,减少重复定义。

另外切面类必须注入到容器之中。

3、切面类组成

(1)切点Pointcut

表示哪些类或方法需要被拦截,即定义切点。

* com.example.controller..*.*(..)切点表达式

第一个*号:表示返回类型, *号表示所有的类型

包名: 表示需要拦截的包名,后面的..表示当前包和当前包的所有子com.example.controller包、子孙包下所有类的方法。

第二个*号: 表示类名,*号表示所有的类。

*(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数

如果又多个表达式可以用||连接,例如

@Pointcut("execution(public * com. . . .controller. .*(..)) || execution(public * com. . .controller. . (..))")

切点也可以是某个自定义注解,把注解加在指定的方法上面也可以实现业务方法增强,例如

@Before("@annotation(com.example.annotation.Myannotation)")

(2)通知Advice

表示实际增强的逻辑入口,即当方法被拦截时需要执行的代码。

@Before :标识一个前置增强方法,相当于BeforeAdvice的功能

@AfterReturning :后置增强,相当于AfterReturningAdvice,方法退出时执行

@AfterThrowing :异常抛出增强,相当于ThrowsAdvice

@After :final增强,不管是抛出异常或者正常退出都会执行

@Around :环绕增强,相当于MethodInterceptor

4、aop执行顺序

image-20240701174329429.png

三、纯POJO切面(Advisor)

  • Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。
  • Advisor是一个接口,通常由Advice和Pointcut两个组件组成。

1、定义Advice

Advice接口有多种实现,如MethodInterceptor、BeforeAdvice、AfterAdvice等,分别代表不同的通知类型。

你可以根据需要选择适当的Advice实现,并定义具体的增强逻辑。例如

public class MyAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // 前置逻辑
        System.out.println("xxxxxxxxxxxxxxxx");
    }
}
​

2、定义Advisor

@Configuration
public class AopConfig {
    @Bean
    public Advisor myAdvisor() {
        // 创建切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.example.controller.*.*(..))");
        // 创建通知
        MyAdvice myAdvice = new MyAdvice();
        // 创建Advisor
        return new DefaultPointcutAdvisor(pointcut, myAdvice);
    }
​
}

AspectJ 提供了更广泛和灵活的 AOP 支持,包括编译时和加载时织入,以及更丰富的注解和表达式语言。Spring AOP(通过 Advisor)主要支持运行时织入。效率上面AspectJ 更高。