news 2025/12/17 15:18:39

Spring AOP 源码深度解析:从代理创建到通知执行的完整链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AOP 源码深度解析:从代理创建到通知执行的完整链路

Spring AOP 源码深度解析:从代理创建到通知执行的完整链路

在上一篇文章中,我们掌握了 Spring AOP 的基本用法和核心概念。但“知其然”之后,更要“知其所以然”。

今天,我们将深入 Spring Framework 源码(以Spring 6.2.x为例),一步步拆解Spring AOP 的实现原理,揭开动态代理与通知链的神秘面纱。


一、入口:@EnableAspectJAutoProxy做了什么?

当你在配置类上加上:

@EnableAspectJAutoProxy public class AopConfig {}

这个注解的定义如下(简化):

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(AspectJAutoProxyRegistrar.class) // ← 关键! public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }

▶ 核心动作:注册一个 BeanDefinition 后处理器

AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,它的作用是在容器启动时向 Spring 容器注册一个特殊的 BeanPostProcessor

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(...) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); } }

继续跟进,最终会注册一个名为internalAutoProxyCreator的 Bean,其实现类是:

AnnotationAwareAspectJAutoProxyCreator

这是整个 Spring AOP 的核心引擎,它继承自:

AnnotationAwareAspectJAutoProxyCreator └── AspectJAwareAdvisorAutoProxyCreator └── AbstractAdvisorAutoProxyCreator └── AbstractAutoProxyCreator └── SmartInstantiationAwareBeanPostProcessor

也就是说,它是一个BeanPostProcessor—— 这意味着它能在每个 Bean 初始化前后进行干预!

二、代理对象何时创建?

AbstractAutoProxyCreator重写了postProcessAfterInitialization方法:

@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); // ← 关键方法 } } return bean; }

wrapIfNecessary:决定是否需要代理

这个方法做了三件事:

  1. 跳过不需要代理的 Bean(如 Advisor、Advice 本身)
  2. 查找所有匹配当前 Bean 的 Advisor(通知器)
  3. 如果有匹配的 Advisor,则创建代理对象
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 1. 获取所有适用的 Advisor(包含 Pointcut + Advice) Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 2. 创建代理 Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return bean; }

关键点:只有存在匹配的切面(Advisor),才会为该 Bean 创建代理!

三、如何查找匹配的 Advisor?—— 切面的“匹配逻辑”

getAdvicesAndAdvisorsForBean()最终会调用:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { List<Advisor> candidateAdvisors = this.findCandidateAdvisors(); List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); this.extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { try { eligibleAdvisors = this.sortAdvisors(eligibleAdvisors); } catch (BeanCreationException ex) { throw new AopConfigException("Advisor sorting failed with unexpected bean creation, probably due to custom use of the Ordered interface. Consider using the @Order annotation instead.", ex); } } return eligibleAdvisors; }

其中,findAdvisorsThatCanApply会遍历每个Advisor,并调用其内部的PointcutgetClassFilter().matches()getMethodMatcher().matches()

例如,你写的:

@Pointcut("execution(public * org.example.spring.aop.service..*.*(..))")

会被解析为一个AspectJExpressionPointcut,其matches()方法会使用AspectJ 表达式引擎判断目标方法是否匹配。

🔍 注意:Spring AOP 虽然使用 AspectJ 的注解和表达式语法,但匹配逻辑由 Spring 自己实现,并未依赖完整的 AspectJ 编译器。

四、代理对象如何创建?—— JDK Proxy vs CGLIB

createProxy()方法内部会根据配置和目标类特性选择代理方式:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 满足任一条件时,优先考虑使用 CGLIB 代理: // - config.setOptimize(true):启用优化(已废弃,但保留兼容) // - config.setProxyTargetClass(true):强制使用 CGLIB(如 @EnableAspectJAutoProxy(proxyTargetClass = true)) // - 没有用户显式指定接口(即目标类未实现任何业务接口) if (config.isOptimize() || config.isProxyTargetClass() || !config.hasUserSuppliedInterfaces()) { Class<?> targetClass = config.getTargetClass(); // 校验:必须能确定目标类型(要么有目标类,要么有接口) if (targetClass == null && config.getProxiedInterfaces().length == 0) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // 若目标类为 null、是接口、已是 JDK 代理类、或是 Lambda 表达式生成的类, // 则仍使用 JDK 动态代理(CGLIB 无法代理接口或特殊类) if (targetClass == null || targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 否则使用 CGLIB 代理(通过 Objenesis 优化实例化性能) return new ObjenesisCglibAopProxy(config); } else { // 明确指定了接口且未强制 CGLIB → 使用 JDK 动态代理 return new JdkDynamicAopProxy(config); } }
  • 若目标类实现了接口 → 默认用JDK Proxy
  • 若未实现接口 或 设置了proxyTargetClass=true→ 用CGLIB

✅ Spring 默认优先使用 JDK 动态代理(基于接口),只有在必要时才回退到 CGLIB。

五、方法调用时:通知如何执行?—— 责任链模式

假设我们获取的是一个JDK 代理对象,其InvocationHandlerJdkDynamicAopProxy

