Skip to content

Spring 循环依赖、三级缓存与早期代理全过程

循环依赖表示对象图中存在环,例如 A 依赖 B、B 又依赖 A。Spring 不是“通过三级缓存解决所有循环依赖”,而是在特定条件下,允许另一个 singleton 暂时取得当前 Bean 的早期引用,让属性填充继续进行。

真正困难的地方不是提前暴露一个对象,而是:如果 A 最终需要事务、缓存或其他 AOP 代理,B 不能永远持有原始 A,而其他 Bean 持有代理 A。三级缓存中的 ObjectFactory 为按需生成一致早期代理提供了机会。

学习目标

  • 区分构造器、字段、Setter、prototype、dependsOn 和代理循环;
  • 写出 Spring 5.3 singleton 三缓存的数据结构与职责;
  • 解释 addSingletonFactorygetSingleton(name, true)addSingleton
  • 解释三级工厂为什么不是普通 Bean 对象;
  • 说清 getEarlyBeanReference 与 AutoProxyCreator;
  • 解释 earlyProxyReferences 如何避免重复代理;
  • 理解 raw injection despite wrapping 的问题;
  • 理解创建失败后三级缓存为什么必须清理;
  • 说明 Spring Framework 与 Spring Boot 2.6+ 默认开关差异;
  • 使用 @Lazy 代理打断构造器创建时序,并理解其设计代价;
  • 运行 JDK 8/Spring 5 属性循环成功、构造循环失败、禁用循环失败和 Lazy 成功四组 Demo;
  • 用职责拆分、协调器、事件和端口接口真正消除业务循环;
  • 根据 BeanCurrentlyInCreationException 完成生产排查。

一、先识别循环类型

1.1 构造器循环

java
class AService {
    AService(BService b) { }
}

class BService {
    BService(AService a) { }
}

创建 A 前必须先取得 B,创建 B 前又必须先取得 A。双方都没有实例,无法注册 early reference 工厂。

1.2 字段或 Setter 循环

java
class AService {
    @Autowired BService b;
}

class BService {
    @Autowired AService a;
}

可以先调用 A 无参构造器得到对象壳子,再创建 B;B 需要 A 时有可能取得 A 的早期引用。这只是“可能”,还取决于 scope、开关和代理一致性。

1.3 prototype 循环

prototype 每次 getBean 创建新实例,不进入 singleton 三缓存,通常会通过 prototype 当前创建状态检测到循环并失败。

1.4 dependsOn 循环

text
A dependsOn B
B dependsOn A

这属于显式创建顺序环,Spring 会检查依赖关系并失败。它与字段注入的 early reference 不是同一个问题。

1.5 逻辑调用循环

A 调用 B,B 在运行时又回调 A,即使容器能够创建成功,也可能出现无限递归、重复事务、重复消息或死锁。三级缓存只处理创建时对象引用,不解决运行时业务调用环。

二、Spring 能解决的条件

经典 Spring 5 属性循环通常要求:

  • 参与者是 singleton;
  • allowCircularReferences=true
  • 至少有一方可先完成实例化;
  • 依赖在实例化后的属性填充阶段才需要;
  • early reference 与最终暴露对象可以保持一致;
  • 没有某个后置处理器只在初始化后包装对象却不支持早期引用;
  • Bean 创建没有在其他生命周期回调中再次形成更复杂的环。
mermaid
flowchart TD
    A["发现Bean创建环"] --> B{"都是singleton?"}
    B -- "否" --> X["通常失败"]
    B -- "是" --> C{"allowCircularReferences?"}
    C -- "否" --> X
    C -- "是" --> D{"能先实例化一方?"}
    D -- "否:构造器互相需要" --> X
    D -- "是" --> E["注册early reference工厂"]
    E --> F{"最终代理能与早期引用一致?"}
    F -- "否" --> X
    F -- "是" --> G["可能完成属性循环注入"]

“能解决”只表示容器可以完成创建,不代表对象职责合理。

三、三缓存分别是什么

Spring 5.3 的 DefaultSingletonBeanRegistry 核心结构可抽象为:

java
Map<String, Object> singletonObjects;
Map<String, Object> earlySingletonObjects;
Map<String, ObjectFactory<?>> singletonFactories;
通俗级别字段保存内容什么时候存在
一级singletonObjects完成创建的最终singleton初始化和包装完成后
二级earlySingletonObjects工厂已生成的早期引用真正发生早期获取后
三级singletonFactories可以生成早期引用的ObjectFactory实例化后、属性填充前

