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 的生命周期:实例化 -> 属性填充 -> 初始化postProcessAfterInitializationBeanPostProcessor 提供的、在 Bean 完成所有初始化(包括执行完 init-method)之后的最后一个干预点。

为什么是这个时机?因为它完美地保证了:

  1. Bean 已就绪: 原始的 Bean 对象已经是一个完全体,所有该有的依赖和状态都已准备好。
  2. 替换正当时: 此时去创建一个代理并替换掉原始 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。

  1. 判断: 如果侦察兵带回了至少一个适用的 Advisor,那么就意味着这个 Bean 需要被代理。

  2. 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 的决策逻辑如下:

  1. 默认策略: 如果目标 Bean 实现了接口,Spring 默认使用 JDK 动态代理
  2. 强制 CGLIB: 如果目标 Bean 没有实现任何接口,Spring 别无选择,只能使用 CGLIB
  3. 用户干预: 如果用户在配置中(例如通过 @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 接口的特定对象。例如,AspectJAfterAdviceAspectJMethodBeforeAdvice 等。
  • Pointcut (切点): “在哪里做”
  • 它定义了一个匹配规则,用于精准地找到需要被增强的目标方法。
  • 最常用的实现是 AspectJExpressionPointcut,它能够解析我们熟知的 execution(* com.example.service.*.*(..)) 这样的 AspectJ 表达式。它内部通过 AspectJ 的库来解析表达式,并生成一个“影子匹配器”(ShadowMatch),在运行时可以判断任何给定的方法是否符合该表达式。
  • Advisor (通知器): “在什么地方,做什么事”
  • 它是一个“小而美”的聚合对象,是 AdvicePointcut 的合体。它将“做什么”和“在哪里做”这两个信息绑定在了一起。
  • 在 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 内部就包含了:

  1. 一个 AspectJExpressionPointcut,其表达式为 execution(* com.example.service.*.*(..))
  2. 一个 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 代理)

无论哪种代理方式,它们最终都会走到一个相似的逻辑中:

  1. 获取当前方法匹配的所有 Advisor 列表(即 Advice 列表)。
  2. 创建一个 MethodInvocation 对象。
  3. 执行调用链。

我们以 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 计数器和递归调用,完美地构建了一个责任链:

  1. proceed() 方法被第一次调用时,它从 Advice 链中取出第 0 个 Advice
  2. 然后调用这个 Adviceinvoke 方法,并将 MethodInvocation 自身(this)作为参数传进去。
  3. 这个 Advice 在执行完自己的前置逻辑后,如果想让调用链继续下去,它就必须调用 MethodInvocation.proceed() 方法。
  4. 这一调用,又会回到 proceed() 方法内部,此时 currentInterceptorIndex 已经自增,它会取出第 1 个 Advice 并执行……
  5. 如此往复,形成一个“洋葱式”的调用结构:Advice1 -> Advice2 -> Advice3 -> 目标方法 -> Advice3 -> Advice2 -> Advice1
  6. currentInterceptorIndex 到达链的末端时,proceed() 方法会调用 invokeJoinpoint(),这才是真正通过反射调用我们原始 Bean 的那个方法。
  7. 目标方法返回后,调用结果会沿着递归调用栈原路返回,每个 @Around Advice 都有机会处理返回值或抛出异常。

这就是 @Around, @Before, @After 等通知能够按照预期顺序执行的根本原因。它们都被串在同一条调用链上,通过 proceed() 方法的递归调用依次执行。

当我们从源码的深处浮出水面,AOP 的轮廓变得异常清晰。它并非什么黑魔法,而是两种经典设计模式的一次天作之合:

  1. 代理模式 (Proxy Pattern): Spring AOP 使用 JDK 动态代理或 CGLIB 在运行时为目标对象创建一个替身。这个替身拦截了所有对原始对象的调用,从而获得了添加额外逻辑的权力。这是 AOP 的结构基础
  2. 责任链模式 (Chain of Responsibility): 当多个切面作用于同一个方法时,Spring AOP 将这些切面的 Advice 串成一个调用链。请求(即方法调用)在这个链上传递,每个 Advice 都有机会处理它,然后决定是否将请求传递给链上的下一个节点。这是 AOP 的执行核心

通过这次探险,我们明白了:

  • AOP 的织入点,在 Bean 生命周期的初始化之后
  • AOP 的识别,靠的是AdvisorPointcut 的匹配。
  • AOP 的执行,靠的是基于MethodInvocation 的递归调用链。

AOP 是 Spring “无侵入式”编程理念的极致体现。它让我们能够将那些横跨多个模块的“横切关注点”(如日志、事务、安全)从业务逻辑中优雅地剥离出来,让我们的业务代码更纯粹,让系统的结构更清晰。

看穿了 AOP 的源码,你便掌握了改变 Spring 世界运行规则的能力。