当调用orderService.placeOrder(...)时,实际执行的是:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 构建拦截器链(Interceptor Chain) List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { // 无通知,直接调用目标方法 return method.invoke(target, args); } else { // 2. 执行责任链 MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); return invocation.proceed(); // ← 递归执行通知链 } }

▶ 拦截器链(Interceptor Chain)的组成

每个@Before@AfterReturning等注解,最终都会被包装成一个MethodInterceptor

注解对应的 Interceptor
@BeforeMethodBeforeAdviceInterceptor
@AfterReturningAfterReturningAdviceInterceptor
@Around直接实现MethodInterceptor
@AfterThrowingThrowsAdviceInterceptor

这些 Interceptor 按照通知类型 + 切面优先级排序,形成一条链。

proceed()的递归执行机制

ReflectiveMethodInvocation.proceed()是典型的责任链递归实现:

public Object proceed() throws Throwable { if (currentInterceptorIndex == interceptors.length - 1) { // 所有通知执行完毕,调用目标方法 return method.invoke(target, arguments); } // 获取下一个通知 Object interceptor = interceptors[++currentInterceptorIndex]; if (interceptor instanceof MethodInterceptor) { return ((MethodInterceptor) interceptor).invoke(this); // ← 递归 } // ... }

@Before+@Around+@AfterReturning为例,执行顺序如下:

Around.before() → Before通知 → 目标方法 → AfterReturning通知 → Around.after()

环绕通知(@Around)拥有最高控制权:它可以决定是否调用proceed(),甚至修改参数或返回值。

六、为什么“自调用”不触发 AOP?

考虑以下代码:

@Service public class OrderService { public void methodA() { this.methodB(); // ← 自调用! } @Transactional public void methodB() { ... } }

▶ 根本原因:绕过了代理对象

  • Spring 容器中保存的是代理对象(Proxy)
  • 但在methodA()内部,this指向的是原始的 OrderService 实例,而非代理
  • 因此this.methodB()直接调用原始方法,不会进入JdkDynamicAopProxy.invoke()

▶ 解决方案

  1. 注入自身(推荐)
@Autowired private OrderService self; // 注入的是代理对象 public void methodA() { self.methodB(); // 通过代理调用 }
  1. 使用AopContext.currentProxy()(需开启暴露):
@EnableAspectJAutoProxy(exposeProxy = true) public class AopConfig {} public void methodA() { ((OrderService) AopContext.currentProxy()).methodB(); }

⚠️ 注意:第二种方式耦合了 AOP 框架,一般不推荐。

七、总结 Spring AOP 执行流程

[Spring 容器启动] ↓ @EnableAspectJAutoProxy → 注册 AnnotationAwareAspectJAutoProxyCreator ↓ Bean 初始化完成 → postProcessAfterInitialization() ↓ wrapIfNecessary() → 查找匹配的 Advisor ↓ 存在匹配切面? → 是 → createProxy() → 返回代理对象 ↓ 否 返回原始 Bean ↓ 调用代理方法 → JdkDynamicAopProxy.invoke() / CglibAopProxy.intercept() ↓ 构建 Interceptor 链 → ReflectiveMethodInvocation.proceed() ↓ 依次执行 @Before → @Around → 目标方法 → @AfterReturning/@AfterThrowing → @After

八、结语:AOP 的本质是“代理 + 责任链”

Spring AOP 并非魔法,而是巧妙结合了:

  • BeanPostProcessor:在 Bean 初始化后动态包装
  • 动态代理(JDK/CGLIB):拦截方法调用
  • 责任链模式:组织多个通知的执行顺序
  • AspectJ 表达式:提供灵活的切入点匹配能力

理解了这套机制,你不仅能用好 AOP,还能在排查“为什么 AOP 不生效”时直击根源

📌关注我,每天5分钟,带你从 Java 小白变身编程高手!
👉 点赞 + 关注,让更多小伙伴一起进步!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/12 1:24:16

我为什么要离开家乡,来北京打拼?(说说我自己的故事...)

建了一个新号&#xff1a;1. 讲职场与第二曲线&#xff1b;2. 聊自己的故事&#xff0c;内心的感悟。谢谢大家&#xff0c;听我的故事。希望对大伙也有帮助。最近做了一个新产品&#xff1a;70天&#xff0c;每天30分钟&#xff0c;短视频行动营&#xff08;第二曲线最佳选择&a…

作者头像 李华
网站建设 2025/12/14 8:28:36

如何在 LTspice放置 .op data 并能够设置显示的小数点个数?

简 介&#xff1a; 本文介绍了在LTspice中格式化.op数据标签的方法。通过使用round函数可以设置显示数据的小数点位数&#xff0c;使仿真结果更加简洁直观。具体操作是右键点击.op数据标签&#xff0c;使用round函数调整小数位数。这种方法能有效优化电路静态偏置量的显示效果&…

作者头像 李华
网站建设 2025/12/12 1:20:56

Wan2.2-T2V-A14B支持长时间序列生成吗?实测60秒连续输出

Wan2.2-T2V-A14B支持长时间序列生成吗&#xff1f;实测60秒连续输出 在影视制作、广告创意和虚拟内容生产领域&#xff0c;一个长期悬而未决的难题是&#xff1a;AI能否真正理解“时间”&#xff1f; 不是简单拼接几帧画面&#xff0c;也不是靠后期插值强行延长视频&#xff…

作者头像 李华
网站建设 2025/12/12 1:18:34

【高效运维必看】:Agent服务在Docker中跨环境迁移的7种优化方案

第一章&#xff1a;Agent服务在Docker中跨环境迁移的核心挑战在将Agent服务通过Docker容器化部署并实现跨环境迁移的过程中&#xff0c;尽管容器技术提供了“一次构建&#xff0c;处处运行”的理想承诺&#xff0c;实际落地仍面临诸多核心挑战。这些挑战主要集中在配置管理、网…

作者头像 李华
网站建设 2025/12/12 1:16:03

深度指南:如何设计Prompt引导DeepSeek生成高效的分步故障排查流程

深度指南&#xff1a;如何设计Prompt引导DeepSeek生成高效的分步故障排查流程在当今技术驱动的世界中&#xff0c;系统、设备或应用程序出现故障几乎是不可避免的。快速、准确地定位并解决这些故障对于维持业务连续性、提升用户体验以及降低运营成本至关重要。传统的故障排查手…

作者头像 李华