3.1 三级缓存不是第三份 Bean

三级保存工厂:

java
ObjectFactory<?> factory

工厂被调用后才产生早期引用。这个引用可能是原始对象,也可能是经过 getEarlyBeanReference 的代理。

3.2 二级缓存为什么存在

同一个 early factory 只应生成一次早期引用。第一次调用后将结果放入二级并移除三级工厂,后续循环参与者复用同一引用,避免多次生成不同代理。

3.3 一级缓存的对象才是完整对象吗

通常一级保存最终 exposedObject,但“完整”仍是容器生命周期意义:外部资源可能后来失效,Bean 也可能内部状态不健康。一级只代表 Spring 创建流程成功完成。

四、doCreateBean 为什么此时加入三级工厂

常见条件:

java
boolean earlySingletonExposure = mbd.isSingleton()
        && this.allowCircularReferences
        && isSingletonCurrentlyInCreation(beanName);

满足后:

java
addSingletonFactory(beanName,
        () -> getEarlyBeanReference(beanName, mbd, bean));

时机是:

text
createBeanInstance完成

MergedBeanDefinitionPostProcessor完成

注册singletonFactory

populateBean开始

必须先实例化,因为工厂需要捕获原始 bean;必须早于属性填充,因为循环可能在解析字段或 Setter 时发生。

五、addSingletonFactory() 做什么

Spring 5.3 主线可概括为:

text
一级缓存还没有同名完整对象

singletonFactories.put(beanName, factory)

earlySingletonObjects.remove(beanName)

registeredSingletons记录名称

它不会把 bean 放入一级,也不会立即调用工厂。此时 A 仍在创建中。

六、getSingleton(beanName, true) 读取顺序

当 B 解析依赖 A 时,BeanFactory 查询 singleton:

text
singletonObjects.get(A)
    ↓ 未命中
A是否currentlyInCreation
    ↓ 是
earlySingletonObjects.get(A)
    ↓ 未命中
allowEarlyReference是否为true
    ↓ 是
singletonFactories.get(A)
    ↓ 命中
factory.getObject()

earlySingletonObjects.put(A, earlyReference)

singletonFactories.remove(A)
mermaid
flowchart TD
    A["getSingleton A"] --> B["查询singletonObjects"]
    B --> C{"一级命中?"}
    C -- "是" --> D["返回最终singleton"]
    C -- "否" --> E{"A正在创建?"}
    E -- "否" --> F["返回null并走正常创建"]
    E -- "是" --> G["查询earlySingletonObjects"]
    G --> H{"二级命中?"}
    H -- "是" --> I["返回同一早期引用"]
    H -- "否" --> J{"允许early reference?"}
    J -- "否" --> F
    J -- "是" --> K["取得singletonFactory"]
    K --> L["调用getEarlyBeanReference"]
    L --> M["结果放二级并删除三级"]
    M --> I

allowEarlyReference=false 的查询不会主动调用三级工厂,常用于创建后检查已经有人取过早期引用没有。

七、A 与 B 的完整时间线

时刻动作A的缓存状态
1getBean(A)
2A标记currentlyInCreation
3实例化A原始A仅在创建栈
4注册A的ObjectFactory三级
5populate A,解析B三级
6实例化B并注册B工厂A三级、B三级
7populate B,解析AA三级
8调用A工厂生成early referenceA转入二级
9B注入early A并完成初始化A二级、B最终将进一级
10A注入完整BA二级、B一级
11A初始化和包装A二级
12对比early A与最终exposedObjectA二级
13addSingleton(A)A一级,二/三级删除

八、为什么只有二级缓存不够

如果实例化 A 后直接把原始 A 放到二级:

  1. B 注入原始 A;
  2. A 初始化后匹配事务 Advisor;
  3. 容器最终返回代理 A;
  4. B 永远持有原始 A;
  5. 其他 Bean 持有代理 A;
  6. B 调用 A 时事务、缓存或审计不生效。

三级工厂把“生成哪种早期引用”的决定推迟到真正需要时,并调用 AutoProxyCreator 的早期引用扩展点。

理论上可以设计其他数据结构实现同样语义,但 Spring 的三级结构清楚分离了:完整对象、已生成早期引用、尚未执行的早期引用工厂。面试重点应讲延迟工厂和代理一致性,不是争论 Map 数量。

