Spring 源码探险:AOP 的织入艺术——在无形中改变世界
在我们的日常开发中,总有一些“幽灵代码”。以 @Transactional
为例,我们只是在方法上轻轻地标注了一下,它就拥有了控制数据库事务的能力——自动开启、智能提交、异常回滚。我们自己的业务代码里,从头到尾都看不到任何一行 connection.commit()
或 connection.rollback()
的踪迹。
这段逻辑,它不在我们的类里,却在我们的方法执行时如影随形。它就像一个透明的魔术师,在我们毫无察觉的情况下,改变了方法的行为。
这,就是 AOP (Aspect-Oriented Programming) 的魅力。
今天,我们不谈理论,不画网图。我们将再次扮演那个冷酷的 Debugger,带上最锋利的解剖刀,直奔后台,去捕获这个“魔术师”的身影。我们将亲眼见证,Spring AOP 是如何在 Bean 生命周期的某个精确瞬间完成“偷天换日”,以及它是如何构建出一个精妙的调用体系,将本不属于方法的逻辑,天衣无缝地织入其中。
准备好了吗?让我们开始揭秘。
第一章:代理的诞生——AOP 的切入点
AOP 的秘密,始于“代理”。Spring AOP 的本质,就是在运行时为我们的 Bean 创建一个代理对象,用这个代理对象去替换掉容器中原始的 Bean 实例。后续所有对该 Bean 的调用,实际上都打到了这个代理对象上,从而给了 AOP 一个执行额外逻辑的机会。
那么,第一个核心问题来了:代理是在何时被创建的?
答案,藏在我们上一篇探险过的 Bean 生命周期的终点。
- 源码路径:
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
- 核心方法:
postProcessAfterInitialization(Object bean, String beanName)
回顾一下 Bean 的生命周期:实例化
-> 属性填充
-> 初始化
。postProcessAfterInitialization
是 BeanPostProcessor
提供的、在 Bean 完成所有初始化(包括执行完 init-method
)之后的最后一个干预点。
为什么是这个时机?因为它完美地保证了:
- Bean 已就绪: 原始的 Bean 对象已经是一个完全体,所有该有的依赖和状态都已准备好。
- 替换正当时: 此时去创建一个代理并替换掉原始 Bean,对后续的使用方是完全透明的,它们拿到的将直接是这个功能增强后的代理对象。
让我们深入 AbstractAutoProxyCreator
(它是 AnnotationAwareAspectJAutoProxyCreator
等类的父类,是处理 AOP 的核心),看看 postProcessAfterInitialization
里面发生了什么。
// AbstractAutoProxyCreator.java
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// ...
return wrapIfNecessary(bean, beanName, cacheKey);
}
return bean;
}
核心逻辑委托给了 wrapIfNecessary
方法,其名自明:“如果需要,就包装一下”。
// AbstractAutoProxyCreator.java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// ...
// 寻找适用于当前 bean 的所有 Advisor (通知器)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 如果找到了任何一个 Advisor,说明需要为这个 bean 创建代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
这里的流程非常清晰:
1.getAdvicesAndAdvisorsForBean()
: 这是 AOP 的“侦察兵”。它会遍历 Spring 容器中所有的 Advisor
(我们下一章会详述),通过 Pointcut
(切点)表达式进行匹配,判断哪些 Advisor
应该应用于当前的 Bean。
判断: 如果侦察兵带回了至少一个适用的
Advisor
,那么就意味着这个 Bean 需要被代理。createProxy()
: 这是 AOP 的“兵工厂”,负责创建代理对象。
决策:JDK vs. CGLIB
进入 createProxy()
,我们会遇到 AOP 中最经典的一个问题:Spring 到底用哪种方式创建代理?
- 源码路径:
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
// DefaultAopProxyFactory.java
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// isProxyTargetClass() 这个配置项默认为 false
// optimize 默认为 false
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// ...
// 如果目标类是接口,或者被 final 修饰,Spring 还是会警告并尝试用 JDK 代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
Spring 的决策逻辑如下:
- 默认策略: 如果目标 Bean 实现了接口,Spring 默认使用 JDK 动态代理。
- 强制 CGLIB: 如果目标 Bean 没有实现任何接口,Spring 别无选择,只能使用 CGLIB。
- 用户干预: 如果用户在配置中(例如通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
)将proxyTargetClass
设置为true
,那么 Spring 将强制使用 CGLIB,无论目标 Bean 是否实现了接口。
为什么 CGLIB 越来越成为主流? 因为 JDK 动态代理是基于接口的,如果一个方法没有在接口中定义(比如是类自己特有的方法),那么 AOP 将无法代理这个方法。而 CGLIB 是通过继承目标类来创建代理的,它可以代理类中所有非 final
的方法,这在很多场景下更为灵活和强大。
至此,一个全新的代理对象诞生了。它怀揣着原始 Bean 的引用(TargetSource
)和即将要执行的额外逻辑(Advisors
),悄无声息地替换了容器中的原始实例。大幕,已经拉开。
第二章:切面的三大支柱——Advisor
, Pointcut
, Advice
在上一章的 wrapIfNecessary
方法中,我们看到了 Advisor
这个概念。它是 AOP 世界的基石。为了理解 AOP 是如何工作的,我们必须弄清楚这三个核心组件的关系。
Advice
(通知): “做什么”。- 它代表了你想要织入的“额外逻辑”,比如事务管理、日志记录等。
- 在源码中,它通常是以
MethodInterceptor
接口的形态出现的。@Before
,@After
,@Around
等不同的注解,最终都会被封装成实现了Advice
接口的特定对象。例如,AspectJAfterAdvice
,AspectJMethodBeforeAdvice
等。 Pointcut
(切点): “在哪里做”。- 它定义了一个匹配规则,用于精准地找到需要被增强的目标方法。
- 最常用的实现是
AspectJExpressionPointcut
,它能够解析我们熟知的execution(* com.example.service.*.*(..))
这样的 AspectJ 表达式。它内部通过 AspectJ 的库来解析表达式,并生成一个“影子匹配器”(ShadowMatch
),在运行时可以判断任何给定的方法是否符合该表达式。 Advisor
(通知器): “在什么地方,做什么事”。- 它是一个“小而美”的聚合对象,是
Advice
和Pointcut
的合体。它将“做什么”和“在哪里做”这两个信息绑定在了一起。 - 在 Spring 中,AOP 配置的基本单位就是
Advisor
。Spring 在寻找应用到 Bean 的切面时,找的就是它。DefaultPointcutAdvisor
是最常见的实现。
当你写下这段 AspectJ 注解的代码时:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
// ... log something
}
}
Spring 在启动时,AnnotationAwareAspectJAutoProxyCreator
就会解析这个类,并为 logBefore
方法创建一个 Advisor
。这个 Advisor
内部就包含了:
- 一个
AspectJExpressionPointcut
,其表达式为execution(* com.example.service.*.*(..))
。 - 一个
AspectJMethodBeforeAdvice
,它封装了logBefore
方法的逻辑。
现在,我们再回看第一章的 getAdvicesAndAdvisorsForBean
方法,就豁然开朗了。它的工作就是:拿出容器里所有的 Advisor
,挨个用它们的 Pointcut
去和当前 Bean 的 Class 及所有方法进行匹配,把所有匹配成功的 Advisor
收集起来,返回给 wrapIfnecessary
方法用于创建代理。
第三章:代理的执行——调用链的秘密
代理对象已经创建,并且持有了所有匹配的 Advisor
。那么,当外部代码调用代理对象的方法时,这些 Advisor
里的 Advice
(通知逻辑)是如何被执行的呢?
这正是 AOP 设计中最精妙的部分——责任链模式的运用。
- 源码路径:
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
(JDK 代理)org.springframework.aop.framework.CglibAopProxy#intercept
(CGLIB 代理)
无论哪种代理方式,它们最终都会走到一个相似的逻辑中:
- 获取当前方法匹配的所有
Advisor
列表(即Advice
列表)。 - 创建一个
MethodInvocation
对象。 - 执行调用链。
我们以 CGLIB 为例,看 intercept
方法的核心:
// CglibAopProxy.java
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// ...
// 获取适用于此方法的所有 interceptors (advice)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// ...
// 创建一个方法调用对象
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
// ...
return retVal;
}
最关键的一行是 new CglibMethodInvocation(...).proceed()
。CglibMethodInvocation
就是那个贯穿整个调用链的“信使”对象,而 proceed()
方法则是启动这条链的按钮。
让我们深入 proceed()
方法:
- 源码路径:
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
// ReflectiveMethodInvocation.java
public Object proceed() throws Throwable {
// 当所有 advice 都执行完毕
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 调用原始的目标方法
return invokeJoinpoint();
}
// 获取下一个 interceptor (advice)
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// ...
// 递归调用,将自己 (this) 作为参数传给下一个 advice
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
这段代码堪称艺术品!它通过一个 currentInterceptorIndex
计数器和递归调用,完美地构建了一个责任链:
proceed()
方法被第一次调用时,它从Advice
链中取出第 0 个Advice
。- 然后调用这个
Advice
的invoke
方法,并将MethodInvocation
自身(this
)作为参数传进去。 - 这个
Advice
在执行完自己的前置逻辑后,如果想让调用链继续下去,它就必须调用MethodInvocation.proceed()
方法。 - 这一调用,又会回到
proceed()
方法内部,此时currentInterceptorIndex
已经自增,它会取出第 1 个Advice
并执行…… - 如此往复,形成一个“洋葱式”的调用结构:
Advice1 -> Advice2 -> Advice3 -> 目标方法 -> Advice3 -> Advice2 -> Advice1
。 - 当
currentInterceptorIndex
到达链的末端时,proceed()
方法会调用invokeJoinpoint()
,这才是真正通过反射调用我们原始 Bean 的那个方法。 - 目标方法返回后,调用结果会沿着递归调用栈原路返回,每个
@Around
Advice
都有机会处理返回值或抛出异常。
这就是 @Around
, @Before
, @After
等通知能够按照预期顺序执行的根本原因。它们都被串在同一条调用链上,通过 proceed()
方法的递归调用依次执行。
当我们从源码的深处浮出水面,AOP 的轮廓变得异常清晰。它并非什么黑魔法,而是两种经典设计模式的一次天作之合:
- 代理模式 (Proxy Pattern): Spring AOP 使用 JDK 动态代理或 CGLIB 在运行时为目标对象创建一个替身。这个替身拦截了所有对原始对象的调用,从而获得了添加额外逻辑的权力。这是 AOP 的结构基础。
- 责任链模式 (Chain of Responsibility): 当多个切面作用于同一个方法时,Spring AOP 将这些切面的
Advice
串成一个调用链。请求(即方法调用)在这个链上传递,每个Advice
都有机会处理它,然后决定是否将请求传递给链上的下一个节点。这是 AOP 的执行核心。
通过这次探险,我们明白了:
- AOP 的织入点,在 Bean 生命周期的初始化之后。
- AOP 的识别,靠的是
Advisor
和Pointcut
的匹配。 - AOP 的执行,靠的是基于
MethodInvocation
的递归调用链。
AOP 是 Spring “无侵入式”编程理念的极致体现。它让我们能够将那些横跨多个模块的“横切关注点”(如日志、事务、安全)从业务逻辑中优雅地剥离出来,让我们的业务代码更纯粹,让系统的结构更清晰。
看穿了 AOP 的源码,你便掌握了改变 Spring 世界运行规则的能力。