🍃Spring源码阅读-AOP

type
status
date
slug
summary
tags
category
icon
password
原文

一、Spring AOP组成和核心注解

1.1 Spring AOP 组成

一个完整的切面通常有以下元素组成
  1. 切点(Pointcut):定义代码中哪些位置需要被拦截(如特定方法、注解或类),意思就是哪些方法需要被增强(通过 类、方明名等规则匹配)
    1. 表达式匹配:execution(* com.example.service.*.*(..))
      注解匹配:@annotation(com.example.Loggable)
  1. 通知(Advice):定义在目标方法执行时触发的逻辑(如日志、事务等)
    1. 通知类型有前置通知、后置通知等。
  1. 切面类(Aspect):承载切点和通知的容器类,使用@Aspect注解标记。
  1. 引入(Introduction): 为目标类动态添加新的接口实现(属于特殊的通知类型)
  1. 织入(Weaving): 将切面逻辑插入目标位置的实现过程。
    1. 织入方式有:
      • 编译时织入(AspectJ):在编译阶段直接将切面逻辑写入字节码,无运行时性能损耗
      • 类加载时织入(LTW):在类加载时动态增强字节码(需配合 Java Agent)
      • 运行时织入:Spring AOP 默认行为

引入(Introduction)

引入是一种特殊的通知类型,允许动态地为目标对象添加的接口实现,从而扩展其功能。它不同于常规通知(例如Before、Around),而是通过代理机制为对象“引入”新的行为和状态。
目标类无须显示实现接口,在不修改原有代码的基础上,可以通过AOP代理动态实现新接口,引入的方法由切面提供具体的实现。
这里的关键是:
  1. 目标对象必须由 Spring容器管理,否则无法应用引入这个功能,
  1. 只能引入接口不能引入具体的类(接口相当于标记作用)
  1. 只有通过代理调用时,引入才生效。
引入使用示例

2. 核心注解