九、getEarlyBeanReference() 与代理

AbstractAutowireCapableBeanFactory 会让 SmartInstantiationAwareBeanPostProcessor 参与早期引用。AutoProxyCreator 可:

  1. 记录当前 Bean 的早期代理标识;
  2. 查找适用 Advisor;
  3. 必要时创建 JDK/CGLIB 代理;
  4. 返回代理作为 early reference;
  5. 若无需代理,返回原始 bean。
mermaid
flowchart TD
    A["三级工厂被调用"] --> B["getEarlyBeanReference"]
    B --> C["遍历SmartInstantiationAwareBPP"]
    C --> D["AutoProxyCreator检查Advisor"]
    D --> E{"需要代理?"}
    E -- "否" --> F["返回原始早期对象"]
    E -- "是" --> G["创建早期AOP代理"]
    G --> H["记录earlyProxyReferences"]
    F --> I["放入二级缓存"]
    H --> I

9.1 为什么记录 earlyProxyReferences

Bean 后续到 postProcessAfterInitialization 时,AutoProxyCreator 要知道该 Bean 已经产生过早期代理,避免再次创建一个不同代理。具体缓存键和实现随版本演进,但目标是维持引用一致性。

9.2 所有后置处理器都支持早期代理吗

不是。某个处理器如果只在初始化后包装 Bean,却不参与 getEarlyBeanReference,循环依赖者可能已经拿到原始引用,最终包装时就会产生不一致。复杂代理组合不能保证被循环引用机制兜底。

十、最终对象怎样与早期引用对账

initializeBean 后得到 exposedObject。Spring 再以不主动创建 early reference 的方式查询:

java
Object earlySingletonReference = getSingleton(beanName, false);

若存在早期引用:

  • exposedObject == raw bean:说明正常链未产生新包装,最终可直接采用 early reference;
  • exposedObject 已是与早期引用一致的代理:继续使用;
  • 其他 Bean 已注入 raw bean,但最终又包装成不同对象:需要检测并可能失败。

十一、raw injection despite wrapping

问题场景:

text
B在循环依赖中注入原始A

A初始化后被某个BPP包装为代理A

容器对外返回代理A

B仍持有原始A

allowRawInjectionDespiteWrapping=false 且存在真实 dependent Bean 时,Spring 可能抛 BeanCurrentlyInCreationException,提示 Bean 已以原始形式注入其他对象,但最终又被包装。

不要通过打开 raw injection 选项掩盖问题。正确方向是消除环,或确保相关代理处理器支持 early reference。

十二、addSingleton() 如何完成晋级

成功创建最终 singleton 后:

text
singletonObjects.put(beanName, singletonObject)

singletonFactories.remove(beanName)

earlySingletonObjects.remove(beanName)

registeredSingletons记录名称

从此正常 getBean 直接命中一级,不再访问 early reference。

十三、创建失败怎样清理

若 A 的 PostConstruct、BPP 或代理创建失败,必须清理:

  • singletonObjects 中可能的条目;
  • earlySingletonObjects;
  • singletonFactories;
  • currentlyInCreation;
  • dependentBeanMap 等依赖关系;
  • disposableBeans;
  • refresh 期间已经创建的 singleton。

否则后续 getBean 可能取得半成品、永久误报循环、销毁顺序错误或持有泄漏资源。业务初始化中自行创建的线程和连接仍需在异常路径释放。

十四、构造器循环为什么无法解决

mermaid
flowchart TD
    A["开始创建A"] --> B["解析A构造器参数B"]
    B --> C["开始创建B"]
    C --> D["解析B构造器参数A"]
    D --> E["发现A正在创建"]
    E --> F["A构造器尚未执行"]
    F --> G["没有A实例"]
    G --> H["无法注册A的ObjectFactory"]
    H --> I["抛BeanCurrentlyInCreationException"]

三级工厂只在实例化后注册。构造器参数在实例化前就需要,因此没有早期对象。

十五、prototype 循环为什么失败

prototype 不保存到 singleton 三缓存。每次获取创建新实例,Spring 使用 prototype 当前线程创建状态检测循环。即使手工提前缓存,也会破坏“每次获取新对象”的 scope 语义和销毁责任。

十六、@DependsOn 环为什么失败

dependsOn 表达显式创建顺序:A 创建前先创建 B。A dependsOn B、B dependsOn A 没有合法拓扑顺序,Spring 会在依赖关系检查时失败。它不通过 early reference 解决,因为这不是字段赋值问题。

十七、代理、事务、缓存和异步边界

  • 事务和缓存常由 AutoProxyCreator Advisor 体系创建,具备早期代理参与能力时可能保持一致;
  • 自定义 BPP 若只在初始化后包装,可能产生 raw injection 问题;
  • 多层代理顺序和 early reference 组合更复杂;
  • final/private/static 方法本来就不能按普通代理方式增强;
  • @Async、事务、重试、安全等多个处理器组合必须通过集成测试验证;
  • 即使容器启动成功,自调用仍可能绕过代理。

不要把“属性循环能启动”推导为“循环中的所有 AOP 都一定生效”。

十八、@Lazy 怎样打断构造器创建时序

java
class AService {
    AService(@Lazy BService bProxy) { }
}

class BService {
    BService(AService a) { }
}

创建 A 时只需要生成 B 类型代理,不立即创建真实 B,所以 A 可以完成。以后创建或调用真实 B 时,A 已存在。

这不是三级缓存解决构造器循环,而是注入点 Lazy 代理改变了依赖解析时机。

风险:

  • 逻辑双向依赖仍存在;
  • B 初始化错误可能推迟;
  • 首次调用变慢;
  • 代理类型限制;
  • 初始化回调过早调用代理仍可能失败;
  • 团队更难理解对象关系。

详细见@Lazy懒加载与代理全过程

十九、Spring Framework 与 Boot 开关差异

Spring Framework 5.3 的 AbstractAutowireCapableBeanFactory 经典默认允许 circular references。Spring Boot 2.6 起默认禁止循环引用,常见配置:

properties
spring.main.allow-circular-references=true

只建议作为遗留系统短期过渡。Boot 2.7、Boot 3 项目也应以实际版本默认值和配置为准,并优先重构。

为什么 Boot 改为默认禁止

  • 循环依赖掩盖职责问题;
  • 构造和初始化顺序难理解;
  • 代理一致性风险;
  • AOT/native image 更需要清晰对象图;
  • 升级和测试更难;
  • 启动成功不代表业务调用无环。

二十、完整 JDK 8 + Spring 5 Demo

以下程序验证四种场景:

  1. 默认允许时 singleton 字段循环成功;
  2. 构造器循环失败;
  3. 主动关闭 allowCircularReferences 后字段循环失败;
  4. 构造器参数 Lazy 代理打断时序成功。
java
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Lazy;

public class CircularDependencyDemo {
    static class FieldA {
        @Autowired FieldB b;
        String call() { return "A->" + b.name(); }
        String name() { return "A"; }
    }

    static class FieldB {
        @Autowired FieldA a;
        String name() { return "B(" + a.name() + ")"; }
    }

    static class ConstructorA {
        ConstructorA(ConstructorB b) { }
    }

    static class ConstructorB {
        ConstructorB(ConstructorA a) { }
    }

    static class LazyA {
        private final LazyB b;
        LazyA(@Lazy LazyB b) { this.b = b; }
        String call() { return "A->" + b.name(); }
        String name() { return "A"; }
    }

    static class LazyB {
        private final LazyA a;
        LazyB(LazyA a) { this.a = a; }
        String name() { return "B(" + a.name() + ")"; }
    }

    public static void main(String[] args) {
        fieldCycleSucceeds();
        constructorCycleFails();
        disabledFieldCycleFails();
        lazyConstructorCycleSucceeds();
    }

    private static void fieldCycleSucceeds() {
        AnnotationConfigApplicationContext context = newContext();
        context.register(FieldA.class, FieldB.class);
        context.refresh();
        try {
            FieldA a = context.getBean(FieldA.class);
            if (a.b.a != a) throw new AssertionError("early引用不一致");
            if (!"A->B(A)".equals(a.call())) throw new AssertionError(a.call());
            System.out.println("fieldCycle=passed");
        } finally {
            context.close();
        }
    }

    private static void constructorCycleFails() {
        AnnotationConfigApplicationContext context = newContext();
        context.register(ConstructorA.class, ConstructorB.class);
        try {
            context.refresh();
            throw new AssertionError("预期构造器循环失败");
        } catch (BeansException expected) {
            System.out.println("constructorCycle=passed");
        } finally {
            context.close();
        }
    }

    private static void disabledFieldCycleFails() {
        AnnotationConfigApplicationContext context = newContext();
        context.getDefaultListableBeanFactory()
                .setAllowCircularReferences(false);
        context.register(FieldA.class, FieldB.class);
        try {
            context.refresh();
            throw new AssertionError("预期禁用循环引用后失败");
        } catch (BeansException expected) {
            System.out.println("disabledFieldCycle=passed");
        } finally {
            context.close();
        }
    }

    private static void lazyConstructorCycleSucceeds() {
        AnnotationConfigApplicationContext context = newContext();
        context.register(LazyA.class, LazyB.class);
        context.refresh();
        try {
            String result = context.getBean(LazyA.class).call();
            if (!"A->B(A)".equals(result)) throw new AssertionError(result);
            System.out.println("lazyConstructorCycle=passed");
        } finally {
            context.close();
        }
    }

    private static AnnotationConfigApplicationContext newContext() {
        return new AnnotationConfigApplicationContext();
    }
}

预期:

text
fieldCycle=passed
constructorCycle=passed
disabledFieldCycle=passed
lazyConstructorCycle=passed

失败场景中 Spring 会记录 refresh 取消警告,这是预期测试证据,不是测试失败。

二十一、为什么应该消除循环而不是开启配置

循环通常意味着:

  • 职责边界混乱;
  • 一个 Service 同时编排和执行;
  • 读写逻辑互相调用;
  • 为复用一小段逻辑注入整个 Service;
  • 领域事件被直接回调替代;
  • 模块接口方向错误。

21.1 提取协调器

原来:

text
OrderService <-> InventoryService

改为:

text
OrderApplicationService
    ├── OrderService
    └── InventoryService

OrderService 和 InventoryService 不再互相编排,由上层应用服务协调事务和顺序。

21.2 提取小接口

如果 A 只需要 B 的查询能力,不要注入承担大量写操作的 BService,可提取 InventoryQuery 端口,由独立实现提供。

21.3 领域事件

订单完成后通知积分、消息、审计等非核心副作用,可发布事件或可靠消息,避免 OrderService 反向依赖所有下游。但必须设计事务一致性、幂等、重试和失败补偿,不能把同步循环换成不可靠异步。

21.4 把共享逻辑下沉

A 和 B 互相调用只是为了复用一个转换或校验,应把纯逻辑提取为无状态组件,而不是两个 Service 互相注入。

二十二、商业场景:订单与库存

错误设计:

text
OrderService.createOrder
    ↓ 调用
InventoryService.reserve
    ↓ 回调
OrderService.updateReserveStatus

问题:

  • 创建时依赖环;
  • 事务边界不清;
  • 运行时递归风险;
  • 测试必须同时构造两个 Service;
  • 库存模块知道订单内部状态;
  • 将来拆服务更困难。

重构:

text
CreateOrderApplicationService

OrderDomainService创建订单

InventoryPort预占库存

OrderRepository保存状态

Outbox保存事件

由应用服务编排,领域服务和端口保持单向依赖。

二十三、常见错误与后果

错误后果正确方向
开启allowCircularReferences结束排查设计环和代理风险保留画依赖图并重构
把构造器改字段只为启动隐藏强制依赖和空值窗口调整职责方向
叠加多个Lazy错误推迟到首个请求仅作为受控过渡
认为三级缓存解决prototypescope语义不成立调整scope和所有权
手工把半成品放Map原始对象泄漏使用框架或直接失败
打开raw injection部分调用绕过代理保证早期代理一致或消环
只看最外层异常找不到真实依赖路径沿cause和BeanName追踪
业务方法运行时互调无限递归、重复事务提取协调器或事件

二十四、生产排查 Runbook

24.1 收集证据

  1. 保存完整 BeanCreationException/UnsatisfiedDependencyException cause 链;
  2. 提取所有 BeanCurrentlyInCreationException 中的 Bean 名;
  3. 记录注入点是构造器、字段、Setter、工厂方法还是 dependsOn;
  4. 记录 scope、Lazy、代理注解和实际 Spring/Boot 版本;
  5. 记录 spring.main.allow-circular-references 实际值;
  6. 获取条件装配报告和 BeanDefinition 来源。

24.2 画最小依赖图

不要只看 A 和 B,异常链可能是:

text
A -> B -> C -> D -> B

区分:

  • 创建依赖;
  • 业务调用依赖;
  • 事件订阅;
  • FactoryBean 工厂与产品;
  • 父子容器。

24.3 判断属于哪类

  • 构造器环:三级缓存无能为力;
  • singleton属性环:看开关和代理一致性;
  • prototype环:失败;
  • dependsOn环:修改顺序定义;
  • raw injection:检查后置处理器和代理;
  • 初始化回调环:不要在PostConstruct过早调用协作者;
  • Lazy运行时失败:检查首次真实目标解析。

24.4 临时止损

遗留系统可在充分回归后临时启用 allowCircularReferences 或在一侧使用 Lazy,但要:

  • 记录技术债负责人和期限;
  • 增加启动集成测试;
  • 验证事务、缓存、异步和权限代理;
  • 做首调预热;
  • 监控 Bean 初始化失败;
  • 禁止继续新增环。

24.5 长期修复

提取协调器、查询端口、共享纯组件或可靠事件,让依赖图成为有向无环图。修复后重新关闭循环引用开关并运行全量测试。

二十五、源码断点

以 A、B 为条件断点:

text
AbstractAutowireCapableBeanFactory.doCreateBean
DefaultSingletonBeanRegistry.addSingletonFactory
DefaultSingletonBeanRegistry.getSingleton(String, boolean)
AbstractAutowireCapableBeanFactory.getEarlyBeanReference
AbstractAutoProxyCreator.getEarlyBeanReference
AbstractAutoProxyCreator.postProcessAfterInitialization
DefaultSingletonBeanRegistry.addSingleton
DefaultSingletonBeanRegistry.removeSingleton

观察:

  • singletonsCurrentlyInCreation;
  • singletonFactories;
  • earlySingletonObjects;
  • singletonObjects;
  • earlyProxyReferences;
  • exposedObject;
  • dependentBeanMap;
  • allowCircularReferences;
  • allowRawInjectionDespiteWrapping。

二十六、版本边界

技术线常见行为
Spring Framework 4/5工厂层经典默认允许部分singleton循环引用
Spring 5.3本页源码与Demo主要验证线
Spring Boot 2.6+默认禁止循环引用,遗留可临时配置开启
Spring Boot 2.7仍应以实际配置为准并优先重构
Spring 6 / Boot 3Java 17基线,更不应依赖循环引用设计

不同版本的锁和缓存实现可能演进,学习源码要匹配项目实际依赖。稳定原理是:只有实例化后才可能提供早期引用,代理必须保持一致。

二十七、面试标准回答

Spring 如何解决循环依赖

在允许循环引用的singleton属性注入场景中,A实例化后、属性填充前注册能调用getEarlyBeanReference的ObjectFactory。B创建时需要A,getSingleton依次查一级、二级、三级,调用工厂取得原始或早期代理A并放入二级;B完成后A继续注入和初始化,最终对账早期引用并进入一级缓存。

为什么需要三级缓存

三级保存延迟工厂,让AutoProxyCreator在真正有人需要早期引用时生成一致代理;二级保存工厂已经产生的同一早期引用;一级保存最终singleton。若实例化后直接暴露原始对象,循环依赖者可能持有原始A,而其他Bean持有事务代理A,增强语义不一致。

构造器循环为什么解决不了

early reference工厂在实例化后才注册,而构造器参数在实例化前必须解析。A构造需要B、B构造需要A时,A实例尚不存在,没有对象可提前暴露。

Boot为什么默认禁止循环依赖

循环依赖掩盖职责问题,增加初始化、代理一致性、测试、升级和AOT难度。Boot 2.6起默认禁止,开启配置只能作为遗留过渡,长期应把依赖图重构为单向无环。

Lazy能解决构造器循环吗

在一侧注入点使用Lazy会先注入代理,不立即创建真实依赖,从而打断创建时序;这不是三级缓存解决,也没有消除逻辑双向依赖,还会引入首调延迟和运行期失败风险。

二十八、关联知识与验收

掌握验收:

  • 能区分构造器、属性、prototype、dependsOn和逻辑调用环;
  • 能写出三缓存的内容与迁移时机;
  • 能解释addSingletonFactory为何不立即生成对象;
  • 能画出getSingleton读取一级、二级、三级的流程;
  • 能解释getEarlyBeanReference和earlyProxyReferences;
  • 能说明raw injection despite wrapping;
  • 能运行四组成功失败Demo;
  • 能说明Framework与Boot开关差异;
  • 能用协调器、端口和事件消除业务循环;
  • 能根据完整cause和依赖图执行生产Runbook。