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 的条件:
config.isOptimize()— 很少直接使用config.isProxyTargetClass()—proxyTargetClass=true标志位!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() 调用处:
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
这是"短路"路径——如果某个 BeanPostProcessor 从 postProcessBeforeInstantiation() 返回了非 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)依次调用。每个拦截器可以:
- 在调用
proceed()之前执行操作(例如:开启事务) - 调用
proceed()将控制权交给下一个拦截器(或目标方法) - 在
proceed()返回后执行操作(例如:提交事务) - 捕获异常(例如:回滚事务)
这正是经典的责任链模式,也是 @Transactional、@Cacheable、@Async 能够叠加应用在同一个方法上的根本原因。
MergedAnnotations:注解发现引擎
当实际标注的是 @TransactionalService——一个内部包含 @Transactional 的自定义组合注解时,Spring 是如何发现该方法带有 @Transactional 的?答案是 MergedAnnotations:
spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java#L31-L60
MergedAnnotations 为 Java 元素(类、方法、字段)上的注解提供了统一的视图,涵盖以下内容:
- 直接声明的注解 — 物理上标注在元素上的注解
- 元注解 — 标注在注解上的注解(递归查找)
@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 代理流程贯穿了本系列所涵盖的每一个环节:
-
第 3 篇:
refresh()第 5 步发现配置类上的@EnableTransactionManagement,该注解通过@Import引入ProxyTransactionManagementConfiguration,注册TransactionInterceptor(Advice)和TransactionAttributeSourceAdvisor(Advisor)。 -
第 3 篇:
refresh()第 6 步将AnnotationAwareAspectJAutoProxyCreator注册为BeanPostProcessor。 -
第 2 篇:在第 11 步(
finishBeanFactoryInitialization)中,当你的@ServiceBean 被创建时,initializeBean()会对每个已注册的BeanPostProcessor调用postProcessAfterInitialization()。 -
本篇:
AbstractAutoProxyCreator.postProcessAfterInitialization()发现TransactionAttributeSourceAdvisor的 pointcut 与该 Bean 匹配(因为它含有@Transactional方法),随即创建一个 CGLIB 代理,将事务拦截器包裹在你的 Bean 外层。 -
在运行时,调用带有
@Transactional的方法时,请求先经过代理,再进入TransactionInterceptor,由其开启事务、调用你的实际方法,最后提交或回滚。
下一篇
至此,我们已经覆盖了 Spring 内部的四大支柱:模块体系、IoC 容器、启动流程和 AOP 代理。下一篇将进入 Web 层——追踪一个请求如何穿越 DispatcherServlet.doDispatch(),以及其响应式对应实现 DispatcherHandler.handle(),并对比同一套策略模式如何衍生出一个 1400 行的命令式 Servlet 与一个 220 行的响应式处理器。