首页 / 资讯中心 / 文章详情

Spring AOP自动代理机制源码解析:DefaultAdvisorAutoProxyCreator

Spring AOP自动代理机制源码解析:DefaultAdvisorAutoProxyCreator ★ FEATURED ARTICLE
1. 从一次线上故障说起为什么我要啃 DefaultAdvisorAutoProxyCreator 的源码去年帮一个团队排查一个挺诡异的问题他们在一个 Spring 老项目里加了个自定义注解想给标注了该注解的 Service 方法统一加上日志和耗时统计。注解写了切面也写了Aspect也标了结果启动之后发现——有的 Service 生效了有的死活不生效还有的莫名其妙被代理了两次日志打了两遍。更离谱的是同一个类在 A 环境正常在 B 环境就失效。他们一开始怀疑是切点表达式写错了改了好几版execution表达式没用。又怀疑是 Bean 加载顺序问题加了一堆Order还是没用。最后把问题抛给我我扫了一眼他们的配置发现根子上根本不是切点的问题而是他们压根没搞清楚 Spring AOP 的自动代理到底是谁在什么时候、按什么规则决定要不要给这个 Bean 生成代理。这个谁就是DefaultAdvisorAutoProxyCreator。很多人用 Spring AOP 用了好几年写Aspect、写Around、写Pointcut都很熟练但一旦遇到代理没生效代理重复代理了不该代理的类这类问题就抓瞎了。原因很简单大家学的是怎么用但没学Spring 在背后是怎么决定用不用、用哪个。而DefaultAdvisorAutoProxyCreator恰恰就是这套决策机制的核心执行者。这篇内容我打算把DefaultAdvisorAutoProxyCreator从它是什么一直拆到它内部每一步在干什么结合我自己踩过的坑和排查过的案例把源码逻辑讲透。适合两类人看一类是已经会用 Spring AOP、但想搞清楚底层自动代理机制的中高级开发者另一类是正在被代理不生效/代理重复/代理范围失控折磨、想从根上定位问题的同学。看完你应该能做到拿到一个 AOP 不生效的场景能自己顺着源码链路把问题定位到具体环节而不是靠猜和试。2. DefaultAdvisorAutoProxyCreator 到底站在 Spring AOP 的哪个位置2.1 先理清 Spring AOP 里代理创建这件事的分工要理解DefaultAdvisorAutoProxyCreator得先知道 Spring AOP 在容器启动过程中代理这件事是怎么被拆解的。我习惯把它拆成三个角色Advisor通知器它是切点 通知的组合体。切点Pointcut负责判断这个方法要不要被增强通知Advice负责增强的具体逻辑是什么。你写的Aspect类最终会被解析成一个个 Advisor。Advisor 的收集者负责从容器里把所有 Advisor 找出来交给后面用。代理创建者AutoProxyCreator负责在 Bean 初始化之后判断这个 Bean 要不要被代理要的话用哪个 Advisor 集合去生成代理对象。DefaultAdvisorAutoProxyCreator属于第三类但它同时把第二类的活也干了——它自己会去容器里找所有 Advisor。这就是它和AnnotationAwareAspectJAutoProxyCreator也就是EnableAspectJAutoProxy背后那个最大的区别之一。用一句话概括它的定位它是一个 BeanPostProcessor在 Bean 初始化完成后介入自动从容器中收集所有 Advisor然后对匹配的 Bean 生成代理。2.2 它和 EnableAspectJAutoProxy 那条线的关系很多人会混淆我平时写EnableAspectJAutoProxy就能用 AOP 了那DefaultAdvisorAutoProxyCreator是哪来的这里要讲清楚一条继承链。AnnotationAwareAspectJAutoProxyCreator继承自AspectJAwareAdvisorAutoProxyCreator而AspectJAwareAdvisorAutoProxyCreator又继承自AbstractAdvisorAutoProxyCreator。DefaultAdvisorAutoProxyCreator同样继承自AbstractAdvisorAutoProxyCreator。也就是说它们共享了绝大部分自动代理的核心逻辑区别只在如何找到 Advisor这一层对比项DefaultAdvisorAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreator触发方式手动注册 Bean或某些框架自动装配EnableAspectJAutoProxy自动注册Advisor 来源容器中所有 Advisor 类型的 Bean容器中 Advisor Bean 解析Aspect注解生成的 Advisor是否解析 Aspect不解析解析典型使用场景需要完全手动控制 Advisor 集合、或做底层框架扩展日常业务开发写Aspect理解这个区别非常关键。因为当你用EnableAspectJAutoProxy时真正干活的其实是AnnotationAwareAspectJAutoProxyCreator而它复用的正是AbstractAdvisorAutoProxyCreator里那套找 Advisor、判断是否代理、生成代理的骨架。你把DefaultAdvisorAutoProxyCreator的源码读透了等于把整条自动代理链路读透了。2.3 它在 Bean 生命周期中介入的精确时机DefaultAdvisorAutoProxyCreator的顶层接口是BeanPostProcessor更精确地说它实现了SmartInstantiationAwareBeanPostProcessor。它介入 Bean 生命周期的位置是postProcessAfterInitialization——也就是 Bean 完成了属性注入、初始化方法调用之后。这个时机点很重要我画个文字版的顺序帮你建立直觉Bean 实例化构造方法属性填充依赖注入各种 Aware 接口回调BeanPostProcessor.postProcessBeforeInitialization初始化方法PostConstruct、afterPropertiesSet、init-methodBeanPostProcessor.postProcessAfterInitialization←它在这里介入Bean 进入单例池可以被使用为什么必须放在第 6 步因为代理对象是要替换原始对象的。如果太早代理原始 Bean 还没初始化完代理进去会出问题如果太晚Bean 已经被别的 Bean 注入引用了替换就来不及了。放在初始化之后、进入单例池之前是唯一合理的窗口。提示这也是为什么在PostConstruct里调用自己被增强的方法不会走代理——因为那时候代理还没生成你拿到的还是原始对象。3. 拆开 postProcessAfterInitialization代理决策的完整链路3.1 入口方法其实很短重头戏在 wrapIfNecessaryAbstractAutoProxyCreator.postProcessAfterInitialization这个方法本身逻辑不复杂核心就几行先判断 Bean 是不是基础设施类比如 Advice、Advisor、Pointcut 这些本身不该被代理的如果是就直接返回否则调用wrapIfNecessary去尝试包装。这里有个容易被忽略的细节它会对所有 Bean 都走一遍这个方法。也就是说容器里每一个 Bean 初始化完都会经过这里判断一次要不要代理。这也是为什么 AOP 会带来一定的启动开销——Bean 越多这个判断次数越多。不过判断本身很快因为大部分 Bean 在第一步找匹配的 Advisor时就被快速排除了。wrapIfNecessary才是真正干活的地方它内部大致做了这么几件事如果这个 Bean 已经被处理过在targetSourcedBeans缓存里直接返回。调用getAdvicesAndAdvisorsForBean拿到适用于这个 Bean 的 Advisor 数组。如果数组为空说明不需要代理把 Bean 标记为无需代理放进advisedBeans缓存直接返回原始 Bean。如果数组不为空调用createProxy生成代理对象返回。这四步里第 2 步是决策的核心第 4 步是执行的核心。下面分别拆。3.2 getAdvicesAndAdvisorsForBean谁来决定这个 Bean 要不要被代理这个方法在AbstractAdvisorAutoProxyCreator里实现逻辑是调用findCandidateAdvisors()拿到容器里所有候选 Advisor。调用findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName)过滤出真正能应用到当前 Bean 的 Advisor。如果过滤结果为空返回DO_NOT_PROXY否则返回排序后的 Advisor 数组。findCandidateAdvisors()在DefaultAdvisorAutoProxyCreator里的实现就是去容器里getBeansOfType(Advisor.class)把所有 Advisor 类型的 Bean 捞出来。注意它捞的是 Advisor 类型的 Bean不是Aspect注解。这就是为什么你单独用DefaultAdvisorAutoProxyCreator而不配合 AspectJ 解析器时写Aspect是不生效的——因为Aspect类本身不是 Advisor Bean它需要被解析器转换成 Advisor 才行。findAdvisorsThatCanApply这一步是性能关键。它内部用了一个叫AopUtils.canApply的方法逐个 Advisor 判断切点能否匹配当前 Bean 的类。这里有个优化它会先判断类级别的匹配如果类级别都不匹配就不会深入到方法级别。所以如果你的切点写得很宽比如execution(* *(..))那几乎每个 Bean 都要做方法级匹配启动会明显变慢。3.3 createProxy代理对象是怎么被造出来的当确定要代理后createProxy负责生成代理。它内部的关键步骤构建一个ProxyFactory。把原始 Bean、BeanName、所有匹配的 Advisor 塞进去。判断用 JDK 动态代理还是 CGLIB。判断依据是ProxyConfig.proxyTargetClass和 Bean 是否实现了接口。默认情况下如果 Bean 实现了接口就用 JDK 代理否则用 CGLIB。调用proxyFactory.getProxy()拿到代理对象。这里有个非常经典的坑如果一个类没有实现任何接口Spring 会用 CGLIB 生成子类代理。但如果这个类是 final 的或者目标方法是 final/private 的CGLIB 就代理不了。表现就是切面写了但不生效而且不报错静默失效。我排查过好几次这类问题最后都是因为方法被final修饰了。还有一个坑是自调用。即使代理生成成功在类内部用this.methodB()调用另一个被增强的方法也不会走代理因为this是原始对象不是代理对象。这个和DefaultAdvisorAutoProxyCreator本身无关但排查 AOP 问题时经常一起出现得心里有数。4. 三个高频踩坑场景的完整排查链路4.1 场景一切面写了但完全不生效这是最常见的。我一般按这个顺序排查从外到内第一步确认自动代理器到底有没有注册。很多人以为加了 AOP 依赖就自动有了其实不是。如果你用的是DefaultAdvisorAutoProxyCreator这条线得确认它作为一个 Bean 被注册了。可以在启动日志里搜或者直接applicationContext.getBeanNamesForType(DefaultAdvisorAutoProxyCreator.class)看有没有。第二步确认 Advisor 有没有被收集到。如果自动代理器在但findCandidateAdvisors返回空那说明容器里根本没有 Advisor 类型的 Bean。这时候要看你写的切面到底有没有被转换成 Advisor。用Aspect的话必须有对应的解析器AnnotationAwareAspectJAutoProxyCreator或显式配置的AspectJAdvisorFactory在场。第三步确认切点是否匹配。这一步最容易出错。我见过切点写成execution(* com.example.service.*.*(..))但实际类在com.example.services多了个 s的也见过包名大小写不一致的。建议用AopUtils.canApply的思路写个单元测试直接验证切点对目标类的匹配结果比在运行时猜快得多。第四步确认代理方式是否可行。目标类是不是 final目标方法是不是 final/private/static这些都会导致代理静默失效。第五步确认调用方式。是不是自调用是不是通过new出来的对象而不是从容器拿的这两种情况代理都不生效。把这五步走完99% 的不生效问题都能定位到。4.2 场景二同一个方法被代理了两次日志打两遍这个问题的根因通常是注册了多个自动代理器。比如你既用了EnableAspectJAutoProxy又手动注册了一个DefaultAdvisorAutoProxyCreator两个代理器都会对同一个 Bean 尝试生成代理。虽然 Spring 内部有一些去重机制比如advisedBeans缓存但在某些配置下还是会出现重复代理。排查方法打印容器里所有BeanPostProcessor中属于AbstractAutoProxyCreator子类的实例看看有几个。正常情况下应该只有一个。如果发现两个就要确认是不是重复配置了。还有一种情况是 Advisor 本身重复。比如同一个切面被注册了两次或者切点表达式重叠导致同一个通知被应用两次。这种要看findCandidateAdvisors返回的列表里有没有重复的 Advisor 实例。4.3 场景三代理范围失控不该代理的也被代理了DefaultAdvisorAutoProxyCreator有个特性它会扫描容器里所有 Advisor然后对每个 Bean 判断是否匹配。如果你的切点写得太宽或者 Advisor 的切点配置有问题就可能导致大量无关 Bean 被代理。我遇到过一个案例有人写了个切点execution(* *(..))本意是想拦截某个包下的所有方法结果漏写了包名限定导致整个应用几乎所有 Bean 都被代理了。启动慢了好几倍内存也涨了不少。排查这种问题可以在wrapIfNecessary里打个断点或者临时加日志看看哪些 Bean 被判定为需要代理。如果发现数量远超预期就去检查切点表达式。注意DefaultAdvisorAutoProxyCreator默认会代理所有匹配的 Bean包括一些框架内部的 Bean。如果不想代理某些 Bean可以通过设置proxyTargetClass、exposeProxy等属性或者用Advisor的切点精确限定范围。5. 和 AnnotationAwareAspectJAutoProxyCreator 的差异以及什么时候该用哪个5.1 核心差异在 findCandidateAdvisors 这一层前面说过两者共享AbstractAdvisorAutoProxyCreator的骨架差异在findCandidateAdvisors的实现。DefaultAdvisorAutoProxyCreator的实现非常直接beanFactory.getBeansOfType(Advisor.class)把容器里所有 Advisor Bean 捞出来。AnnotationAwareAspectJAutoProxyCreator则复杂得多它先调用父类的findCandidateAdvisors拿到 Advisor Bean然后再通过aspectJAdvisorsBuilder去解析所有标注了Aspect的 Bean把它们转换成 Advisor 加进来。所以它既能处理显式注册的 Advisor也能处理Aspect注解。这个差异决定了两者的适用场景。5.2 什么时候该用 DefaultAdvisorAutoProxyCreator日常业务开发我几乎不会直接用DefaultAdvisorAutoProxyCreator因为EnableAspectJAutoProxy那条线更方便。但在下面这些场景它反而更合适做底层框架或中间件扩展你需要完全控制 Advisor 的收集逻辑不想被Aspect解析干扰。比如你自己实现了一套基于注解的 AOP 机制注解解析后生成 Advisor 注册到容器然后让DefaultAdvisorAutoProxyCreator去收集。需要精确控制代理范围DefaultAdvisorAutoProxyCreator只认 Advisor Bean不会去解析Aspect所以代理范围完全由你注册的 Advisor 决定可控性更强。排查 AOP 问题时做隔离验证当你怀疑是Aspect解析环节出了问题可以临时换成DefaultAdvisorAutoProxyCreator 手动注册 Advisor看问题是否还在。如果换了就好了说明问题出在 AspectJ 解析环节。5.3 一个容易忽略的配置项proxyTargetClassDefaultAdvisorAutoProxyCreator继承自ProxyConfig有个proxyTargetClass属性。默认是false意思是有接口用 JDK 代理没接口用 CGLIB。如果设成true就强制用 CGLIB。这个配置在排查问题时很有用。比如你遇到注入了接口类型但拿不到代理的问题可以试试设成true强制 CGLIB。但要注意CGLIB 对 final 类和 final 方法无能为力设了也可能不生效。还有一个属性是exposeProxy设成true后可以把当前代理对象暴露到AopContext.currentProxy()这样就能解决自调用不走代理的问题。不过这个有性能开销非必要不开。6. 从源码里能学到的几个设计思路6.1 缓存是自动代理性能的关键AbstractAutoProxyCreator里有两个重要缓存advisedBeans和targetSourcedBeans。前者记录哪些 Bean 已经被判断过是否需要代理后者记录哪些 Bean 是用户手动指定了目标源的。这两个缓存的意义在于避免对同一个 Bean 重复做昂贵的 Advisor 匹配计算。因为findAdvisorsThatCanApply涉及切点匹配是相对耗时的操作。有了缓存同一个 Bean 只会被完整判断一次。这个设计思路值得借鉴在需要做是否匹配判断的场景里缓存判断结果往往比优化判断逻辑本身收益更大。6.2 用标记 延迟处理循环依赖自动代理和循环依赖是一对老冤家。如果 A 依赖 BB 依赖 A且两者都需要被代理处理起来就很麻烦。Spring 的做法是在getEarlyBeanReference里提前返回代理对象配合三级缓存解决。AbstractAutoProxyCreator实现了getEarlyBeanReference在 Bean 还在创建过程中就把代理对象暴露出去。这样即使有循环依赖注入的也是代理对象而不是原始对象。这个机制保证了代理对象在整个依赖链里是一致的。理解这一点就能明白为什么有时候在构造方法里注入的 Bean 不是代理对象——因为构造方法执行时Bean 还没走到getEarlyBeanReference那一步。6.3 责任链式的 Advisor 排序findAdvisorsThatCanApply返回的 Advisor 数组是经过排序的。排序规则由Advisor的Order决定。这个排序直接影响了通知的执行顺序。在DefaultAdvisorAutoProxyCreator这条线里排序发生在sortAdvisors方法中。如果你发现通知执行顺序不符合预期八成是 Order 没设对。Aspect场景下可以用Order注解手动注册 Advisor 的场景下可以实现Ordered接口或加Order。7. 我实际排查时常用的几个验证手段7.1 用 BeanPostProcessor 断点确认代理时机最直接的验证方式在AbstractAutoProxyCreator.postProcessAfterInitialization里打断点观察每个 Bean 经过时的beanName和beanClass以及最终是否返回了代理对象。这样能一眼看出哪些 Bean 被代理了、哪些没有。如果不想打断点可以临时加一行日志打印beanName和wrapIfNecessary的返回值类型。注意别在生产环境长期开着日志量会很大。7.2 用 AopUtils 验证切点匹配AopUtils.canApply(Advisor, Class, boolean)这个方法可以直接在单元测试里调用验证某个 Advisor 能否应用到某个类。比在运行时猜快得多。我一般会写一个测试把目标类和切面 Advisor 都准备好然后断言canApply返回 true。如果返回 false就逐步缩小切点范围定位到具体是哪个条件不匹配。7.3 用 AopProxyUtils 确认代理类型AopProxyUtils.ultimateTargetClass(bean)可以拿到代理对象背后的原始类。结合AopUtils.isAopProxy(bean)和AopUtils.isCglibProxy(bean)可以确认一个 Bean 到底是不是代理、用的哪种代理。这几个工具方法在排查代理没生效或代理类型不对时特别有用。7.4 一个我踩过的坑Bean 名称影响代理结果有一次排查一个诡异问题同一个类注册成两个不同 Bean 名称一个被代理了一个没有。最后发现是因为其中一个 Bean 名称命中了某个 Advisor 的切点里的bean()限定符另一个没命中。这个坑提醒我切点表达式里如果用了bean()限定符Bean 名称就成了匹配条件的一部分。排查时不能只看类和方法还要看 Bean 名称。8. 写在最后的一点个人体会DefaultAdvisorAutoProxyCreator这个类代码量不算大但它串起了 Spring AOP 自动代理的整条链路。我自己的经验是不要孤立地读它要把它放在AbstractAutoProxyCreator的继承体系里读放在 Bean 生命周期的时序里读放在你实际遇到的故障场景里读。单纯看源码很容易看完就忘。但如果你带着一个具体的为什么不生效的问题去读每读一段就对照自己的场景验证一下理解会深得多。我上面列的三个踩坑场景每一个都是我真实遇到过的排查过程也都是从猜到顺着源码链路定位的转变。最后分享一个习惯每次遇到 AOP 相关问题我都会先问自己三个问题——自动代理器注册了吗Advisor 收集到了吗切点匹配了吗这三个问题覆盖了 90% 的故障场景。剩下的 10%才需要深入到代理生成和调用链的细节里。把这三个问题变成条件反射排查效率会高很多。
阅读完成 · 觉得有帮助?
咨询建站