SpringAOP两套代理创建路线设计哲学与开源实践

同一个 bean 谁来包代理:Spring AOP 两套代理创建路线的设计哲学与开源实践

Spring 给一个 bean 织入增强有两条完全不同的路线:一条是 AbstractAutoProxyCreator------代理的"所有者",统揽一切、独占代理命运、为循环依赖提前暴露兜底;另一条是 AbstractAdvisingBeanPostProcessor------增强的"贡献者",只往已有代理上幂等叠加一条 advisor,不拥有代理、不为循环依赖负责。@Async 在循环依赖里静默失效,根因不是 bug,而是后一套哲学自带的取舍。本文从源码拆开这两套设计,推演"为什么不能合并",并按真实可考的 Spring 自家模块给出选型判据。


S --- Situation:同一个 bean,两套代理创建者并存

先讲清楚 Spring AOP 给一个普通 bean 加增强时,系统里同时存在着两条互不相通的路线。
#mermaid-svg-QxNKas1gbbD5jhFh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QxNKas1gbbD5jhFh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QxNKas1gbbD5jhFh .error-icon{fill:#552222;}#mermaid-svg-QxNKas1gbbD5jhFh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QxNKas1gbbD5jhFh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QxNKas1gbbD5jhFh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QxNKas1gbbD5jhFh .marker.cross{stroke:#333333;}#mermaid-svg-QxNKas1gbbD5jhFh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QxNKas1gbbD5jhFh p{margin:0;}#mermaid-svg-QxNKas1gbbD5jhFh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QxNKas1gbbD5jhFh .cluster-label text{fill:#333;}#mermaid-svg-QxNKas1gbbD5jhFh .cluster-label span{color:#333;}#mermaid-svg-QxNKas1gbbD5jhFh .cluster-label span p{background-color:transparent;}#mermaid-svg-QxNKas1gbbD5jhFh .label text,#mermaid-svg-QxNKas1gbbD5jhFh span{fill:#333;color:#333;}#mermaid-svg-QxNKas1gbbD5jhFh .node rect,#mermaid-svg-QxNKas1gbbD5jhFh .node circle,#mermaid-svg-QxNKas1gbbD5jhFh .node ellipse,#mermaid-svg-QxNKas1gbbD5jhFh .node polygon,#mermaid-svg-QxNKas1gbbD5jhFh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QxNKas1gbbD5jhFh .rough-node .label text,#mermaid-svg-QxNKas1gbbD5jhFh .node .label text,#mermaid-svg-QxNKas1gbbD5jhFh .image-shape .label,#mermaid-svg-QxNKas1gbbD5jhFh .icon-shape .label{text-anchor:middle;}#mermaid-svg-QxNKas1gbbD5jhFh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QxNKas1gbbD5jhFh .rough-node .label,#mermaid-svg-QxNKas1gbbD5jhFh .node .label,#mermaid-svg-QxNKas1gbbD5jhFh .image-shape .label,#mermaid-svg-QxNKas1gbbD5jhFh .icon-shape .label{text-align:center;}#mermaid-svg-QxNKas1gbbD5jhFh .node.clickable{cursor:pointer;}#mermaid-svg-QxNKas1gbbD5jhFh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QxNKas1gbbD5jhFh .arrowheadPath{fill:#333333;}#mermaid-svg-QxNKas1gbbD5jhFh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QxNKas1gbbD5jhFh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QxNKas1gbbD5jhFh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QxNKas1gbbD5jhFh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QxNKas1gbbD5jhFh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QxNKas1gbbD5jhFh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QxNKas1gbbD5jhFh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QxNKas1gbbD5jhFh .cluster text{fill:#333;}#mermaid-svg-QxNKas1gbbD5jhFh .cluster span{color:#333;}#mermaid-svg-QxNKas1gbbD5jhFh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QxNKas1gbbD5jhFh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QxNKas1gbbD5jhFh rect.text{fill:none;stroke-width:0;}#mermaid-svg-QxNKas1gbbD5jhFh .icon-shape,#mermaid-svg-QxNKas1gbbD5jhFh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QxNKas1gbbD5jhFh .icon-shape p,#mermaid-svg-QxNKas1gbbD5jhFh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QxNKas1gbbD5jhFh .icon-shape .label rect,#mermaid-svg-QxNKas1gbbD5jhFh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QxNKas1gbbD5jhFh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QxNKas1gbbD5jhFh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QxNKas1gbbD5jhFh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 路线一 AbstractAutoProxyCreator
路线二 AbstractAdvisingBeanPostProcessor
普通 bean rawBean 进入初始化
谁来给它加增强
扫描全局 Advisor 注册表getAdvicesAndAdvisorsForBean统一组装一条拦截链
只验证自带这一条 advisorisEligible适合就挂 不适合就走
拥有代理对象建账本 advisedBeans earlyProxyReferences
不拥有代理已有代理就 addAdvisor 没有才建最小代理

这两条路线不是新旧之分,也不是优劣之分,而是 Spring 在 AOP 子系统里做的一次职责分治。它们的真实分工,从框架内的具体子类一眼就能看出来:

路线 框架内代表实现 服务的注解/功能
路线一 AbstractAutoProxyCreator AnnotationAwareAspectJAutoProxyCreator @AspectJ 切面、@Transactional
路线一 InfrastructureAdvisorAutoProxyCreator <tx:annotation-driven>
路线一 BeanNameAutoProxyCreator / DefaultAdvisorAutoProxyCreator 编程式批量代理
路线二 AbstractAdvisingBeanPostProcessor AsyncAnnotationBeanPostProcessor @Async
路线二 MethodValidationPostProcessor 方法级 @Valid 校验
路线二 PersistenceExceptionTranslationPostProcessor @Repository 异常翻译

一个 bean 同时被两条路线盯上时(典型:同时带 @Transactional 和 @Async),看似会出问题------但绝大多数情况它工作得很好。反常出现在循环依赖里 :@Async 的异步语义悄无声息地失效了,不报错、不告警,调用方以为方法在异步线程里跑,实际还在调用线程里同步执行。这个静默失效,就是这两套设计哲学差异最容易暴露的断点。

T --- Task:三个目标,一个约束

  1. 拆清两条路线 :从源码层面说清楚,AbstractAutoProxyCreator 和 AbstractAdvisingBeanPostProcessor 各自怎么决策、怎么建代理、怎么防重复;
  2. 解释循环依赖的失效:为什么路线一能在循环依赖里兜住,路线二扛不住------根因不在"少写了一个方法",而在"职责定位决定生命周期介入深度";
  3. 回答一个反问 :能不能直接给 AsyncAnnotationBeanPostProcessor 实现 getEarlyBeanReference 来规避?为什么 Spring 没有这么做。

约束:不脱离当前可考的 Spring 源码(SPR-5.x)编造"知名开源框架都怎么选";关于第三方框架的例子,只点名能在 Spring 仓库里直接核实的 Spring 自家模块,不杜撰外部项目的内部实现。

A --- Action:源码拆解、循环依赖推演、反方案证伪

第一步:路线一的"拥有者"哲学------建代理、记代理、管代理一辈子

两条路线的根差,可以用一个角色名概括:路线一(AbstractAutoProxyCreator)是代理的拥有者 ------它建代理、它记代理、它管代理一辈子;路线二(AbstractAdvisingBeanPostProcessor)是装饰者------它不建代理,只在已有代理上挂一条 advisor。这两词不是修辞,后面所有现象都从这两套代码行为差异里长出来。

先看拥有者怎么"建":wrapIfNecessary 里调 getAdvicesAndAdvisorsForBean 扫一遍全局 Advisor 注册表,匹配出一组,然后 createProxy 统一组装成一个代理------一个 bean 最终被包成什么样的代理,是它拍板的:

java 复制代码
// AbstractAutoProxyCreator.java:357-373(节选)
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
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;
}

再看它怎么"记"和"管一辈子":它维护三份"拥有者才需要的账"------

java 复制代码
// AbstractAutoProxyCreator.java
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);  // 这家伙我是不是提前包过一次了
private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);         // 这家伙需不需要被代理
private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);          // 这家伙代理后的类型是什么
  • advisedBeans:判断过一次就记账,免得反复扫 Advisor;
  • earlyProxyReferences:这个 bean 是不是已经在循环依赖里被提前包过一次了,核心防重账本;
  • proxyTypes:代理后的类型,原型 scope 复用,免得每次都重新生成代理类。

一个不拥有代理的组件根本不需要记这些账。装饰者不拥有,所以一本都没有------这正是它扛不住循环依赖的根。

  • earlyProxyReferences:这个 bean 是不是已经在循环依赖里被提前包过一次了,核心防重账本;
  • proxyTypes:这个 bean 代理后的类型,原型 scope 复用,免得每次都重新生成代理类。

这三份账的语义是一致的:只有"代理的拥有者"才需要记。装饰者不拥有代理,自然不需要这种账。

下面是循环依赖里这套账怎么运作的完整推演。设 A 同时被事务(走路线一)和 @Async(走路线二)增强,且 A↔B 循环依赖:
AbstractAdvisingBeanPostProcessor AbstractAutoProxyCreator initializeBean getSingleton A true populateBean 三级缓存 singletonFactories doCreateBean A AbstractAdvisingBeanPostProcessor AbstractAutoProxyCreator initializeBean getSingleton A true populateBean 三级缓存 singletonFactories doCreateBean A #mermaid-svg-AAwXgJgXZVadrUNo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AAwXgJgXZVadrUNo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AAwXgJgXZVadrUNo .error-icon{fill:#552222;}#mermaid-svg-AAwXgJgXZVadrUNo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AAwXgJgXZVadrUNo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AAwXgJgXZVadrUNo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AAwXgJgXZVadrUNo .marker.cross{stroke:#333333;}#mermaid-svg-AAwXgJgXZVadrUNo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AAwXgJgXZVadrUNo p{margin:0;}#mermaid-svg-AAwXgJgXZVadrUNo .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAwXgJgXZVadrUNo text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AAwXgJgXZVadrUNo .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AAwXgJgXZVadrUNo .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AAwXgJgXZVadrUNo .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AAwXgJgXZVadrUNo .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AAwXgJgXZVadrUNo #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AAwXgJgXZVadrUNo .sequenceNumber{fill:white;}#mermaid-svg-AAwXgJgXZVadrUNo #sequencenumber{fill:#333;}#mermaid-svg-AAwXgJgXZVadrUNo #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AAwXgJgXZVadrUNo .messageText{fill:#333;stroke:none;}#mermaid-svg-AAwXgJgXZVadrUNo .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAwXgJgXZVadrUNo .labelText,#mermaid-svg-AAwXgJgXZVadrUNo .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AAwXgJgXZVadrUNo .loopText,#mermaid-svg-AAwXgJgXZVadrUNo .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AAwXgJgXZVadrUNo .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AAwXgJgXZVadrUNo .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AAwXgJgXZVadrUNo .noteText,#mermaid-svg-AAwXgJgXZVadrUNo .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AAwXgJgXZVadrUNo .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAwXgJgXZVadrUNo .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAwXgJgXZVadrUNo .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AAwXgJgXZVadrUNo .actorPopupMenu{position:absolute;}#mermaid-svg-AAwXgJgXZVadrUNo .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AAwXgJgXZVadrUNo .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AAwXgJgXZVadrUNo .actor-man circle,#mermaid-svg-AAwXgJgXZVadrUNo line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AAwXgJgXZVadrUNo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} addSingletonFactory A lambda对象属性注入 → 触发 getBean BB 反过来要 A → 命中三级缓存getEarlyBeanReference rawAearlyProxyReferences.put A rawA ← 记账wrapIfNecessary → 包成 P_aop 带 tx提前暴露 P_aop 给 BA 自己继续初始化postProcessAfterInitialization rawAearlyProxyReferences.remove A == rawA ✓ 给过了跳过 wrapIfNecessary 返回 rawApostProcessAfterInitialization rawArawA instanceof Advised? false 裸 beanisEligible → 自己建代理 P_async2 只 async 没 txexposedObject = P_async2earlySingletonReference = P_aop 来自二级缓存exposedObject P_async2 != bean rawA ⚠ 抛异常

时序图最后那行 exposedObject != bean 是 doCreateBean 第 632 行的致命判断:

java 复制代码
// AbstractAutowireCapableBeanFactory.java:621-636
if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);  // 二级缓存(只有循环依赖发生过才有值)
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {            // ← 致命判断
            exposedObject = earlySingletonReference;
        } else if (... && hasDependentBean(beanName)) {
            throw new BeanCurrentlyInCreationException(...);   // ← 直接炸
        }
    }
}

这段话翻译过来:如果发生过循环依赖(二级缓存有值),那么 initializeBean 之后的 exposedObject 必须==原始 bean ;一旦 initializeBean 把 bean 换成了别的对象(代理),就认定"提前暴露给别人的引用"和"最终放进容器的引用"不一致,直接抛异常。

AbstractAutoProxyCreator 之所以能安然通过这个判断,是因为它的 postProcessAfterInitialization 里有 earlyProxyReferences.remove(cacheKey) != bean 这道闸------发生过提前暴露就跳过 wrapIfNecessary,保证 exposedObject 仍 == bean:

java 复制代码
// AbstractAutoProxyCreator.java:299-308
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {  // ← 这道闸:没提前暴露过才包
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

注意这里有个容易看走眼的地方:getEarlyBeanReference 和 postProcessAfterInitialization 两个方法都调 wrapIfNecessary,看起来是"两次都会包"的诱因,但真正防重的是 earlyProxyReferences 账本让后者跳过 wrapIfNecessary------防重靠的是显式账本,不是靠被多次调用本身。这是"所有者"用自家账本换来的安然过关。

第二步:路线二的"装饰者"哲学------借船出海

路线二的核心代码只有一段,在 AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization:

java 复制代码
// AbstractAdvisingBeanPostProcessor.java:65-104
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
    return bean;
}
if (bean instanceof Advised) {                              // 你已经是代理了
    Advised advised = (Advised) bean;
    if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
        if (this.beforeExistingAdvisors) {
            advised.addAdvisor(0, this.advisor);              // 插链头
        } else {
            advised.addAdvisor(this.advisor);                 // 挂链尾
        }
        return bean;                                          // 返回的还是原来那个代理
    }
}
if (isEligible(bean, beanName)) {                             // 你不是代理,我才自己搭最小代理
    ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
    proxyFactory.addAdvisor(this.advisor);
    return proxyFactory.getProxy(getProxyClassLoader());
}
return bean;

AsyncAnnotationBeanPostProcessor 构造时 setBeforeExistingAdvisors(true),把自己插在链头,保证方法一进来就切到异步线程。

这段代码浓缩了路线二的全部哲学:不拥有代理,只往已有代理上挂一条 advisor 。它没有 earlyProxyReferences,没有 advisedBeans,没有 proxyTypes------一个"借船出海"的师傅不需要这些账本。这也是它能和任何 AutoProxyCreator 幂等共存的原因:挂在链上,不重建代理,加一次和(配合 isEligible 缓存)判断过一次的结果一致。

代价同样来自这个哲学:它没有资格在生命周期的早期出现 。循环依赖时三级缓存提前暴露出去的引用,路线二根本没参与。如果这个 bean 的"异步挂钩"还没挂上,提前暴露给别人的就是个没挂钩的半成品------这就是 @Async 在循环依赖里静默失效的根。

⚠️ 踩坑:以为给 AsyncBPP 加 getEarlyBeanReference 就能修好

这是最自然的反问:既然路线二扛不住循环依赖,直接给它实现 getEarlyBeanReference 不就行了?

技术上加一行接口实现很容易,但加了之后循环依赖反而直接抛异常 ,比静默失效更糟。原因回到时序图:即使路线二在 getEarlyBeanReference 提前把 async advisor 挂上了,initializeBean 阶段框架传给 BPP 的永远是 doCreateBean 第 604 行的 exposedObject = bean(裸 bean),不是 提前暴露出去的那个代理。所以路线二在 postProcessAfterInitialization 面对的是裸 bean,不知道自己曾经在 getEarlyBeanReference 里挂过 async advisor,于是又包一次,产生 P_async2 != rawA,触发 BeanCurrentlyInCreationException。
#mermaid-svg-19gKKlTvQFXKaQfj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-19gKKlTvQFXKaQfj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-19gKKlTvQFXKaQfj .error-icon{fill:#552222;}#mermaid-svg-19gKKlTvQFXKaQfj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-19gKKlTvQFXKaQfj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-19gKKlTvQFXKaQfj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-19gKKlTvQFXKaQfj .marker.cross{stroke:#333333;}#mermaid-svg-19gKKlTvQFXKaQfj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-19gKKlTvQFXKaQfj p{margin:0;}#mermaid-svg-19gKKlTvQFXKaQfj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-19gKKlTvQFXKaQfj .cluster-label text{fill:#333;}#mermaid-svg-19gKKlTvQFXKaQfj .cluster-label span{color:#333;}#mermaid-svg-19gKKlTvQFXKaQfj .cluster-label span p{background-color:transparent;}#mermaid-svg-19gKKlTvQFXKaQfj .label text,#mermaid-svg-19gKKlTvQFXKaQfj span{fill:#333;color:#333;}#mermaid-svg-19gKKlTvQFXKaQfj .node rect,#mermaid-svg-19gKKlTvQFXKaQfj .node circle,#mermaid-svg-19gKKlTvQFXKaQfj .node ellipse,#mermaid-svg-19gKKlTvQFXKaQfj .node polygon,#mermaid-svg-19gKKlTvQFXKaQfj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-19gKKlTvQFXKaQfj .rough-node .label text,#mermaid-svg-19gKKlTvQFXKaQfj .node .label text,#mermaid-svg-19gKKlTvQFXKaQfj .image-shape .label,#mermaid-svg-19gKKlTvQFXKaQfj .icon-shape .label{text-anchor:middle;}#mermaid-svg-19gKKlTvQFXKaQfj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-19gKKlTvQFXKaQfj .rough-node .label,#mermaid-svg-19gKKlTvQFXKaQfj .node .label,#mermaid-svg-19gKKlTvQFXKaQfj .image-shape .label,#mermaid-svg-19gKKlTvQFXKaQfj .icon-shape .label{text-align:center;}#mermaid-svg-19gKKlTvQFXKaQfj .node.clickable{cursor:pointer;}#mermaid-svg-19gKKlTvQFXKaQfj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-19gKKlTvQFXKaQfj .arrowheadPath{fill:#333333;}#mermaid-svg-19gKKlTvQFXKaQfj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-19gKKlTvQFXKaQfj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-19gKKlTvQFXKaQfj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-19gKKlTvQFXKaQfj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-19gKKlTvQFXKaQfj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-19gKKlTvQFXKaQfj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-19gKKlTvQFXKaQfj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-19gKKlTvQFXKaQfj .cluster text{fill:#333;}#mermaid-svg-19gKKlTvQFXKaQfj .cluster span{color:#333;}#mermaid-svg-19gKKlTvQFXKaQfj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-19gKKlTvQFXKaQfj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-19gKKlTvQFXKaQfj rect.text{fill:none;stroke-width:0;}#mermaid-svg-19gKKlTvQFXKaQfj .icon-shape,#mermaid-svg-19gKKlTvQFXKaQfj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-19gKKlTvQFXKaQfj .icon-shape p,#mermaid-svg-19gKKlTvQFXKaQfj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-19gKKlTvQFXKaQfj .icon-shape .label rect,#mermaid-svg-19gKKlTvQFXKaQfj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-19gKKlTvQFXKaQfj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-19gKKlTvQFXKaQfj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-19gKKlTvQFXKaQfj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 设想:给 AsyncBPP 加 getEarlyBeanReference
提前暴露阶段:挂上 async ✓
initializeBean 阶段:框架传给 BPP 的是 rawA不是提前暴露的 P_aop
AsyncBPP 以为没挂过,又包一次 P_async2
exposedObject P_async2 != bean rawA
⚠ BeanCurrentlyInCreationException比静默失效更糟

