Spring AOP 设计解密:代理对象生成、拦截器链调度与注解适配全流程源码解析

文章目录

    • 主要基础接口
      • [1. Advice 通知接口:增强逻辑的载体](#1. Advice 通知接口:增强逻辑的载体)
      • [2. Pointcut 切点接口:精准定位需要增强的方法](#2. Pointcut 切点接口:精准定位需要增强的方法)
      • [3. Advisor 通知器接口:组合 Pointcut 和 Advice](#3. Advisor 通知器接口:组合 Pointcut 和 Advice)
    • 创建代理对象的触发入口
      • ProxyFactoryBean
        • [1. 生成代理对象的源码入口](#1. 生成代理对象的源码入口)
        • [2. 初始化 Advisor 链](#2. 初始化 Advisor 链)
        • [3. 生成单例代理对象](#3. 生成单例代理对象)
    • 动态代理选择机制
    • 代理对象创建过程
      • [1. JDK动态代理实现(JdkDynamicAopProxy)](#1. JDK动态代理实现(JdkDynamicAopProxy))
      • [2. CGLIB代理实现(ObjenesisCglibAopProxy)](#2. CGLIB代理实现(ObjenesisCglibAopProxy))
    • [Spring AOP 拦截器调用的实现](#Spring AOP 拦截器调用的实现)
      • [1. JdkDynamicAopProxy 的 invoke() 拦截](#1. JdkDynamicAopProxy 的 invoke() 拦截)
      • [2. CglibAopProxy 的 intercept() 拦截](#2. CglibAopProxy 的 intercept() 拦截)
      • [3. 目标对象中目标方法的调用](#3. 目标对象中目标方法的调用)
      • [4. AOP 拦截器链的调用](#4. AOP 拦截器链的调用)
      • [5. 配置通知器](#5. 配置通知器)
      • [6. Advice 通知的实现](#6. Advice 通知的实现)
    • 代理执行流程(核心拦截器链机制)
    • 源码分析实践建议

深入Spring AOP源码, 在Spring AOP的设计实现中主要使用了 JDK 动态代理 ,在特定场景下(被代理对象没有实现的接口)也用到了 CGLIB 生成代理 。通过源码设计可以看到,首先是为目标对象建立了代理对象(是 JDK 动态代理CGLIB 实现)。然后启动为代理对象配置的拦截器对横切面(目标方法集合)进行相应的增强 ,将 AOP 的横切面设计Proxy 模式有效地结合起来,实现了在 AOP 中定义好的各种织入方式。

主要基础接口

在Spring AOP框架中,AdvicePointcutAdvisor是三个核心接口,共同定义了切面的行为。

1. Advice 通知接口:增强逻辑的载体

本接口定义了切面的增强方式,封装在连接点执行的横切逻辑;如:前置增强 BeforeAdvice,后置增强 AfterAdvice,异常增强 ThrowsAdvice,环绕增强MethodInterceptor(AOP Alliance 标准) 等。看两个主要的子接口的源码:

2. Pointcut 切点接口:精准定位需要增强的方法

本接口用来定义需要增强的目标方法的集合,一般使用正则表达式去匹配筛选指定范围内的所有满足条件的目标方法。作用是确定在哪些连接点(Joinpoint)(如方法执行)应用增强逻辑。Pointcut 接口有很多实现,我们主要看一下 JdkRegexpMethodPointcutNameMatchMethodPointcut 的实现原理:


JdkRegexpMethodPointcut 的实现源码

NameMatchMethodPointcut 的实现源码

3. Advisor 通知器接口:组合 Pointcut 和 Advice

PointcutAdvice 有效地结合在一起组合成一个完整的切面定义 。它定义了在哪些方法(Pointcut)上执行哪些动作(Advice)。下面看一下 DefaultPointcutAdvisor 的源码实现,它通过持有 PointcutAdvice 属性来将两者有效地结合在一起。

DefaultPointcutAdvisor 实现源码

创建代理对象的触发入口

创建代理的触发点在 Spring AOP 中确实是多地方, Spring AOP 的代理创建机制被设计得相当灵活,可以通过不同层次和方式的 API 或配置来触发。

  • 编程式创建 - 最底层/最灵活 (ProxyFactory) :开发者直接实例化 ProxyFactory
  • 声明式创建 - IoC 容器集成 (ProxyFactoryBean ):当 Spring 容器实例化并配置定义在 XML 或 Java Config 中的 ProxyFactoryBean Bean 时。是早期 Spring AOP 配置的主要方式之一。
  • 自动代理创建器 (AbstractAutoProxyCreator 及其子类 - 容器级自动化) :Spring 容器在 Bean 初始化之后(postProcessAfterInitialization 方法) 调用这些自动代理创建器。创建器检查当前 Bean 是否匹配配置的 Pointcut(即是否需要被代理)。

这里主要以 ProxyFactoryBean 的实现为例,对 AOP 的实现原理进行分析。

ProxyFactoryBean

1. 生成代理对象的源码入口

ProxyFactoryBeangetObject() 方法先对通知器链进行了初始化,然后根据被代理对象类型的不同,生成代理对象。

2. 初始化 Advisor 链

生成单例(singleton)的代理对象在 getSingletonInstance() 方法中完成,这是 ProxyFactoryBean 生成 AopProxy 代理对象的调用入口。代理对象会封装对 target 对象的调用,针对 target 对象的方法调用会被这里生成的代理对象所拦截。

3. 生成单例代理对象

上面的 createAopProxy() 方法,调用了 ProxyFactoryBean 的父类 ProxyCreatorSupport 中的实现。

接着就进入并比较重要的核心源码类DefaultAopProxyFactory,它是AopProxyFactory 接口的实现类;主要看createAopProxy(AdvisedSupport config)方法。

动态代理选择机制

Spring AOP的核心代理选择逻辑由DefaultAopProxyFactory实现,其决策流程如下:


代理选择策略:

  1. 强制CGLIB场景:
    • 显式设置proxyTargetClass=true
    • 目标类未实现任何接口(除SpringProxy外)
    • 历史遗留的optimize=true(Spring 5+已废弃)
  2. 强制JDK代理场景:
    • 目标对象是接口类型
    • 目标对象已是JDK代理类
  3. 默认策略:目标类实现接口时使用JDK代理

设计启示:该策略模式实现使Spring能灵活应对不同代理需求,同时屏蔽底层技术差异。

代理对象创建过程

1. JDK动态代理实现(JdkDynamicAopProxy)

JDK 动态代理 生成 AopProxy 代理对象

通过 JdkDynamicAopProxy 的源码可以非常清楚地看到,其使用了 JDK 动态代理 的方式生成了 代理对象。JdkDynamicAopProxy 实现了 InvocationHandler 接口,并通过 java.lang.reflect.ProxynewProxyInstance()静态方法 生成代理对象并返回。

2. CGLIB代理实现(ObjenesisCglibAopProxy)

CGLIB 生成 AopProxy 代理对象

在为目标对象生成代理对象 之后,在调用 代理对象 的目标方法时,目标方法会进行 invoke()回调(JDK 动态代理) 或 callbacks()回调(CGLIB),然后就可以在回调方法中对目标对象的目标方法进行拦截和增强处理了。

Spring AOP 拦截器调用的实现

代理执行流程(核心拦截器链机制)

在通过 JDK 的 Proxy 类生成代理对象时,相关的拦截器已经配置到了代理对象内部持有的 InvocationHandler 实例 (对于 JDK 代理是 JdkDynamicAopProxy) 所引用的 AdvisedSupport 配置对象中 。拦截器最后起作用,是通过调用代理对象的目标方法时,代理机制会触发其关联的处理器 (InvocationHandler.invoke()),这些处理器回调方法会从 AdvisedSupport 中获取拦截器链并执行。

在通过 CGLIB 生成代理对象时,相关的拦截器已经配置到了代理对象内部持有的 CGLIB Callback 实例 (核心是 DynamicAdvisedInterceptor) 所引用的 AdvisedSupport 配置对象中 。当调用代理对象的目标方法时,代理机制会触发其关联的处理器 (MethodInterceptor.intercept()),这些处理器回调方法会从 AdvisedSupport 中获取拦截器链并执行。

图解关系

text 复制代码
+---------------------+       (配置阶段)
|   ProxyFactoryBean   | -------------------+
| (or ProxyFactory)    |                    |
+----------+----------+                     |
           | Configures                     | Holds
           v                                v
+---------------------+       +--------------------------+
|     AdvisedSupport   | <---- | JdkDynamicAopProxy       | (JDK Handler)
| - TargetSource       |       | - advised: AdvisedSupport |------------------+
| - List<Advice>       |       +--------------------------+                  |
| - List<Advisor>      |                                                    |
| - ...                |       +--------------------------+                  |
+---------------------+       | DynamicAdvisedInterceptor | (CGLIB Callback) |
                              | - advised: AdvisedSupport |------------------+
                              +--------------------------+
                                   ^                           ^
                                   | Associated with            | Associated with
                                   |                           |
+---------------------+       +----+--------+             +-----+--------+
|   Proxy Object      |       | JDK Proxy   |             | CGLIB Proxy  |
| (Implements         |       | Object      |             | (Subclass)   |
|  interface(s))      |       +-------------+             +--------------+
| OR                  |           |                             |
| (Subclass)          |           | Holds                       | Holds
+---------------------+           v                             v
                         +------------------+           +-------------------+
                         | InvocationHandler|           | Callback (e.g.    |
                         | (JdkDynamicAopProxy)|           | DynamicAdvised..) |
                         +------------------+           +-------------------+
                                 |                             |
                                 | (invoke called)             | (intercept called)
                                 v                             v
                          [Gets interceptor chain from AdvisedSupport]
                          [Creates MethodInvocation]
                          [Runs interceptor chain -> calls target method]

前面已经通过两种不同的方式生成了 AopProxy 代理对象,下面我们先看一下 JdkDynamicAopProxy 中的 invoke()回调方法 中对拦截器调用的实现。

1. JdkDynamicAopProxy 的 invoke() 拦截

2. CglibAopProxy 的 intercept() 拦截

CglibAopProxyintercept() 回调方法实现和 JdkDynamicAopProxyinvoke() 非常相似,只是在 CglibAopProxy 中构造 CglibMethodInvocation 对象来完成拦截器链的调用,而在 JdkDynamicAopProxy 中则是通过构造 ReflectiveMethodInvocation 对象来完成的。

3. 目标对象中目标方法的调用

对目标对象中目标方法的调用,JdkDynamicAopProxy是在 AopUtils 工具类中利用反射机制完成的,具体代码如下。

对目标对象中目标方法的调用,CglibAopProxy是直接通过调用 MethodProxyinvoke()MethodProxyinvoke 方法用于在相同类型的另一个对象上调用原始方法(非拦截方法) 。它允许直接调用被代理的方法,绕过 CGLIB 应用的任何拦截器或增强。具体代码如下。

4. AOP 拦截器链的调用

JdkDynamicAopProxyCglibAopProxy 虽然使用了不同的代理对象,但对 AOP 拦截的处理却是相同的,都是通过 ReflectiveMethodInvocationproceed() 方法实现的。

责任链模式:通过递归调用实现拦截器链的顺序执行,每个拦截器控制是否向下传递

思考点:为什么拦截器链为空 时,JDK 代理:直接通过 AopUtils.invokeJoinpointUsingReflection(target, method, args) 调用;而CGLIB 代理:直接通过 methodProxy.invoke(target, args) 调用;拦截器链不为空 时,两种代理统一使用 AopUtils.invokeJoinpointUsingReflection(target, method, args) 调用原始方法?

解答: 这种设计是 Spring 在架构一致性、维护成本和性能优化之间做出的合理权衡。虽然理论上 CGLIB 在拦截链场景也能用 methodProxy.invoke(),但实际收益微乎其微,不值得为此破坏架构的纯净性。

到此, Spring AOP的过程就结束了, 下面在看下拦截器链(配置的通知器)的获取源码细节。

5. 配置通知器

由源码可知配置的通知器是在AdvisedSupport 类中获取的;AdvisedSupport 中实现了获取拦截器链的方法 ,并使用了缓存

获取拦截器链的工作是由 AdvisorChainFactory 完成的,他是一个拦截器链的生成工厂 。由于 AdvisorChainFactory 接口只有一个默认实现类 DefaultAdvisorChainFactory,所以我们直接看这个类中的实现。

由上面源码可看出 advisor 通知器(拦截器链)是从 AdvisedSupport 中获取的,而 advisor 的初始化 则是在 ProxyFactoryBeangetObject() 方法中完成的,在前面我们已经看过源码了。

注意,Advisor 本身就被配置为 bean,所以它的获取也是通过 IoC 容器 获得的。

6. Advice 通知的实现

DefaultAdvisorChainFactory 类中的 getInterceptorsAndDynamicInterceptionAdvice() 方法我们可以看到,其通过 AdvisorAdapterRegistry 实例对象的 getInterceptors() 方法,利用配置的 advisor 完成了对拦截器的适配和注册
DefaultAdvisorAdapterRegistrygetInterceptors()方法 封装了 advice 织入实现的入口。

DefaultAdvisorAdapterRegistry 的实现中可以看到,其使用了MethodBeforeAdviceAdapterAfterReturningAdviceAdapterThrowsAdviceAdapter 等一系列的 AdviceAdapter 适配器;它们完全和 Advice 的类型一一对应 ,它们都是实现了 AdviceAdapter 接口的同一层次类,各自承担着不同的适配任务 ,一对一地服务于不同的 Advice 实现。

下面我们主要看下 MethodBeforeAdviceAdapter 的源码实现。

可以看到,其中的 getInterceptor()方法 把 AdviceAdvisor 中取出来,然后创建了一个 MethodBeforeAdviceInterceptor 对象,并返回,这个对象中持有对 Advice 的引用。下面我们看一下 MethodBeforeAdviceInterceptor 拦截器的源码实现。

可以看到,MethodBeforeAdviceInterceptorinvoke()方法 先是触发了 advice 的 before()方法,然后才是 MethodInvocation 的 proceed()方法调用。

简单回顾 一下之前的代码,在 AopProxy 代理对象 触发的 ReflectiveMethodInvocationproceed() 中,在取得 拦截器 interceptor 后调用了其 invoke()方法。按照 AOP 的配置规则,ReflectiveMethodInvocation 触发的拦截器 invoke()回调,最终会根据 Advice 类型的不同,触发 Spring 对不同的 Advice 的拦截器封装,比如 MethodBeforeAdvice 最终会触发 MethodBeforeAdviceInterceptorinvoke()回调,其它两个拦截器与此类似。

代理执行流程(核心拦截器链机制)

执行链构建流程(以JDK代理实现为例的图示)

源码分析实践建议

  1. 调试技巧
properties 复制代码
# 启用代理类保存
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

# 开启AOP调试日志
logging.level.org.springframework.aop=DEBUG
  1. 性能优化点
    • 避免宽泛切点表达式(如execution(* *.*(..))
    • 优先使用@annotation精确匹配注解方法
    • 自调用问题通过AopContext.currentProxy()解决
  2. 核心源码路径 :
    • 代理创建:org.springframework.aop.framework.DefaultAopProxyFactory
    • 执行链:org.springframework.aop.framework.ReflectiveMethodInvocation
    • 注解解析:org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator

最佳实践 :结合@EnableAspectJAutoProxy(exposeProxy=true)解决内部调用代理失效问题,深度理解Spring AOP需把握代理创建时机执行链机制注解适配原理三大核心环节。

通过源码分析可见,Spring AOP通过精妙的责任链模式动态代理技术 的结合,实现了声明式切面编程能力,其设计充分体现了开闭原则单一职责原则,是框架设计中模式应用的典范。很值得我们学习借鉴,与诸君共勉吧!


End!

相关推荐
界面开发小八哥11 分钟前
「Java EE开发指南」如何用MyEclipse创建一个WEB项目?(三)
java·ide·java-ee·myeclipse
ai小鬼头29 分钟前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
idolyXyz37 分钟前
[java: Cleaner]-一文述之
java
一碗谦谦粉1 小时前
Maven 依赖调解的两大原则
java·maven
萧曵 丶1 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
netyeaxi1 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
收破烂的小熊猫~1 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
小七mod2 小时前
【MyBatis】MyBatis与Spring和Spring Boot整合原理
spring boot·spring·mybatis
猴哥源码2 小时前
基于Java+SpringBoot的动物领养平台
java·spring boot
老任与码2 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba