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

最新下载

热门教程

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() 调用(包括显式获取和依赖注入过程中的隐式获取)均直接返回该缓存实例,不会重复调用构造方法

? 执行流程还原(关键点)

  1. 容器初始化阶段:Spring扫描到 DependencyA 和 DependencyB,识别出循环依赖(A ⇄ B);
  2. A 实例化
    • 调用 new DependencyA() → 输出 "I am constructor of Dependency A";
    • 此时A尚未完成属性注入,Spring将其 ObjectFactory 放入三级缓存 singletonFactories
  3. A 注入 B 时触发 B 创建
    • Spring尝试创建 DependencyB:调用 new DependencyB() → 输出 "I am constructor of Dependency B";
    • B 同样未完成初始化,其工厂也进入三级缓存;
  4. B 注入 A
    • Spring从三级缓存中通过 ObjectFactory.getObject() 获取A的早期暴露引用(early reference)(此时A已实例化但未设属性);
    • 执行 setDependencyA() → 输出 "I am setDependencyA !";
  5. B 初始化完成
    • B被移入二级缓存 earlySingletonObjects,再移入一级缓存 singletonObjects;
  6. 回到 A 的属性填充
    • Spring从一级缓存中取出已就绪的B实例,执行 setDependencyB() → 输出 "I am setDependencyB !";
  7. 最终 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利用三级缓存实现了对象实例化与初始化的时空解耦——先有“壳”,再填“瓤”,从而在闭环中建立可靠引用链。

热门栏目