一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Spring循环依赖解析与单例Bean共享机制详解

时间:2026-07-12 09:10:45 编辑:袖梨 来源:一聚教程网

Spring默认采用单例(Singleton)作用域,因此同一类的Bean在IoC容器中仅存在一个共享实例;getBean()多次调用返回的是同一个对象引用,这也是构造器日志仅打印一次的根本原因。

spring默认采用单例(singleton)作用域,因此同一类的bean在ioc容器中仅存在一个共享实例;`getbean()`多次调用返回的是同一个对象引用,这也是构造器日志仅打印一次的根本原因。

在您提供的代码中,DependencyA 和 DependencyB 均未显式声明作用域,因此遵循Spring默认规则:@Component 注解的Bean默认为 singleton 作用域。这意味着:

  • 整个Spring容器生命周期内,每个Bean类(如 DependencyA.class)有且仅有一个实例
  • applicationContext.getBean(DependencyA.class) 和 applicationContext.getBean(DependencyB.class) 分别获取各自类型的唯一实例;
  • 更关键的是:当 DependencyA 初始化时需注入 DependencyB,Spring会先创建 DependencyB 实例(触发其构造函数),并完成其初始化(包括注入 DependencyA);后续再次调用 getBean(DependencyB.class) 时,直接从一级缓存 singletonObjects 中返回已存在的实例——不再重复构造,故 "I am constructor of Dependency B" 仅输出一次

这正是Spring三级缓存机制发挥作用的典型场景:

缓存层级 名称 存储内容 在循环依赖中的角色
一级缓存 singletonObjects 完全初始化完毕的成品Bean(可用Bean) 最终对外提供、getBean() 默认返回来源
二级缓存 earlySingletonObjects 提前曝光的半成品Bean(已实例化,未初始化) 供其他Bean提前引用,避免死锁
三级缓存 singletonFactories ObjectFactory 工厂对象 按需生成早期引用,保障字段/Setter注入可打破循环

您的案例属于“单例 + Setter注入”循环依赖,Spring完全支持:

  • 先调用 DependencyA 构造器 → 放入三级缓存 → 触发注入 DependencyB;
  • 再调用 DependencyB 构造器 → 放入三级缓存 → 注入 DependencyA 时,通过三级缓存获取 DependencyA 的早期引用(ObjectFactory.getObject())→ 完成 DependencyB 初始化 → 升级至一级缓存;
  • DependencyA 继续完成自身属性注入 → 升级至一级缓存。

因此,两次 getBean() 调用本质是获取容器中已就绪的单例对象,而非新建实例。

⚠️ 注意事项:

  • 若将任一Bean改为 @Scope("prototype"),每次 getBean() 都会触发全新构造(日志重复打印),但此时循环依赖将直接失败(BeanCurrentlyInCreationException),因原型Bean不参与三级缓存管理;
  • Spring 6.0+ / Spring Boot 3.x 默认禁用循环依赖检测(spring.main.allow-circular-references=false),需显式配置为 true 才能启用该机制;
  • 构造器注入循环依赖永远无法被Spring自动解决,因其要求依赖必须在构造时就位,与“提前暴露”机制冲突。
// ✅ 正确验证原型行为(将导致循环依赖报错)@Component@Scope("prototype") // ← 添加此注解后,启动将失败class DependencyA { /* ... */ }

总结:getBean() 不创建新实例,而是从容器中共享已有单例——这是Spring高效复用资源的核心设计,也是理解循环依赖能否被解决的前提。合理利用作用域、注入方式与缓存机制,才能写出健壮、可维护的Spring应用。

热门栏目