1. 从一次线上问题说起:为什么我要啃 DefaultAdvisorAutoProxyCreator 的源码
几年前我在维护一个内部业务框架时遇到过一个很典型的问题:某个切面类明明写了@Aspect注解,日志里也能看到 Bean 被创建了,但业务方法执行时切面逻辑就是不生效。排查了大半天,最后发现是配置里同时存在手动注册的ProxyFactoryBean和自动代理创建器,两者对同一个 Bean 的处理顺序出了问题。那次之后我下定决心把 Spring AOP 的自动代理机制从头到尾捋一遍,而DefaultAdvisorAutoProxyCreator就是这条链路上绕不开的核心类。
这篇文章适合两类人看:一类是已经用过 Spring AOP、写过切面,但对“切面到底是怎么被织入的”只有模糊印象的开发者;另一类是想深入 Spring 源码、但被BeanPostProcessor、Advisor、TargetSource这些概念绕晕的同学。我会从设计思路讲到源码细节,再给出可以直接复现的实操步骤和排查技巧。核心关键词包括DefaultAdvisorAutoProxyCreator、自动代理、Advisor、BeanPostProcessor、AOP 源码,这些概念会贯穿全文。
需要先说明一点:本文涉及的源码分析基于 Spring Framework 5.x 的常见实现,不同小版本之间可能有细微差异,但主干逻辑是稳定的。我补充的很多细节是基于实际调试和常见实践的合理推断,如果你在特定版本上发现对不上,以你本地调试的结果为准。
2. 自动代理的整体设计与思路拆解
2.1 手动代理和自动代理的分水岭在哪
要理解DefaultAdvisorAutoProxyCreator,得先搞清楚 Spring AOP 里“代理”这件事是怎么被触发的。最原始的方式是手动创建代理,比如用ProxyFactory或者ProxyFactoryBean,你得明确告诉 Spring:我要给哪个目标对象、应用哪些通知、生成什么类型的代理。这种方式控制力最强,但代价是每加一个切面就要改一次配置,Bean 多了以后配置会爆炸。
自动代理的思路完全不同:开发者只负责把切面(Advisor 或 @Aspect)定义成 Bean,剩下的事情交给一个“代理创建器”去扫描。这个创建器会在 Bean 初始化的过程中介入,判断当前 Bean 是否匹配某个 Advisor,如果匹配就顺手把代理对象替换掉原始对象。整个过程对业务代码零侵入,这就是自动代理的价值所在。
DefaultAdvisorAutoProxyCreator就是 Spring 提供的一个开箱即用的自动代理创建器。它的定位很明确:自动扫描容器中所有的 Advisor,并把这些 Advisor 应用到匹配的 Bean 上。注意这里的关键词是“所有 Advisor”,也就是说它不区分 Advisor 是谁注册的,只要在容器里能被找到,就会被纳入匹配范围。
2.2 它在继承体系里的位置说明了什么
从类名往下看,DefaultAdvisorAutoProxyCreator继承自AbstractAdvisorAutoProxyCreator,再往上追溯到AbstractAutoProxyCreator,最终实现了BeanPostProcessor和SmartInstantiationAwareBeanPostProcessor接口。这条继承链本身就是一份设计说明书。
BeanPostProcessor意味着它能在 Bean 初始化前后插手,这是自动代理能够“偷偷替换对象”的机制基础。AbstractAutoProxyCreator承担了最核心的模板逻辑:判断是否需要代理、获取 Advisor、创建代理、替换 Bean。而AbstractAdvisorAutoProxyCreator在中间层做了“如何找到 Advisor”的抽象,把“找哪些 Advisor”这件事留给子类决定。
DefaultAdvisorAutoProxyCreator自己做的事情其实很少,它主要重写了findCandidateAdvisors()方法,返回容器中所有类型为Advisor的 Bean。这种“父类定骨架、子类填细节”的模板方法模式,是 Spring 源码里非常典型的套路。理解了这一点,你就明白为什么读它的源码不能只盯着这一个类看,必须把父类的流程一起读。
2.3 为什么默认不启用它
很多人会问:既然自动代理这么好用,为什么 Spring 不默认开启?原因在于隐式行为带来的可预测性问题。自动代理会在你不知情的情况下替换 Bean 实例,如果容器里存在一些不该被代理的 Bean(比如某些基础设施 Bean),或者 Advisor 的匹配范围写得太宽,就可能引发难以定位的 bug。所以 Spring 把它设计成需要显式注册的组件,让你清楚地知道“我开启了自动代理”。
在纯注解配置里,@EnableAspectJAutoProxy背后注册的其实是AnnotationAwareAspectJAutoProxyCreator,它是DefaultAdvisorAutoProxyCreator的“加强版”,额外支持了@Aspect注解的解析。但两者的核心代理逻辑是共享的,所以搞懂DefaultAdvisorAutoProxyCreator,再去看注解版本会轻松很多。
3. 核心细节解析:Advisor 匹配与代理创建的关键环节
3.1 findCandidateAdvisors 到底返回了什么
DefaultAdvisorAutoProxyCreator最核心的改动就在findCandidateAdvisors()这个方法上。它的实现逻辑是:通过BeanFactoryAdvisorRetrievalHelper去容器里查找所有类型为Advisor的 Bean 名称,然后逐个获取实例,过滤掉那些还不是完整 Bean 的候选者。
这里有个容易被忽略的细节:它查找的是所有 Advisor 类型的 Bean,包括你手动注册的、通过其他方式导入的,甚至是一些框架内部注册的。这就解释了为什么有时候你会觉得“我没写这个切面啊,怎么方法被增强了”——很可能是某个依赖包悄悄注册了一个 Advisor。
另一个细节是它对 Bean 完整性的判断。在查找过程中,它会检查 Bean 是否已经创建完成,避免在 Bean 还在创建过程中就把它当作 Advisor 使用,从而引发循环依赖。这个判断在源码里通过isEligibleAdvisorBean之类的逻辑体现,虽然代码不长,但它是保证容器启动不崩的关键。
3.2 匹配过程:ClassFilter 和 MethodMatcher 的双重把关
找到 Advisor 之后,接下来要判断“这个 Advisor 是否适用于当前 Bean”。这个判断由Advisor内部的Pointcut完成,而Pointcut又由ClassFilter和MethodMatcher两部分组成。
ClassFilter负责类级别的粗筛,它决定这个 Advisor 是否可能作用于当前类。如果类都不匹配,后面的方法匹配就不用做了,这是一层性能优化。MethodMatcher负责方法级别的细筛,它又分为静态匹配和动态匹配:静态匹配只看方法签名,结果可以缓存;动态匹配还要看运行时参数,每次调用都要重新判断。
我在实际调试时发现一个常见误区:很多人以为只要方法名匹配上了就会走切面,其实ClassFilter这一关如果没过,方法匹配根本不会执行。所以当你遇到“切面不生效”的问题时,第一步应该确认目标类是否通过了ClassFilter,而不是一上来就怀疑方法匹配。
3.3 代理类型的选择:JDK 动态代理还是 CGLIB
匹配通过后,AbstractAutoProxyCreator会调用createProxy来生成代理对象。这里有一个关键决策:用 JDK 动态代理还是 CGLIB。默认规则是:如果目标类实现了接口,用 JDK 动态代理;如果没有实现任何接口,用 CGLIB。
这个规则背后有明确的理由。JDK 动态代理基于接口生成代理类,代理对象和目标对象实现同一个接口,所以只能代理接口中声明的方法。CGLIB 则是通过继承目标类生成子类来代理,能代理类中所有非 final 的方法,但无法代理 final 类和 final 方法。
注意:如果你的目标类实现了接口,但你想代理接口之外的方法,就必须强制使用 CGLIB。可以通过
proxyTargetClass属性来切换,但切换前要评估目标类是否有 final 方法,否则那些方法会静默地不被代理。
3.4 代理对象的缓存与复用
AbstractAutoProxyCreator内部维护了一个proxyTypes缓存,用来记录某个 Bean 应该生成什么类型的代理。这个缓存的意义在于避免重复计算,尤其是在同一个 Bean 被多次请求代理时。缓存以 Bean 的 class 为 key,value 是代理类型(JDK 或 CGLIB)。
这个设计带来的一个副作用是:如果同一个类在不同的 Bean 定义中需要不同的代理策略,缓存可能会给出不符合预期的结果。不过在实际项目中这种情况很少见,因为同一个类通常只有一种代理需求。理解这个缓存的存在,能帮你在排查“为什么这个 Bean 用了 CGLIB 而不是 JDK 代理”时多一个思路。
4. 实操过程:从零搭建一个可调试的自动代理环境
4.1 环境准备与最小依赖
要复现DefaultAdvisorAutoProxyCreator的行为,不需要引入整个 Spring Boot,用最朴素的 Spring Context 就够了。我建议用 Maven 建一个独立的小工程,依赖只需要spring-context和spring-aop,再加一个日志实现方便观察。
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> </dependency> </dependencies>这里特意加了aspectjweaver,因为后面演示@Aspect风格的 Advisor 时会用到 AspectJ 的切点表达式解析。如果你只用编程式的Advisor,这个依赖可以省掉。
4.2 定义目标 Bean 和 Advisor
先写一个简单的业务接口和实现类,作为被代理的目标。然后定义一个Advisor,我选择用DefaultPointcutAdvisor配合MethodInterceptor,这样最直观,不涉及注解解析的干扰。
public interface OrderService { void createOrder(String orderId); void cancelOrder(String orderId); } public class OrderServiceImpl implements OrderService { @Override public void createOrder(String orderId) { System.out.println("创建订单: " + orderId); } @Override public void cancelOrder(String orderId) { System.out.println("取消订单: " + orderId); } }Advisor 的定义如下,切点只匹配createOrder方法,这样能清楚看到“部分方法被代理”的效果:
@Configuration public class AopConfig { @Bean public OrderService orderService() { return new OrderServiceImpl(); } @Bean public Advisor logAdvisor() { NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.addMethodName("createOrder"); return new DefaultPointcutAdvisor(pointcut, (MethodInterceptor) invocation -> { System.out.println("[前置] 准备执行: " + invocation.getMethod().getName()); Object result = invocation.proceed(); System.out.println("[后置] 执行完成: " + invocation.getMethod().getName()); return result; }); } @Bean public DefaultAdvisorAutoProxyCreator autoProxyCreator() { return new DefaultAdvisorAutoProxyCreator(); } }注意最后那个autoProxyCreator的 Bean 定义,这就是开启自动代理的开关。只要它被注册到容器里,容器中所有匹配 Advisor 的 Bean 都会被自动代理。
4.3 启动容器并观察代理结果
写一个 main 方法启动AnnotationConfigApplicationContext,然后从容器里取出OrderService,打印它的 class 名称,再分别调用两个方法。
public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class); OrderService service = ctx.getBean(OrderService.class); System.out.println("实际类型: " + service.getClass().getName()); service.createOrder("ORD-001"); service.cancelOrder("ORD-002"); ctx.close(); } }运行后你会看到类似这样的输出:实际类型是com.sun.proxy.$ProxyXX,说明 JDK 动态代理生效了;createOrder前后打印了拦截器里的日志,而cancelOrder没有任何额外输出。这个结果验证了两件事:自动代理确实替换了原始 Bean,且切点的匹配是精确到方法的。
4.4 参数计算:切点匹配的性能开销估算
有人担心自动代理会拖慢启动速度,这里可以做个粗略估算。假设容器里有 N 个 Bean,M 个 Advisor,每个 Bean 都要和每个 Advisor 做一次ClassFilter匹配。最坏情况下是 N×M 次类级别判断,但ClassFilter通常很快,因为它只做类型判断,不涉及反射调用。
真正耗时的是方法级别的匹配,尤其是动态匹配。不过 Spring 对静态匹配结果做了缓存,同一个方法只会计算一次。以我实测的一个中等规模项目为例,约 300 个 Bean、20 个 Advisor,启动阶段花在自动代理匹配上的时间大约在几十毫秒量级,基本可以忽略。真正影响启动速度的往往是 Bean 本身的创建和依赖注入,而不是代理匹配。
提示:如果你确实遇到启动慢的问题,可以先通过日志确认是不是代理匹配导致的,再考虑收窄 Advisor 的切点范围,而不是盲目关闭自动代理。
5. 常见问题与排查技巧实录
5.1 切面不生效的排查顺序
这是最高频的问题。我总结的排查顺序是:先确认自动代理创建器是否注册,再确认 Advisor 是否被容器识别,然后确认ClassFilter是否通过,最后确认MethodMatcher是否匹配。这个顺序是从粗到细的,能避免你在细节上浪费时间。
具体操作上,可以在autoProxyCreator的 Bean 定义处打个断点,看findCandidateAdvisors返回的列表是否包含你期望的 Advisor。如果不包含,说明 Advisor 没被注册成 Bean,或者注册的时机太晚。如果包含但方法没被增强,就在ClassFilter和MethodMatcher的返回值上打条件断点,看是哪一关没过。
5.2 循环依赖与代理的相互影响
自动代理和循环依赖放在一起时,问题会变得很微妙。因为代理对象的创建发生在 Bean 初始化的后置处理阶段,如果两个 Bean 互相依赖,其中一个需要被代理,就可能出现“注入的是原始对象还是代理对象”的不一致。
Spring 通过三级缓存和getEarlyBeanReference机制来尽量保证注入的是代理对象,但在某些复杂场景下仍可能出问题。我的经验是:如果发现循环依赖的 Bean 中有一个没被代理,优先检查它是不是通过字段注入提前暴露了原始引用。改成构造器注入或者调整依赖关系,往往能解决。
5.3 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 切面完全不生效 | 自动代理创建器未注册 | 检查容器中是否有 DefaultAdvisorAutoProxyCreator 或 @EnableAspectJAutoProxy |
| 部分方法不生效 | 切点表达式范围不对 | 检查 ClassFilter 和 MethodMatcher 的匹配结果 |
| 代理类型不符合预期 | 目标类实现了接口 | 检查 proxyTargetClass 配置和 proxyTypes 缓存 |
| 启动报循环依赖 | 代理与依赖注入顺序冲突 | 检查是否字段注入提前暴露了原始对象 |
| final 方法不被增强 | CGLIB 无法代理 final 方法 | 确认目标方法是否 final,考虑改用接口代理 |
5.4 几个我踩过的坑
第一个坑是 Advisor 的注册顺序。如果 Advisor 在自动代理创建器之后才注册,有可能在创建器扫描时还看不到它。虽然 Spring 做了不少兼容处理,但在某些手动注册的场景下仍会出问题。我的做法是把所有 Advisor 的注册放在自动代理创建器之前,或者干脆用注解方式让 Spring 统一管理。
第二个坑是切点表达式写得太宽。有一次我写了一个匹配execution(* com.example..*.*(..))的切点,结果把一些基础设施 Bean 也代理了,导致序列化时出现奇怪的错误。后来把包路径收窄到具体的业务包,问题就消失了。切点范围宁窄勿宽,这是血泪教训。
第三个坑是忽略了proxyTargetClass的默认值。在纯DefaultAdvisorAutoProxyCreator场景下,它默认是 false,也就是优先用 JDK 动态代理。如果你期望用 CGLIB 但没显式设置,就会得到 JDK 代理,进而导致接口之外的方法无法被增强。
6. 从源码回到实践:我对自动代理的一点个人体会
把DefaultAdvisorAutoProxyCreator的源码读透之后,我最大的收获不是记住了多少方法名,而是理解了 Spring 在“隐式增强”和“可控性”之间做的权衡。它把复杂的代理创建逻辑封装在父类里,把变化点留给子类,这种设计让自动代理既能开箱即用,又能按需扩展。
在实际项目里,我现在更倾向于用@EnableAspectJAutoProxy而不是手动注册DefaultAdvisorAutoProxyCreator,因为前者对注解的支持更完整,配置也更简洁。但每当遇到代理相关的疑难问题时,我还是会回到DefaultAdvisorAutoProxyCreator这条链路上来找答案,因为它是整个自动代理机制最纯粹的形态。
最后分享一个小技巧:如果你想快速确认某个 Bean 是否被代理,不用打断点,直接在启动后打印bean.getClass().getName(),看到$Proxy或$$EnhancerBySpringCGLIB前缀就说明被代理了。这个方法简单粗暴,但在排查问题时特别管用。