要真修好,得同步改三处:① 加 getEarlyBeanReference;② 给 postProcessAfterInitialization 加"提前暴露过就跳过"的闸;③ 协调路线一与路线二的执行顺序,保证 earlyProxyReferences 存的引用语义一致。第 3 点最致命:路线一的 earlyProxyReferences 隐式假设自己是第一个改写 bean 的 SmartIBP,前面有别人介入这个假设就破了。两套独立 BPP 的去重逻辑耦合在一起,Spring 框架层面很难给出不依赖顺序的稳定保证。

教训 :"少了一个方法"往往不是疏漏,是设计取舍的代价。给一条为"轻量叠加"设计的路线强行补"重度生命周期介入"的能力,会让原有契约(框架给 BPP 的入参恒为裸 bean)和其他 BPP 的假设一并崩盘。

第三步:为什么是两套,不是一套

把对方换成自己,两边都失去最值钱的特性:
#mermaid-svg-mt4j9gGwHYW7TRJf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mt4j9gGwHYW7TRJf .error-icon{fill:#552222;}#mermaid-svg-mt4j9gGwHYW7TRJf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mt4j9gGwHYW7TRJf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mt4j9gGwHYW7TRJf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mt4j9gGwHYW7TRJf .marker.cross{stroke:#333333;}#mermaid-svg-mt4j9gGwHYW7TRJf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mt4j9gGwHYW7TRJf p{margin:0;}#mermaid-svg-mt4j9gGwHYW7TRJf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster-label text{fill:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster-label span{color:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster-label span p{background-color:transparent;}#mermaid-svg-mt4j9gGwHYW7TRJf .label text,#mermaid-svg-mt4j9gGwHYW7TRJf span{fill:#333;color:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf .node rect,#mermaid-svg-mt4j9gGwHYW7TRJf .node circle,#mermaid-svg-mt4j9gGwHYW7TRJf .node ellipse,#mermaid-svg-mt4j9gGwHYW7TRJf .node polygon,#mermaid-svg-mt4j9gGwHYW7TRJf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mt4j9gGwHYW7TRJf .rough-node .label text,#mermaid-svg-mt4j9gGwHYW7TRJf .node .label text,#mermaid-svg-mt4j9gGwHYW7TRJf .image-shape .label,#mermaid-svg-mt4j9gGwHYW7TRJf .icon-shape .label{text-anchor:middle;}#mermaid-svg-mt4j9gGwHYW7TRJf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mt4j9gGwHYW7TRJf .rough-node .label,#mermaid-svg-mt4j9gGwHYW7TRJf .node .label,#mermaid-svg-mt4j9gGwHYW7TRJf .image-shape .label,#mermaid-svg-mt4j9gGwHYW7TRJf .icon-shape .label{text-align:center;}#mermaid-svg-mt4j9gGwHYW7TRJf .node.clickable{cursor:pointer;}#mermaid-svg-mt4j9gGwHYW7TRJf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mt4j9gGwHYW7TRJf .arrowheadPath{fill:#333333;}#mermaid-svg-mt4j9gGwHYW7TRJf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mt4j9gGwHYW7TRJf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mt4j9gGwHYW7TRJf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mt4j9gGwHYW7TRJf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mt4j9gGwHYW7TRJf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mt4j9gGwHYW7TRJf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster text{fill:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf .cluster span{color:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mt4j9gGwHYW7TRJf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mt4j9gGwHYW7TRJf rect.text{fill:none;stroke-width:0;}#mermaid-svg-mt4j9gGwHYW7TRJf .icon-shape,#mermaid-svg-mt4j9gGwHYW7TRJf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mt4j9gGwHYW7TRJf .icon-shape p,#mermaid-svg-mt4j9gGwHYW7TRJf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mt4j9gGwHYW7TRJf .icon-shape .label rect,#mermaid-svg-mt4j9gGwHYW7TRJf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mt4j9gGwHYW7TRJf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mt4j9gGwHYW7TRJf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mt4j9gGwHYW7TRJf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 路线一改走路线二会丢什么
每个 Advisor 各建各的代理
AOP 退化成 N 层套娃
✗ 失去统一扫描组装能力失去循环依赖提前暴露兜底
路线二改走路线一会丢什么
Async 自建代理 P_async事务自建代理 P_tx
谁外谁内全凭 BPP order
✗ 双层嵌套代理拦截顺序难推理

所以这是分工,不是优劣。一个系统里必须有人当代理命运的统揽者 (事务、AspectJ 这种全局切面),也必须允许有人当轻量增强的无侵入叠加者 (@Async、@Valid、异常翻译)。Spring 把这两件事拆成两套基类,是"核心编排 + 边缘装饰"的架构分治在 AOP 子系统里的落地。

R --- Result:两套哲学各司其职,选型有据

验证项(全部基于当前仓库源码核实):

验证项 结果
路线一防重靠 earlyProxyReferences 而非"两个方法都调 wrapIfNecessary" ✅ 源码 AbstractAutoProxyCreator.java:302 证实
路线二靠 bean instanceof Advised 合并而非重建代理 ✅ 源码 AbstractAdvisingBeanPostProcessor.java:76 证实
@Async 在循环依赖里静默失效的根因是路线二无 getEarlyBeanReference ✅ 继承链核实:AsyncBPP 不实现 SmartInstantiationAwareBeanPostProcessor
给 AsyncBPP 加 getEarlyBeanReference 反而抛 BeanCurrentlyInCreationException ✅ doCreateBean:632 exposedObject != bean 触发
Spring 5.x 之后收紧循环依赖(6.x 默认禁用)印证"不为 @Async 补提前暴露"是有意取舍 ✅ 方向是"别让循环依赖发生",不是"让更多 BPP 支持循环依赖"

复盘沉淀的三条原则

  1. "拥有者才需要账本,装饰者不需要":看到一组缓存(earlyProxyReferences、advisedBeans、proxyTypes)出现在某个框架基类里,先问"它是拥有这个资源,还是只在使用这个资源"。这三份账是"拥有代理对象"在代码层面的必然投射,不是性能优化,不是历史包袱;
  2. "少一个生命周期钩子"往往是取舍,不是 bug:给为轻量叠加设计的组件强行补重度生命周期介入,会破坏框架与其他组件的隐式契约。判断一个"缺失能力"该不该补,先看它属于哪种职责定位------所有者必须深介入,装饰者必须浅介入;
  3. 两条路线对应"我要做主代理" vs "我要往已有代理补一条增强"两种意图:选型时按意图选,不按"哪个更主流"选。通用 AOP 框架/切面库(批量 pointcut 匹配 + 统一组装)走路线一,单一注解增强(幂等叠加、期望与事务代理共存)走路线二。

选型判据:开源实践参照

按当前 Spring 仓库可直接核实的实现,两套路线的选型遵循一个清晰的意图分界:

维度 选路线一 AbstractAutoProxyCreator 选路线二 AbstractAdvisingBeanPostProcessor
意图 做主代理,扫描一组 Advisor 统一组装 补一条增强,与已有代理幂等共存
决策模型 全局扫描 + 批量匹配 单条 advisor + 局部自证
落地写法 写 @Aspect 切面 写触发注解 + AbstractAdvisingBeanPostProcessor 子类
Spring 框架内 @Transactional、@AspectJ 切面 @Async、方法级 @Valid、@Repository 异常翻译
第三方画像 权限、链路追踪、限流、审计------批量切面 少见,因这类需求已被 Spring 自家模块占住

第三方开源项目大多走路线一,不是因为路线一"更主流",而是因为它们做的事本质就是"扫描一批切面统一组装"。路线二这种"单一注解 + 幂等合并"的需求,被 Spring 自带的 @Async/@Valid/@Repository 占住了,留给第三方的空间本就不大。

落地实践:选了路线一之后,只写 @Aspect,别碰 AutoProxyCreator

表里"落地写法"那一列看似简单,但"路线一只写 @Aspect"背后藏着一个更硬的约束:用户侧不要写 AutoProxyCreator 子类 。源码根因在框架自带的五个 AutoProxyCreator 子类和他们共享的一个 bean 名里。继承关系如下:

复制代码
AbstractAutoProxyCreator
├─ AbstractAdvisorAutoProxyCreator (abstract)
│    ├─ InfrastructureAdvisorAutoProxyCreator          ← 事务
│    ├─ AspectJAwareAdvisorAutoProxyCreator            ← <aop:config> XML 声明式
│    │    └─ AnnotationAwareAspectJAutoProxyCreator    ← @AspectJ(@EnableAspectJAutoProxy) 或 <aop:aspectj-autoproxy>
│    └─ DefaultAdvisorAutoProxyCreator                 ← 编程式手动注册
└─ BeanNameAutoProxyCreator                           ← 按 beanName 批量代理,编程式手动注册
子类 谁注册它 order
AnnotationAwareAspectJAutoProxyCreator @EnableAspectJAutoProxy / <aop:aspectj-autoproxy> Ordered.HIGHEST_PRECEDENCE
AspectJAwareAdvisorAutoProxyCreator <aop:config> XML 声明式 Ordered.HIGHEST_PRECEDENCE
InfrastructureAdvisorAutoProxyCreator @EnableTransactionManagement / <tx:annotation-driven> Ordered.HIGHEST_PRECEDENCE
DefaultAdvisorAutoProxyCreator 编程式手动注册 Ordered.LOWEST_PRECEDENCE(基类默认)
BeanNameAutoProxyCreator 编程式手动注册 Ordered.LOWEST_PRECEDENCE(基类默认)

基类 ProxyProcessorSupport 默认 LOWEST_PRECEDENCE,前三个被框架注册时统一被 AopConfigUtils 设成 HIGHEST_PRECEDENCE,后两个编程式的继承基类默认、需手动设。

这里有个关键差异容易看漏:前三个是框架自动注册的,后两个不会被任何 @Enable 或 XML 标签注册,只有你手动写 @Bean 才进容器 。证据是 AopConfigUtils(框架所有 AutoProxyCreator 注册的中枢)里只有注册前三个的方法,没有注册 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator 的方法。所以你写了 @EnableTransactionManagement + @EnableAspectJAutoProxy 时,容器里只有 AnnotationAwareAspectJAutoProxyCreator 一个,这俩根本不在。

那万一你手动 @Bean 注册了它俩呢?它俩和前三个会并存 ------因为手动注册的 bean 名通常不是 internalAutoProxyCreator,下面要讲的升级链不生效。一旦并存,两个 creator 各自建代理,无论 BPP order 谁先谁后,裸 bean 都会被包成两层:P2 → P1 → target,双层嵌套。这不是危言耸听:框架那个跑在前(它被设了 HIGHEST_PRECEDENCE),你手动那个跑在后(默认 LOWEST_PRECEDENCE),先包的带 tx+AspectJ,后包的再裹一层------代理链顺序和 this 自调用语义都会出错。"控制顺序"这条路根本走不通,因为无论谁先谁后都是嵌套。这正是框架用 internalAutoProxyCreator 同名 + 升级链保证"唯一拥有者"的原因------前三个靠这套机制不会并存,你手动注册的不进这套机制,一旦注册就一定并存,并存就一定嵌套。

但 order 不是关键,"同一个 bean 名下只能有一个"才是关键。 前三个都被注册到同一个常量名下:

java 复制代码
// AopConfigUtils.java:51
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
        "org.springframework.aop.config.internalAutoProxyCreator";

这个 internalAutoProxyCreator 是 Spring 容器里的一个内部 bean 名,日常开发接触不到,但它正是"唯一拥有者"哲学在运行时的落点。注册链路是这样的:
#mermaid-svg-BZmlqlVe3FFB6WOj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BZmlqlVe3FFB6WOj .error-icon{fill:#552222;}#mermaid-svg-BZmlqlVe3FFB6WOj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BZmlqlVe3FFB6WOj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BZmlqlVe3FFB6WOj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BZmlqlVe3FFB6WOj .marker.cross{stroke:#333333;}#mermaid-svg-BZmlqlVe3FFB6WOj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BZmlqlVe3FFB6WOj p{margin:0;}#mermaid-svg-BZmlqlVe3FFB6WOj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster-label text{fill:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster-label span{color:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster-label span p{background-color:transparent;}#mermaid-svg-BZmlqlVe3FFB6WOj .label text,#mermaid-svg-BZmlqlVe3FFB6WOj span{fill:#333;color:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj .node rect,#mermaid-svg-BZmlqlVe3FFB6WOj .node circle,#mermaid-svg-BZmlqlVe3FFB6WOj .node ellipse,#mermaid-svg-BZmlqlVe3FFB6WOj .node polygon,#mermaid-svg-BZmlqlVe3FFB6WOj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BZmlqlVe3FFB6WOj .rough-node .label text,#mermaid-svg-BZmlqlVe3FFB6WOj .node .label text,#mermaid-svg-BZmlqlVe3FFB6WOj .image-shape .label,#mermaid-svg-BZmlqlVe3FFB6WOj .icon-shape .label{text-anchor:middle;}#mermaid-svg-BZmlqlVe3FFB6WOj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BZmlqlVe3FFB6WOj .rough-node .label,#mermaid-svg-BZmlqlVe3FFB6WOj .node .label,#mermaid-svg-BZmlqlVe3FFB6WOj .image-shape .label,#mermaid-svg-BZmlqlVe3FFB6WOj .icon-shape .label{text-align:center;}#mermaid-svg-BZmlqlVe3FFB6WOj .node.clickable{cursor:pointer;}#mermaid-svg-BZmlqlVe3FFB6WOj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BZmlqlVe3FFB6WOj .arrowheadPath{fill:#333333;}#mermaid-svg-BZmlqlVe3FFB6WOj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BZmlqlVe3FFB6WOj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BZmlqlVe3FFB6WOj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BZmlqlVe3FFB6WOj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BZmlqlVe3FFB6WOj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BZmlqlVe3FFB6WOj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster text{fill:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj .cluster span{color:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BZmlqlVe3FFB6WOj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BZmlqlVe3FFB6WOj rect.text{fill:none;stroke-width:0;}#mermaid-svg-BZmlqlVe3FFB6WOj .icon-shape,#mermaid-svg-BZmlqlVe3FFB6WOj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BZmlqlVe3FFB6WOj .icon-shape p,#mermaid-svg-BZmlqlVe3FFB6WOj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BZmlqlVe3FFB6WOj .icon-shape .label rect,#mermaid-svg-BZmlqlVe3FFB6WOj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BZmlqlVe3FFB6WOj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BZmlqlVe3FFB6WOj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BZmlqlVe3FFB6WOj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EnableAspectJAutoProxy
AspectJAutoProxyRegistrar
AopConfigUtils 注册 AnnotationAwareAspectJAutoProxyCreator
EnableTransactionManagement
AutoProxyRegistrar
AopConfigUtils 注册 InfrastructureAdvisorAutoProxyCreator
internalAutoProxyCreator 同一个 bean 名
升级链 APC_PRIORITY_LIST 高的顶掉低的
最终容器里只剩一个 AnnotationAwareAspectJAutoProxyCreator

也就是说,@EnableAspectJAutoProxy 想注册 AnnotationAwareAspectJAutoProxyCreator,@EnableTransactionManagement 想注册 InfrastructureAdvisorAutoProxyCreator,但两个 @Enable 都通过 AopConfigUtils 把自己的中意类注册到 internalAutoProxyCreator 这同一个名字下。两个 @Enable 各自注册自己中意的 creator 类,但因为用同一个 bean 名,加上下面的升级链,最终只剩一个------容器里始终只有一个 AutoProxyCreator ,不会出现两个并存。框架用一个升级链(APC_PRIORITY_LIST)决定谁当这个唯一拥有者:

java 复制代码
// AopConfigUtils.java:57-63
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);        // 优先级 0(最低)
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);         // 优先级 1
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);     // 优先级 2(最高)

后注册的优先级更高时,顶替先注册的(把 BeanDefinition 的 className 换掉):

java 复制代码
// AopConfigUtils.java:128-133(简化)
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
    apcDefinition.setBeanClassName(cls.getName());  // 高的顶掉低的
}

所以一个 Spring 应用里同时开了事务和 AspectJ,实际跑的 AutoProxyCreator 只有 AnnotationAwareAspectJAutoProxyCreator 一个------它优先级最高(2),顶掉了 InfrastructureAdvisorAutoProxyCreator(0)。事务的 Advisor 仍然被这个 creator 扫到并组装进代理,功能不丢,代理拥有者合并成一个了。这正是前文"拥有者必须唯一"这个哲学命题在源码层的实证:框架宁可让一个 creator 兼管事务和 AspectJ,也不允许两个 creator 并存。

为什么顶替是安全的:三个 creator 不是功能相同,是功能递增

读者到这里自然会问:AnnotationAware 顶掉 Infrastructure,事务功能凭什么不丢?答案不是"三个 creator 功能一样",而是三个 creator 功能递增,高的能覆盖低的。继承链是这样的:

复制代码
AbstractAdvisorAutoProxyCreator (abstract)
   ↑ 三者共同父类,提供"扫描容器里 Advisor bean"的基础能力
   │
   ├─ InfrastructureAdvisorAutoProxyCreator           ← 不覆写 findCandidateAdvisors
   │
   ├─ AspectJAwareAdvisorAutoProxyCreator             ← 覆写 sortAdvisors(AspectJ 偏序排序)
   │    │
   │    └─ AnnotationAwareAspectJAutoProxyCreator     ← 覆写 findCandidateAdvisors(加 @Aspect 扫描)

三个的差异就在它们各自覆写了什么:

creator 覆写了什么 增强的能力
InfrastructureAdvisorAutoProxyCreator 啥也没覆写 只用父类的"扫容器里 Advisor bean"
AspectJAwareAdvisorAutoProxyCreator sortAdvisors 加了 AspectJ 偏序排序(同切面内按声明顺序,跨切面按 Ordered)
AnnotationAwareAspectJAutoProxyCreator findCandidateAdvisors + 继承 AspectJAware 的 sortAdvisors 在父类基础上,额外扫 @Aspect 注解的 bean 转成 Advisor

关键看 AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors:

java 复制代码
// AnnotationAwareAspectJAutoProxyCreator.java:90-105
@Override
protected List<Advisor> findCandidateAdvisors() {
    List<Advisor> advisors = super.findCandidateAdvisors();          // ① 父类:扫容器里所有 Advisor bean
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());  // ② 额外:扫 @Aspect
    }
    return advisors;
}

