最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Spring循环依赖与单例Bean共享机制深度解析
时间:2026-07-12 09:11:47 编辑:袖梨 来源:一聚教程网
Spring默认采用单例(Singleton)作用域,因此同一类的Bean在整个IoC容器中仅存在一个共享实例;getBean()多次调用返回的是同一个对象引用,而非新实例——这正是循环依赖能被解决且构造器只执行一次的根本原因。
spring默认采用单例(singleton)作用域,因此同一类的bean在整个ioc容器中仅存在一个共享实例;`getbean()`多次调用返回的是同一个对象引用,而非新实例——这正是循环依赖能被解决且构造器只执行一次的根本原因。
在你提供的代码中,DependencyA 和 DependencyB 均未显式声明作用域,因此遵循Spring默认规则:@Scope("singleton")。这意味着:
- 容器启动时,Spring会为每个单例Bean仅创建一次实例,并将其缓存于一级缓存 singletonObjects 中;
- 后续所有 getBean() 调用(包括显式获取和依赖注入过程中的隐式获取)均直接返回该缓存实例,不会重复调用构造方法。
? 执行流程还原(关键点)
- 容器初始化阶段:Spring扫描到 DependencyA 和 DependencyB,识别出循环依赖(A ⇄ B);
-
A 实例化:
- 调用 new DependencyA() → 输出 "I am constructor of Dependency A";
- 此时A尚未完成属性注入,Spring将其 ObjectFactory 放入三级缓存 singletonFactories;
-
A 注入 B 时触发 B 创建:
- Spring尝试创建 DependencyB:调用 new DependencyB() → 输出 "I am constructor of Dependency B";
- B 同样未完成初始化,其工厂也进入三级缓存;
-
B 注入 A:
- Spring从三级缓存中通过 ObjectFactory.getObject() 获取A的早期暴露引用(early reference)(此时A已实例化但未设属性);
- 执行 setDependencyA() → 输出 "I am setDependencyA !";
-
B 初始化完成:
- B被移入二级缓存 earlySingletonObjects,再移入一级缓存 singletonObjects;
-
回到 A 的属性填充:
- Spring从一级缓存中取出已就绪的B实例,执行 setDependencyB() → 输出 "I am setDependencyB !";
-
最终 getBean() 行为:
DependencyA dependencyA = applicationContext.getBean(DependencyA.class); // 返回已创建好的ADependencyB dependencyB = applicationContext.getBean(DependencyB.class); // 返回已创建好的B
——两者均从 singletonObjects 直接获取,不再触发构造函数,故 "I am constructor of Dependency B" 仅输出一次。
✅ 验证:强制使用 Prototype 作用域
若希望每次 getBean() 都新建实例(即构造器重复执行),需显式指定作用域:
@Component@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或 @Scope("prototype")class DependencyB { // ... 同上}
此时运行将抛出 BeanCurrentlyInCreationException:因为Prototype Bean不参与三级缓存机制,Spring无法提前暴露其引用,循环依赖将直接失败——这也印证了单例+字段/Setter注入是Spring唯一支持循环依赖的组合。
⚠️ 注意事项与最佳实践
- Spring 6.0+ / Spring Boot 3.x 默认禁用循环依赖:需显式配置 spring.main.allow-circular-references=true;
- 构造器注入天然不兼容循环依赖(无“早期引用”介入时机),应优先重构为解耦设计(如引入事件、Service层协调或使用 @Lazy);
- 单例Bean共享实例意味着状态必须无害:避免在单例Bean中维护可变成员变量,否则将引发线程安全问题;
- 可通过 System.identityHashCode(bean) 验证是否为同一实例:
System.out.println(System.identityHashCode(dependencyA)); // 例如:123456789System.out.println(System.identityHashCode(applicationContext.getBean(DependencyA.class))); // 相同值
总结:getBean() 不“创建”Bean,而是“获取”容器中已管理的实例;单例作用域下的循环依赖之所以可行,本质是Spring利用三级缓存实现了对象实例化与初始化的时空解耦——先有“壳”,再填“瓤”,从而在闭环中建立可靠引用链。
相关文章
- 出发吧麦芬学者风暴之主养成攻略 07-20
- 英雄冒险团全部传家宝获取方法汇总 07-20
- 王者万象棋新手入门指引 王者万象棋零基础快速上手教程 07-20
- 检疫区最后一站金色收集品怎么获得 隐藏收集品获得方法介绍 07-20
- 息风谷战略牡丹如何获得 息风谷战略牡丹招募与获取方法详解 07-20
- 一梦九霄职业选择攻略 一梦九霄新手必看三大职业推荐与详细对比 07-20