Spring 循环依赖、三级缓存与早期代理全过程
循环依赖表示对象图中存在环,例如 A 依赖 B、B 又依赖 A。Spring 不是“通过三级缓存解决所有循环依赖”,而是在特定条件下,允许另一个 singleton 暂时取得当前 Bean 的早期引用,让属性填充继续进行。
真正困难的地方不是提前暴露一个对象,而是:如果 A 最终需要事务、缓存或其他 AOP 代理,B 不能永远持有原始 A,而其他 Bean 持有代理 A。三级缓存中的 ObjectFactory 为按需生成一致早期代理提供了机会。
学习目标
- 区分构造器、字段、Setter、prototype、dependsOn 和代理循环;
- 写出 Spring 5.3 singleton 三缓存的数据结构与职责;
- 解释
addSingletonFactory、getSingleton(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 构造器循环
class AService {
AService(BService b) { }
}
class BService {
BService(AService a) { }
}创建 A 前必须先取得 B,创建 B 前又必须先取得 A。双方都没有实例,无法注册 early reference 工厂。
1.2 字段或 Setter 循环
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 循环
A dependsOn B
B dependsOn A这属于显式创建顺序环,Spring 会检查依赖关系并失败。它与字段注入的 early reference 不是同一个问题。
1.5 逻辑调用循环
A 调用 B,B 在运行时又回调 A,即使容器能够创建成功,也可能出现无限递归、重复事务、重复消息或死锁。三级缓存只处理创建时对象引用,不解决运行时业务调用环。
二、Spring 能解决的条件
经典 Spring 5 属性循环通常要求:
- 参与者是 singleton;
allowCircularReferences=true;- 至少有一方可先完成实例化;
- 依赖在实例化后的属性填充阶段才需要;
- early reference 与最终暴露对象可以保持一致;
- 没有某个后置处理器只在初始化后包装对象却不支持早期引用;
- Bean 创建没有在其他生命周期回调中再次形成更复杂的环。
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 核心结构可抽象为:
Map<String, Object> singletonObjects;
Map<String, Object> earlySingletonObjects;
Map<String, ObjectFactory<?>> singletonFactories;| 通俗级别 | 字段 | 保存内容 | 什么时候存在 |
|---|---|---|---|
| 一级 | singletonObjects | 完成创建的最终singleton | 初始化和包装完成后 |
| 二级 | earlySingletonObjects | 工厂已生成的早期引用 | 真正发生早期获取后 |
| 三级 | singletonFactories | 可以生成早期引用的ObjectFactory | 实例化后、属性填充前 |
3.1 三级缓存不是第三份 Bean
三级保存工厂:
ObjectFactory<?> factory工厂被调用后才产生早期引用。这个引用可能是原始对象,也可能是经过 getEarlyBeanReference 的代理。
3.2 二级缓存为什么存在
同一个 early factory 只应生成一次早期引用。第一次调用后将结果放入二级并移除三级工厂,后续循环参与者复用同一引用,避免多次生成不同代理。
3.3 一级缓存的对象才是完整对象吗
通常一级保存最终 exposedObject,但“完整”仍是容器生命周期意义:外部资源可能后来失效,Bean 也可能内部状态不健康。一级只代表 Spring 创建流程成功完成。
四、doCreateBean 为什么此时加入三级工厂
常见条件:
boolean earlySingletonExposure = mbd.isSingleton()
&& this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName);满足后:
addSingletonFactory(beanName,
() -> getEarlyBeanReference(beanName, mbd, bean));时机是:
createBeanInstance完成
↓
MergedBeanDefinitionPostProcessor完成
↓
注册singletonFactory
↓
populateBean开始必须先实例化,因为工厂需要捕获原始 bean;必须早于属性填充,因为循环可能在解析字段或 Setter 时发生。
五、addSingletonFactory() 做什么
Spring 5.3 主线可概括为:
一级缓存还没有同名完整对象
↓
singletonFactories.put(beanName, factory)
↓
earlySingletonObjects.remove(beanName)
↓
registeredSingletons记录名称它不会把 bean 放入一级,也不会立即调用工厂。此时 A 仍在创建中。
六、getSingleton(beanName, true) 读取顺序
当 B 解析依赖 A 时,BeanFactory 查询 singleton:
singletonObjects.get(A)
↓ 未命中
A是否currentlyInCreation
↓ 是
earlySingletonObjects.get(A)
↓ 未命中
allowEarlyReference是否为true
↓ 是
singletonFactories.get(A)
↓ 命中
factory.getObject()
↓
earlySingletonObjects.put(A, earlyReference)
↓
singletonFactories.remove(A)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 --> IallowEarlyReference=false 的查询不会主动调用三级工厂,常用于创建后检查已经有人取过早期引用没有。
七、A 与 B 的完整时间线
| 时刻 | 动作 | A的缓存状态 |
|---|---|---|
| 1 | getBean(A) | 无 |
| 2 | A标记currentlyInCreation | 无 |
| 3 | 实例化A | 原始A仅在创建栈 |
| 4 | 注册A的ObjectFactory | 三级 |
| 5 | populate A,解析B | 三级 |
| 6 | 实例化B并注册B工厂 | A三级、B三级 |
| 7 | populate B,解析A | A三级 |
| 8 | 调用A工厂生成early reference | A转入二级 |
| 9 | B注入early A并完成初始化 | A二级、B最终将进一级 |
| 10 | A注入完整B | A二级、B一级 |
| 11 | A初始化和包装 | A二级 |
| 12 | 对比early A与最终exposedObject | A二级 |
| 13 | addSingleton(A) | A一级,二/三级删除 |
八、为什么只有二级缓存不够
如果实例化 A 后直接把原始 A 放到二级:
- B 注入原始 A;
- A 初始化后匹配事务 Advisor;
- 容器最终返回代理 A;
- B 永远持有原始 A;
- 其他 Bean 持有代理 A;
- B 调用 A 时事务、缓存或审计不生效。
三级工厂把“生成哪种早期引用”的决定推迟到真正需要时,并调用 AutoProxyCreator 的早期引用扩展点。
理论上可以设计其他数据结构实现同样语义,但 Spring 的三级结构清楚分离了:完整对象、已生成早期引用、尚未执行的早期引用工厂。面试重点应讲延迟工厂和代理一致性,不是争论 Map 数量。
九、getEarlyBeanReference() 与代理
AbstractAutowireCapableBeanFactory 会让 SmartInstantiationAwareBeanPostProcessor 参与早期引用。AutoProxyCreator 可:
- 记录当前 Bean 的早期代理标识;
- 查找适用 Advisor;
- 必要时创建 JDK/CGLIB 代理;
- 返回代理作为 early reference;
- 若无需代理,返回原始 bean。
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 --> I9.1 为什么记录 earlyProxyReferences
Bean 后续到 postProcessAfterInitialization 时,AutoProxyCreator 要知道该 Bean 已经产生过早期代理,避免再次创建一个不同代理。具体缓存键和实现随版本演进,但目标是维持引用一致性。
9.2 所有后置处理器都支持早期代理吗
不是。某个处理器如果只在初始化后包装 Bean,却不参与 getEarlyBeanReference,循环依赖者可能已经拿到原始引用,最终包装时就会产生不一致。复杂代理组合不能保证被循环引用机制兜底。
十、最终对象怎样与早期引用对账
initializeBean 后得到 exposedObject。Spring 再以不主动创建 early reference 的方式查询:
Object earlySingletonReference = getSingleton(beanName, false);若存在早期引用:
exposedObject == raw bean:说明正常链未产生新包装,最终可直接采用 early reference;- exposedObject 已是与早期引用一致的代理:继续使用;
- 其他 Bean 已注入 raw bean,但最终又包装成不同对象:需要检测并可能失败。
十一、raw injection despite wrapping
问题场景:
B在循环依赖中注入原始A
↓
A初始化后被某个BPP包装为代理A
↓
容器对外返回代理A
↓
B仍持有原始A当 allowRawInjectionDespiteWrapping=false 且存在真实 dependent Bean 时,Spring 可能抛 BeanCurrentlyInCreationException,提示 Bean 已以原始形式注入其他对象,但最终又被包装。
不要通过打开 raw injection 选项掩盖问题。正确方向是消除环,或确保相关代理处理器支持 early reference。
十二、addSingleton() 如何完成晋级
成功创建最终 singleton 后:
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 可能取得半成品、永久误报循环、销毁顺序错误或持有泄漏资源。业务初始化中自行创建的线程和连接仍需在异常路径释放。
十四、构造器循环为什么无法解决
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 怎样打断构造器创建时序
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 起默认禁止循环引用,常见配置:
spring.main.allow-circular-references=true只建议作为遗留系统短期过渡。Boot 2.7、Boot 3 项目也应以实际版本默认值和配置为准,并优先重构。
为什么 Boot 改为默认禁止
- 循环依赖掩盖职责问题;
- 构造和初始化顺序难理解;
- 代理一致性风险;
- AOT/native image 更需要清晰对象图;
- 升级和测试更难;
- 启动成功不代表业务调用无环。
二十、完整 JDK 8 + Spring 5 Demo
以下程序验证四种场景:
- 默认允许时 singleton 字段循环成功;
- 构造器循环失败;
- 主动关闭 allowCircularReferences 后字段循环失败;
- 构造器参数 Lazy 代理打断时序成功。
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();
}
}预期:
fieldCycle=passed
constructorCycle=passed
disabledFieldCycle=passed
lazyConstructorCycle=passed失败场景中 Spring 会记录 refresh 取消警告,这是预期测试证据,不是测试失败。
二十一、为什么应该消除循环而不是开启配置
循环通常意味着:
- 职责边界混乱;
- 一个 Service 同时编排和执行;
- 读写逻辑互相调用;
- 为复用一小段逻辑注入整个 Service;
- 领域事件被直接回调替代;
- 模块接口方向错误。
21.1 提取协调器
原来:
OrderService <-> InventoryService改为:
OrderApplicationService
├── OrderService
└── InventoryServiceOrderService 和 InventoryService 不再互相编排,由上层应用服务协调事务和顺序。
21.2 提取小接口
如果 A 只需要 B 的查询能力,不要注入承担大量写操作的 BService,可提取 InventoryQuery 端口,由独立实现提供。
21.3 领域事件
订单完成后通知积分、消息、审计等非核心副作用,可发布事件或可靠消息,避免 OrderService 反向依赖所有下游。但必须设计事务一致性、幂等、重试和失败补偿,不能把同步循环换成不可靠异步。
21.4 把共享逻辑下沉
A 和 B 互相调用只是为了复用一个转换或校验,应把纯逻辑提取为无状态组件,而不是两个 Service 互相注入。
二十二、商业场景:订单与库存
错误设计:
OrderService.createOrder
↓ 调用
InventoryService.reserve
↓ 回调
OrderService.updateReserveStatus问题:
- 创建时依赖环;
- 事务边界不清;
- 运行时递归风险;
- 测试必须同时构造两个 Service;
- 库存模块知道订单内部状态;
- 将来拆服务更困难。
重构:
CreateOrderApplicationService
↓
OrderDomainService创建订单
↓
InventoryPort预占库存
↓
OrderRepository保存状态
↓
Outbox保存事件由应用服务编排,领域服务和端口保持单向依赖。
二十三、常见错误与后果
| 错误 | 后果 | 正确方向 |
|---|---|---|
| 开启allowCircularReferences结束排查 | 设计环和代理风险保留 | 画依赖图并重构 |
| 把构造器改字段只为启动 | 隐藏强制依赖和空值窗口 | 调整职责方向 |
| 叠加多个Lazy | 错误推迟到首个请求 | 仅作为受控过渡 |
| 认为三级缓存解决prototype | scope语义不成立 | 调整scope和所有权 |
| 手工把半成品放Map | 原始对象泄漏 | 使用框架或直接失败 |
| 打开raw injection | 部分调用绕过代理 | 保证早期代理一致或消环 |
| 只看最外层异常 | 找不到真实依赖路径 | 沿cause和BeanName追踪 |
| 业务方法运行时互调 | 无限递归、重复事务 | 提取协调器或事件 |
二十四、生产排查 Runbook
24.1 收集证据
- 保存完整 BeanCreationException/UnsatisfiedDependencyException cause 链;
- 提取所有 BeanCurrentlyInCreationException 中的 Bean 名;
- 记录注入点是构造器、字段、Setter、工厂方法还是 dependsOn;
- 记录 scope、Lazy、代理注解和实际 Spring/Boot 版本;
- 记录
spring.main.allow-circular-references实际值; - 获取条件装配报告和 BeanDefinition 来源。
24.2 画最小依赖图
不要只看 A 和 B,异常链可能是:
A -> B -> C -> D -> B区分:
- 创建依赖;
- 业务调用依赖;
- 事件订阅;
- FactoryBean 工厂与产品;
- 父子容器。
24.3 判断属于哪类
- 构造器环:三级缓存无能为力;
- singleton属性环:看开关和代理一致性;
- prototype环:失败;
- dependsOn环:修改顺序定义;
- raw injection:检查后置处理器和代理;
- 初始化回调环:不要在PostConstruct过早调用协作者;
- Lazy运行时失败:检查首次真实目标解析。
24.4 临时止损
遗留系统可在充分回归后临时启用 allowCircularReferences 或在一侧使用 Lazy,但要:
- 记录技术债负责人和期限;
- 增加启动集成测试;
- 验证事务、缓存、异步和权限代理;
- 做首调预热;
- 监控 Bean 初始化失败;
- 禁止继续新增环。
24.5 长期修复
提取协调器、查询端口、共享纯组件或可靠事件,让依赖图成为有向无环图。修复后重新关闭循环引用开关并运行全量测试。
二十五、源码断点
以 A、B 为条件断点:
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 3 | Java 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。
