Read OSS

AOP 代理的底层原理:从 @Transactional 到运行时拦截

高级

前置知识

  • 第 2 篇:BeanPostProcessor 生命周期
  • 第 3 篇:refresh() 执行顺序与 Bean 初始化
  • 了解 Java 动态代理的基本概念

AOP 代理的底层原理:从 @Transactional 到运行时拦截

当你在方法上标注 @Transactional 时,Spring 并不会修改你的类本身,而是用一个代理来替换你的 Bean——这个在运行时动态生成的包装对象会拦截方法调用,在你的代码执行前开启事务,并在执行后提交或回滚。@Async@Cacheable、Spring Security 的 @PreAuthorize 以及所有其他注解驱动的横切关注点,背后都依赖同一套代理机制。本文将从概念模型到运行时字节码,完整梳理这套代理基础设施的实现路径。

AOP Alliance 模型:Advice、Pointcut 与 Advisor

Spring AOP 构建在 AOP Alliance 接口(位于 org.aopalliance 包)之上,并在此基础上进行了 Spring 特有的扩展。核心概念如下:

  • Advice — 要做什么(拦截器逻辑)。例如:开启事务、校验权限、缓存返回结果。
  • Pointcut — 在哪里应用(用于匹配连接点的断言)。例如:"所有标注了 @Transactional 的方法"、"service 包下的所有 public 方法"。
  • Advisor — Advice 与 Pointcut 的组合,也是注册到代理中的实际单元。
  • AopProxy — 真正的代理对象,负责包装目标 Bean 并将调用分发到 advice 链中。

ProxyFactory 是以编程方式构建代理配置的入口:

spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java#L36

classDiagram
    class ProxyConfig {
        +proxyTargetClass: boolean
        +optimize: boolean
        +exposeProxy: boolean
    }
    
    class AdvisedSupport {
        -targetSource: TargetSource
        -advisors: List~Advisor~
        +addAdvisor(Advisor)
        +getInterceptorsAndDynamicInterceptionAdvice() List
    }
    
    class ProxyCreatorSupport {
        -aopProxyFactory: AopProxyFactory
        +createAopProxy() AopProxy
    }
    
    class ProxyFactory {
        +getProxy() Object
    }
    
    class AopProxy {
        <<interface>>
        +getProxy() Object
    }
    
    class JdkDynamicAopProxy {
        implements InvocationHandler
    }
    
    class ObjenesisCglibAopProxy {
        Generates subclass
    }
    
    ProxyConfig <|-- AdvisedSupport
    AdvisedSupport <|-- ProxyCreatorSupport
    ProxyCreatorSupport <|-- ProxyFactory
    AopProxy <|.. JdkDynamicAopProxy
    AopProxy <|.. ObjenesisCglibAopProxy

AdvisedSupport 保存了代理配置——目标对象、advisor 列表以及接口信息。ProxyCreatorSupport 委托 AopProxyFactory 创建具体的 AopProxy 实现。正是这个工厂做出了关键决策:使用 JDK 动态代理还是 CGLIB 子类?

代理选型:JDK 动态代理 vs CGLIB

选型逻辑位于 DefaultAopProxyFactory.createAopProxy() 中,代码出人意料地简洁:

spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java#L60-L76

flowchart TD
    Start["createAopProxy(config)"]
    
    Start --> Check1{"optimize || proxyTargetClass<br/>|| no user interfaces?"}
    Check1 -->|No| JDK["JdkDynamicAopProxy<br/>(interface-based proxy)"]
    
    Check1 -->|Yes| Check2{"targetClass is null<br/>|| is interface<br/>|| is Proxy class<br/>|| is lambda?"}
    Check2 -->|Yes| JDK
    Check2 -->|No| CGLIB["ObjenesisCglibAopProxy<br/>(subclass-based proxy)"]

第 61 行的逻辑检查三个倾向于使用 CGLIB 的条件:

  1. config.isOptimize() — 很少直接使用
  2. config.isProxyTargetClass()proxyTargetClass=true 标志位
  3. !config.hasUserSuppliedInterfaces() — 没有配置任何接口