2.1 切面类与目标方法注解

  • @Aspect 定义切面类
  • @Before 前置通知,在目标方法执行前执行
  • @After 最终通知,在目标方法后执行(无论是否抛出异常)
  • @AfterReturning 目标方法成功返回后
  • @AfterThrowing 异常通知,在方法抛出异常后执行,可捕获异常对象
  • @Around 环绕通知,包裹目标方法执行,需调用proceed()
    • @Pointcut 定义可重用的切入点表达式。

      2.2 配置与扩展注解

      • @EnableAspectJAutoProxy 使 Aspect 注解生效
        • 💡
          @EnableAspectJAutoProxy // 使 Aspect注解生效@EnableAspectJAutoProxy(proxyTargetClass = true) // 默认为 jdk动态代理 [proxyTargetClass = true 使用cglib动态代理]
          默认情况下使用 Jdk 动态代理的情况下,如果目标类没有实现接口,会用 Cglib 来生成。
          如果使用 Cglib,即使目标类实现了接口,也会使用 Cglib 来生成代理对象
           
      • @Order 控制多个切面的执行顺序,数值越小执行优先级越高

      2.3 切入点表达式

      • execution: 匹配方法执行,如 execution(* com.example.service.*.*(..))
      • @annotation: 匹配带有指定注解的方法。

      二、整体流程简述

      1. 启动阶段

      使用@EnableAspectJAutoProxy 时,Spring容器会注册AnnotationAwareAspectJAutoProxyCreator,它是 BeanPostProcessor 的实现类。启动阶段AOP这块主要是将相关AOP处理的容器Bean 注册到容器中,方便后面对应用自定义切面的处理,这也体现了一个按需加载的设计。
      EnableAspectJAutoProxy 注解源码
       
      在容器启动完成后是可以看到这个AnnotationAwareAspectJAutoProxyCreator 会存在容器当中,它的主要作用是自动为符合条件的 Bean 创建动态代理对象。
      在 Spring 中以Enable为开头的注解都有按需加载的意思,根据该种注解创建内置所需的处理类。

      2. 切面的扫描与解析

      扫描@Aspect 注解的类,通过BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors() (BeanFactoryAspectJAdvisorsBuilder 在上一步已经注入到了AnnotationAwareAspectJAutoProxyCreator 当中) 扫描所有 Bean,识别带有@Aspect 的类,解析类中@PointCut 和通知方法将它们转换成Advisor 并存入缓存。构建缓存是会根据 Bean 的 Scope 来放入不同的缓存中。
      扫描与解析源码(BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors)
       
      这一步会在IOC 容器完成初始(refresh#finishBeanFactoryInitialization(beanFactory))的过程中初始化第一个 Bean 触发上述流程。

      什么是Advisor

      Advisor 是 Spring Aop的核心概念,用于将通知(advice)和切入点(Pointcut)组合成一个完成的切面(Aspect),从而定义在何处(切入点)执行何种逻辑(通知)。
      Advisor的作用是让 Spring AOP 知道:
      • 在哪些方法上(Pointcut
      • 执行什么操作(Advice
      Advisor中了用户自定义的切入类(@Aspect 修饰的类),切入点,方法名等信息
       

      3. 创建代理

      触发时机

      代理对象的创建主要发生在 Bean 的初始化阶段(post-process after initialization),具体通过 BeanPostProcessor 实现。
      实例化 Bean → 填充属性 → 执行初始化方法 → 执行BeanPostProcessor 后置处理。
       
      经过前面的步骤,切面类相关注解已经处理完成。当 Bean 初始化完成后,开始为需要创建动态代理对象。
      创建动态代理入口,这里所有 Bean 初始化完成后会触发,入口会判断当前 Bean 是否需要创建动态代理。
      动态代理的创建是通过ProxyFactory 创建,先配置好 ProxyFactory,这些配置包括目标源(需要被代理的完成初始化的 Bean)、拦截器数组(前面流程中解析完成的 Advisor列表)、是否强制使用CGLIB(显示配置是否启用何种代理)、需要被代理实现的接口等信息。工厂设置完成好以后会调用DefaultAopProxyFactory#createAopProxy() 方法,这里会根据PorxyFactory 配置的信息创建最终的动态代理对象。动态代理的对象指的是 JdkDynamicAopProxyObjenesisCglibAopProxy 包装的对象。
      配置 ProxyFactory
       
      💡
      hints: 这里配置好的 ProxyFactory会根据配置生成一个 AopProxy,AopProxy有两个具体实现JdkDynamicAopProxyObjenesisCglibAopProxy ,然后调用 AopProxy 的 getProxy 方法获得最终的代理对象。
      选择 AopProxy创建最终代理代理对象

      三、动态代理对象包含了什么信息

      动态代理对象是“包装”过的对象,它包含了构建这个代理对象的一些元数据,这些元数据都是基于扫描注解解析而来的,信息都封装在AdvisedSupport 这个对象中(这一步是在最终生成代理对象
      时会将 AdvisedSupport 设置代理对象中,上述代码 最终创建代理对象代码
      notion image
      其中 methodCache 是需要拦截的方法,targetSource 是源对象,advisors 是该动态代理的Advisor链。

      四、执行流程

      代理对象的方法调用会触发拦截器链路的执行。拦截器链路由Advisor转换而来,这里要筛选出当前方法匹配的Advisor转换成MethodInterceptor。拦截器链路完成后,然后会去递归调用执行。
      💡
      这里是由数组组成的拦路器链路,多个匹配的 Advisor 就是转换成MethodInterceptor数组,数组是有顺序的(顺序指的是显示指定@Order和注册顺序),
      这里有个细节就是动态切点和静态切点,这里是返回的MethodInterceptor 对象是不一样的。返回的数组为 List<Object>包含了动态和静态切点。

      方法的修饰

      代理对象执行目标方法时,目标方法的修饰符会直接影响是否有增强(也就是执行通知),如果代理类执行目标的方法是事务方法(@Transtation注解的方法),决定它是否有增强逻辑的规则只有一条,目标方法必须是 public。
      • JDK 动态代理:
        • JDK 动态代理基于接口实现的(实现目标类实现的接口),无法代理类中的方法。接口中的方法默认都是public的,所以无法声明其他修饰符的。(jdk9+ 虽然能声明private 的,但是仍然无法代理的。)这里的无法代理确切的说是代理对象根本就不会有这些非 public方法,它只有目标类实现的接口方法。
      • Cglib
        • Cglib 是直接通过继承目标类生成代理子类,可代理非 final、非 static、非 private 的方法,因为子类能重写所以排除以上这些都可以使用。 Cglib 也是支持protected 的,Spring 官方的建议是优先使用 public。
           

      什么静态切点和动态切点

      静态切点

      在编译器或容器启动时即可确定是否匹配目标方法,其逻辑匹配仅基于方法签名(类名、方法名、注解等),不依赖运行时的的参数值。
      静态切点的匹配规则是程序启动时即可确定的,如 @Pointcut("execution(* org.springframework.example.service.*.*(..))")

      动态切点

      根据运行时条件动态决定哪些方法需要被拦截。与静态切点(如 @Pointcut 注解定义的固定表达式)不同,动态切点可以根据参数值、环境变量、配置状态等动态因素灵活匹配目标方法,适合高度灵活拦截逻辑处理的场景。
      在 Spring 实现动态切点的方式有多种。
      • 继承StaticMethodMatcherPointcutDynamicMethodMatcherPointcut
      • 实现MethodMatcher 接口
      实现的主要逻辑也同 Spring 的切面处理一样,定义匹配逻辑,将切点与通知结合生成 Advisor,将 Advisor 注册到容器。

      全流程总结

      1. 激活代理:通过@EnableAspectJAutoPoxy,注册AnnotationAwareAspectJAutoProxyCreator
      1. 切面解析:扫描@Aspect类,解析@Pointcut 和通知方法,生成 Advisor
      1. 代理决策:在 Bean 初始化后,判断是否需要创建代理
      1. 代理生成:根据目标类选择 JDK 或 CGLIB 动态代理
      1. 拦截执行:通过责任链模式调用拦截器链。

      Spring AOP的局限性

      仅支持方法级别的增强,无法拦截字段访问、构造方法、静态方法等。(Spring支持Asjept来完成这种更细粒度的级别的增强)
      运行时织入性能较低:基于动态代理的拦截会在每次方法调用时产生额外开销。(动态切点,无法缓存就需要每次构建拦截链路)
      依赖 Spring 容器:只能增强 Spring 管理的 Bean。
       
      Prev
      Spring源码阅读-IOC
      Next
      Spring源码阅读-事务
      Loading...