super.findCandidateAdvisors() 一路向上调到 AbstractAdvisorAutoProxyCreator.findCandidateAdvisors → advisorRetrievalHelper.findAdvisorBeans(),这正是 InfrastructureAdvisorAutoProxyCreator 用的同一段扫描逻辑 。事务的 BeanFactoryTransactionAttributeSourceAdvisor 作为容器里的 Advisor bean,在这一步照样被找出来------所以 AnnotationAware 顶替 Infrastructure 后,事务 advisor 一点没丢。
#mermaid-svg-CCbSWnzU5D6czfmW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CCbSWnzU5D6czfmW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CCbSWnzU5D6czfmW .error-icon{fill:#552222;}#mermaid-svg-CCbSWnzU5D6czfmW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CCbSWnzU5D6czfmW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CCbSWnzU5D6czfmW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CCbSWnzU5D6czfmW .marker.cross{stroke:#333333;}#mermaid-svg-CCbSWnzU5D6czfmW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CCbSWnzU5D6czfmW p{margin:0;}#mermaid-svg-CCbSWnzU5D6czfmW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CCbSWnzU5D6czfmW .cluster-label text{fill:#333;}#mermaid-svg-CCbSWnzU5D6czfmW .cluster-label span{color:#333;}#mermaid-svg-CCbSWnzU5D6czfmW .cluster-label span p{background-color:transparent;}#mermaid-svg-CCbSWnzU5D6czfmW .label text,#mermaid-svg-CCbSWnzU5D6czfmW span{fill:#333;color:#333;}#mermaid-svg-CCbSWnzU5D6czfmW .node rect,#mermaid-svg-CCbSWnzU5D6czfmW .node circle,#mermaid-svg-CCbSWnzU5D6czfmW .node ellipse,#mermaid-svg-CCbSWnzU5D6czfmW .node polygon,#mermaid-svg-CCbSWnzU5D6czfmW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CCbSWnzU5D6czfmW .rough-node .label text,#mermaid-svg-CCbSWnzU5D6czfmW .node .label text,#mermaid-svg-CCbSWnzU5D6czfmW .image-shape .label,#mermaid-svg-CCbSWnzU5D6czfmW .icon-shape .label{text-anchor:middle;}#mermaid-svg-CCbSWnzU5D6czfmW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CCbSWnzU5D6czfmW .rough-node .label,#mermaid-svg-CCbSWnzU5D6czfmW .node .label,#mermaid-svg-CCbSWnzU5D6czfmW .image-shape .label,#mermaid-svg-CCbSWnzU5D6czfmW .icon-shape .label{text-align:center;}#mermaid-svg-CCbSWnzU5D6czfmW .node.clickable{cursor:pointer;}#mermaid-svg-CCbSWnzU5D6czfmW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CCbSWnzU5D6czfmW .arrowheadPath{fill:#333333;}#mermaid-svg-CCbSWnzU5D6czfmW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CCbSWnzU5D6czfmW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CCbSWnzU5D6czfmW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CCbSWnzU5D6czfmW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CCbSWnzU5D6czfmW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CCbSWnzU5D6czfmW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CCbSWnzU5D6czfmW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CCbSWnzU5D6czfmW .cluster text{fill:#333;}#mermaid-svg-CCbSWnzU5D6czfmW .cluster span{color:#333;}#mermaid-svg-CCbSWnzU5D6czfmW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CCbSWnzU5D6czfmW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CCbSWnzU5D6czfmW rect.text{fill:none;stroke-width:0;}#mermaid-svg-CCbSWnzU5D6czfmW .icon-shape,#mermaid-svg-CCbSWnzU5D6czfmW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CCbSWnzU5D6czfmW .icon-shape p,#mermaid-svg-CCbSWnzU5D6czfmW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CCbSWnzU5D6czfmW .icon-shape .label rect,#mermaid-svg-CCbSWnzU5D6czfmW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CCbSWnzU5D6czfmW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CCbSWnzU5D6czfmW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CCbSWnzU5D6czfmW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AbstractAdvisorAutoProxyCreator扫容器里所有 Advisor bean事务的 TransactionAdvisor 在这步被扫到
InfrastructureAdvisorAutoProxyCreator能力 = BASE
AspectJAwareAdvisorAutoProxyCreator能力 = BASE 加 AspectJ 偏序排序
AnnotationAwareAspectJAutoProxyCreator能力 = BASE 加 AspectJ 排序 加 扫描 Aspect

也就是说,AnnotationAwareAspectJAutoProxyCreator 是另外两个的功能超集:

  • 它继承 AspectJAware,所以有 sortAdvisors 的 AspectJ 偏序排序能力;
  • 它的 findCandidateAdvisors 第一行调 super,等价于走了一遍 AbstractAdvisorAutoProxyCreator 的扫描,也就是 InfrastructureAdvisorAutoProxyCreator 用的那段逻辑;
  • 它额外加了 buildAspectJAdvisors() 扫 @Aspect。

回看升级链的优先级顺序,会发现它不是随便排的,是按"功能丰富程度"递增排的:

java 复制代码
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);     // 优先级 0(功能最弱)
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);       // 优先级 1(中等)
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);   // 优先级 2(功能最全)

功能最全的优先级最高,顶替功能较弱的。因为高优先级是低优先级的功能超集 ,顶替后低优先级能做的事高优先级都能做,功能不丢。这是设计上的精心安排,不是"反正都是 creator 随便选一个"------升级链依赖的是"子类覆盖父类能力"的面向对象规律,不是"同名 bean 随便顶替"的字符串把戏。

需要补一句边界差异:Infrastructure 注册时 AutoProxyRegistrar 还会调 forceAutoProxyCreatorToUseClassProxying 设 proxyTargetClass------这个是设到 BeanDefinition 的属性上的,谁当 creator 这个属性都在,不丢。Infrastructure 那些"专门为事务配的特殊属性"要么是注册到 BeanDefinition 上的(谁顶上去都继承),要么是 AnnotationAware 也能做的扫描,所以顶替是安全的。

顺带说清:<aop:config> 和 @EnableAspectJAutoProxy 注册的不是同一个 creator

五个子类的注册入口表里有一处容易看走眼:AspectJAwareAdvisorAutoProxyCreator(优先级 1,由 <aop:config> 注册)和 AnnotationAwareAspectJAutoProxyCreator(优先级 2,由 @EnableAspectJAutoProxy 或 <aop:aspectj-autoproxy> 注册)名字很像,但能力差一截。

关键差别:AspectJAwareAdvisorAutoProxyCreator 不扫 @Aspect 注解的 bean 。它只处理 <aop:config> 里 XML 显式声明的 <aop:advisor>/<aop:pointcut>/<aop:aspect>;buildAspectJAdvisors()(扫 @Aspect 那段)是 AnnotationAwareAspectJAutoProxyCreator 才覆写加上的。