即便倾向于 CGLIB,工厂在遇到接口、已有代理对象和 lambda 类时(第 67–69 行)仍会回退到 JDK 代理,因为 CGLIB 无法对这些类型生成子类。

Spring Boot 默认启用 proxyTargetClass=true,这意味着几乎所有 Bean 都使用 CGLIB 代理。这是一个务实的选择:JDK 代理只能暴露接口中声明的方法,当代码调用的方法仅定义在具体类上时,就会出现难以察觉的 bug;而 CGLIB 代理通过继承具体类来避免这个问题。

提示: 可以通过 AopUtils.isCglibProxy(bean)AopUtils.isJdkDynamicProxy(bean) 来验证 Spring 为某个 Bean 创建了哪种类型的代理。在 debug 日志中,CGLIB 代理的类名包含 $$SpringCGLIB$$

AbstractAutoProxyCreator:通过 BeanPostProcessor 自动包装代理

开发者很少直接使用 ProxyFactory。Spring 在我们第 2 篇介绍的 Bean 初始化阶段,会自动将 Bean 包装为代理,这一机制由 AbstractAutoProxyCreator 实现——它是一个 SmartInstantiationAwareBeanPostProcessor,在 Bean 创建的两个阶段分别进行拦截。

第一个拦截点在 createBean() 中的 resolveBeforeInstantiation() 调用处:

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L512-L517

// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
    return bean;
}

这是"短路"路径——如果某个 BeanPostProcessorpostProcessBeforeInstantiation() 返回了非 null 对象,则完全绕过正常的 Bean 创建流程。这主要用于脚本 Bean 等特殊场景。

更常见的路径是 postProcessAfterInitialization(),也就是我们在第 2 篇中看到的 initializeBean() 的最后一步。在这里,AbstractAutoProxyCreator 会检查每个 Bean,判断是否有 advisor 与之匹配,并在需要时将 Bean 包装为代理。

sequenceDiagram
    participant BF as BeanFactory
    participant AAPC as AbstractAutoProxyCreator
    participant DPF as DefaultAopProxyFactory
    participant Proxy as Generated Proxy

    BF->>AAPC: postProcessAfterInitialization(bean, "myService")
    AAPC->>AAPC: wrapIfNecessary(bean, "myService", cacheKey)
    AAPC->>AAPC: getAdvicesAndAdvisorsForBean()
    Note right of AAPC: Find all Advisors whose<br/>Pointcuts match this bean
    
    alt No matching advisors
        AAPC-->>BF: return original bean
    else Has matching advisors
        AAPC->>AAPC: createProxy(bean, advisors)
        AAPC->>DPF: createAopProxy(config)
        DPF-->>AAPC: JdkDynamicAopProxy or CglibAopProxy
        AAPC->>Proxy: aopProxy.getProxy()
        AAPC-->>BF: return proxy (replaces original bean)
    end

关键在于:getAdvicesAndAdvisorsForBean() 会查询容器中所有已注册的 Advisor Bean,并根据它们的 pointcut 与目标 Bean 的类进行匹配。对于 @Transactional,advisor 的 pointcut 匹配标注了 @Transactional 的方法;对于 @Async,则匹配标注了 @Async 的方法。

实际项目中最常遇到的具体子类是 AnnotationAwareAspectJAutoProxyCreator,它同时处理 Spring 原生 advisor 和 AspectJ 风格的 @Aspect 类。

运行时的调用链

代理方法在运行时被调用时,并不会直接调用你的方法,而是从所有适用的拦截器构建一条调用链。

对于 JDK 代理,JdkDynamicAopProxy 实现了 InvocationHandler

spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java

对于 CGLIB 代理,CglibAopProxy 会生成一个带有回调拦截器的子类:

spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java

两种实现遵循相同的模式:从 AdvisedSupport 获取 MethodInterceptor 实例链,然后通过 ReflectiveMethodInvocation(JDK)或 CglibMethodInvocation(CGLIB)依次调用。每个拦截器可以:

  1. 在调用 proceed() 之前执行操作(例如:开启事务)
  2. 调用 proceed() 将控制权交给下一个拦截器(或目标方法)
  3. proceed() 返回后执行操作(例如:提交事务)
  4. 捕获异常(例如:回滚事务)

这正是经典的责任链模式,也是 @Transactional@Cacheable@Async 能够叠加应用在同一个方法上的根本原因。

MergedAnnotations:注解发现引擎

当实际标注的是 @TransactionalService——一个内部包含 @Transactional 的自定义组合注解时,Spring 是如何发现该方法带有 @Transactional 的?答案是 MergedAnnotations

spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java#L31-L60

MergedAnnotations 为 Java 元素(类、方法、字段)上的注解提供了统一的视图,涵盖以下内容:

  1. 直接声明的注解 — 物理上标注在元素上的注解
  2. 元注解 — 标注在注解上的注解(递归查找)
  3. @AliasFor 解析 — 注解与其元注解之间的属性别名
flowchart TD
    Element["@PostMapping('/users')"]
    
    Element --> Direct["Direct: @PostMapping"]
    Direct --> Meta1["Meta: @RequestMapping(method=POST)"]
    Meta1 --> Meta2["Meta: @Mapping"]
    
    Element --> Merged["MergedAnnotations.from(method)"]
    Merged --> Get["mergedAnnotations.get(RequestMapping.class)"]
    Get --> Result["MergedAnnotation with merged attributes:<br/>path='/users', method=POST"]
    
    Note1["@AliasFor resolves<br/>@PostMapping.value → @RequestMapping.path"]

这就是 @GetMapping("/users") 能够作为 @RequestMapping(path="/users", method=GET) 快捷方式使用的原因——MergedAnnotations 系统会透明地合并元注解层次结构中的属性。同样的机制也驱动着 AOP 的 pointcut 匹配:在判断某个方法是否带有 @Transactional 时,Spring 通过 MergedAnnotations 进行查找,即使它嵌套在某个组合注解内部也能被发现。

提示: 如果你在编写带有 @AliasFor 的自定义注解,可以通过 MergedAnnotations.from(element).get(YourAnnotation.class) 来验证属性合并是否按预期工作。@AliasFor 声明中细微的错误往往会静默失败,难以排查。

串联全局

完整的 AOP 代理流程贯穿了本系列所涵盖的每一个环节:

  1. 第 3 篇refresh() 第 5 步发现配置类上的 @EnableTransactionManagement,该注解通过 @Import 引入 ProxyTransactionManagementConfiguration,注册 TransactionInterceptor(Advice)和 TransactionAttributeSourceAdvisor(Advisor)。

  2. 第 3 篇refresh() 第 6 步将 AnnotationAwareAspectJAutoProxyCreator 注册为 BeanPostProcessor

  3. 第 2 篇:在第 11 步(finishBeanFactoryInitialization)中,当你的 @Service Bean 被创建时,initializeBean() 会对每个已注册的 BeanPostProcessor 调用 postProcessAfterInitialization()

  4. 本篇AbstractAutoProxyCreator.postProcessAfterInitialization() 发现 TransactionAttributeSourceAdvisor 的 pointcut 与该 Bean 匹配(因为它含有 @Transactional 方法),随即创建一个 CGLIB 代理,将事务拦截器包裹在你的 Bean 外层。

  5. 在运行时,调用带有 @Transactional 的方法时,请求先经过代理,再进入 TransactionInterceptor,由其开启事务、调用你的实际方法,最后提交或回滚。

下一篇

至此,我们已经覆盖了 Spring 内部的四大支柱:模块体系、IoC 容器、启动流程和 AOP 代理。下一篇将进入 Web 层——追踪一个请求如何穿越 DispatcherServlet.doDispatch(),以及其响应式对应实现 DispatcherHandler.handle(),并对比同一套策略模式如何衍生出一个 1400 行的命令式 Servlet 与一个 220 行的响应式处理器。