所以纯用 <aop:config> 配 AOP 时,你同时写 @Aspect 注解的切面不会被识别 ------要让注解切面生效,必须配 @EnableAspectJAutoProxy 或 <aop:aspectj-autoproxy> 把 creator 升级成 AnnotationAware。混用时 AnnotationAware(2)顶掉 AspectJAware(1),XML 声明的 advisor 和 @Aspect 注解切面都被这一个 creator 兼管------再次印证"功能更全的排更高"。

这套升级链只为前三个框架自带子类设计,你的自定义子类不进这套机制。 这就是"不要自己写 AutoProxyCreator 子类"的根因。比如你写个 class MyAutoProxyCreator extends AbstractAdvisorAutoProxyCreator,注册成 bean 后:

  • 你不知道 AUTO_PROXY_CREATOR_BEAN_NAME 这个内部常量,不会用它注册,所以升级链不生效;
  • 你的 MyAutoProxyCreator 和框架自带的 AnnotationAwareAspectJAutoProxyCreator 两个拥有者同时存在;
  • 一个 bean 被两个拥有者盯上,各自建各自代理,谁外谁内全凭 order------双层嵌套代理就出来了,这正是前文"踩坑"推演过的 exposedObject != bean 异常的触发条件。

所以用户侧的边界很清楚:

你要做的事 用不用碰 AutoProxyCreator
写 @Aspect 切面(任意 advice) 不用碰 。写 @Aspect 类即可,框架的 AnnotationAwareAspectJAutoProxyCreator 会通过 buildAspectJAdvisors 扫到
写触发注解 + PostProcessor(路线二) 不用碰 。继承 AbstractAdvisingBeanPostProcessor
想自定义"按 beanName 批量代理" 用现成的 BeanNameAutoProxyCreator 配置即可,不要自己 extends
想改代理创建行为(暴露代理、强制 CGLIB) 用 @EnableAspectJAutoProxy(exposeProxy=true, proxyTargetClass=true),不要自己写 creator

一句话:用户侧永远只写 @Aspect(路线一)或 AbstractAdvisingBeanPostProcessor 子类(路线二),AutoProxyCreator 那一层是框架的私产,五个子类全是框架自己的角色,不要进去加第六个。

编程军规:把上面的设计哲学落到一行行代码

设计哲学讲完了,但落到日常研发,真正能拦住事故的是几条可执行的硬规矩。下面这些是在"Spring 版本动不了、循环依赖禁不掉、团队水平参差"的现实约束下沉淀的,按优先级从高到低排:

1. 写 AOP 增强,默认写 @Aspect 切面,不要写 AutoProxyCreator 子类。

这是最高优先级的一条。@Aspect 切面会被框架自带的 AnnotationAwareAspectJAutoProxyCreator 扫到(buildAspectJAdvisors),你不用碰 creator 层。AutoProxyCreator 是框架私产,你手动注册一个就和框架那个并存,前面说过------无论 BPP order 谁先谁后,都是双层嵌套。

2. 按规则圈目标写 execution(...),按注解选目标写 @annotation(...),两种都用 @Aspect 形式。

注解匹配不等于必须走路线二。@annotation(yourAnno) 是 pointcut 表达式的一种,走的还是路线一(AnnotationAwareAspectJAutoProxyCreator 扫描),享受路线一的循环依赖兜底。只有"单一拦截器 + 失效可降级 + 接受循环依赖里失效"时,才考虑路线二(AbstractAdvisingBeanPostProcessor 子类)。

3. 增强失效会"出错"的,必须走路线一;失效只是"降级"的,才考虑路线二。

这条比"按规则还是按注解"更硬。权限没校验、审计漏记、限流没生效------这些失效=数据安全/合规事故,必须走路线一兜底;@Async 失效=同步执行、埋点失效=没指标------这些失效=功能降级,路线二可接受。@Transactional 失效=数据不一致,出错,所以它走路线一;@Async 失效=降级,所以它走路线二。拿这个对照自己的增强判断够不够格走路线二。

4. 项目层面把循环依赖禁掉,别让业务研发去判断。

业务研发预判不了目标 bean 将来会不会卷进循环依赖,也控制不了团队怎么改代码。能做的项目级动作(按代价从低到高):显式配 spring.main.allow-circular-references=false → 升级到 Spring 6 / Boot 2.6+ 默认就禁 → 上 ArchUnit 静态检查兜底。把"判断循环依赖"这个不该压在业务研发身上的担子,从项目层面卸掉,路线二的"循环依赖里静默失效"就根本没触发场景。

5. 两个以上 @Aspect 切面必须用 @Order 显式排序。

路线一允许多个切面共存,但拦截顺序默认未定义。如果有"审计必须在事务里执行""限流必须在日志外层"这类要求,不显式 @Order 会导致顺序漂移,生产环境行为不可预期。一个切面不用管,两条以上就要明确排好。

遗留事项

既然 AutoProxyCreator 是框架私产不能碰,那么遇到循环依赖时,正解就只能在业务侧解决------重构拆掉循环依赖,而不是去补 AsyncAnnotationBeanPostProcessor 的提前暴露能力。Spring 6 / Boot 2.6+ 默认 spring.main.allow-circular-references=false,正是把这种"重构优于补丁"的方向固化成了默认行为。


本文所有源码引用均来自当前仓库(springframework-comment,Spring 5.x 分支),无敏感信息需脱敏。涉及第三方开源框架的选型判断只覆盖能在 Spring 仓库内直接核实的模块,未在源码层面核实的外部项目实现不作断言。

相关推荐
专业程序开发源1 小时前
springboot外卖系统94294-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
谢亮_vipxieliang11 小时前
Spring Cloud 服务治理入门:注册发现、配置中心、网关限流、熔断降级与分布式一致性方案
分布式·spring·spring cloud
海绵宝宝转agent13 小时前
MySql高频面试八股开源笔记总结
mysql·面试·开源
专业程序开发源13 小时前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
vx_Biye_Design16 小时前
springboot高校选课系统82776-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·课程设计·express
niyongsheng16 小时前
后端零改动,给若依换一套现代化前端
vue.js·开源·node.js
EasyBr指纹浏览器16 小时前
抖音创作者数据导出:怎样留下可比较的日报?
数据分析·开源·抖音·数据导出
Sweet锦17 小时前
不调 Python,不装向量库:我用纯 Java 写了一套以图搜图引擎
java·人工智能·开源·图搜索
网络毒刘18 小时前
开源 AI 编程助手横向对比(Cursor / Continue / Aider):能力边界与 AtomGit 落地建议
人工智能·开源