Spring Boot开启spring.main.allow-circular-references=true时Bean的构造机制及PCF环境循环依赖问题咨询
关于Spring Boot循环依赖与
spring.main.allow-circular-references=true的疑问解答 哥们,我特别懂你看到循环依赖日志时的头大——尤其是Spring还贴心画了箭头,简直是“大型现场抓包”既视感。咱们先把你关心的两个核心问题说清楚,再给你点靠谱的长期解决方案:
1. 开启允许循环引用后,Spring到底怎么构造Bean?
其实Spring靠的是三级缓存机制来搞定这个事儿,说人话就是:
- 当Spring要创建Bean A,发现它依赖Bean B,会先把A的「半成品实例」(已经new出来,但还没填充属性、没执行初始化方法)放到缓存里。
- 然后去创建Bean B,这时候B又依赖A,Spring就从缓存里拿出那个半成品A,先注入给B,完成B的完整初始化(填属性、跑init方法)。
- 最后回头把已经完全建好的B注入给A,再完成A的初始化流程。
举个你场景里的例子:假设你的RestTemplateConfig依赖了UserService,而UserService又依赖了RestTemplateConfig。开启属性后,Spring会先造个空的RestTemplateConfig实例存起来,接着造UserService的时候把这个半成品Config塞进去,等UserService建好,再把它塞回Config里,最后两个Bean都能正常用。
2. 这些Bean会不会一直处于未满足的状态?
放心,不会!只要你的循环依赖是Spring能处理的场景(比如都是单例Bean,不管是构造器、Setter还是字段注入),最终两个Bean都会走完完整的生命周期,变成完全可用的状态。
唯一例外的是如果循环里涉及原型(prototype)Bean——这种情况Spring就算开了允许属性也处理不了,还是会报错,但RestTemplate这类组件一般都是单例的,所以你大概率不用操心这个。
3. 不推荐一直依赖allow-circular-references=true,试试这些打破循环的方案
允许循环引用只是“应急补丁”,长期来看会让代码耦合度越来越高,维护起来糟心。给你几个实用的重构思路:
- 抽离公共逻辑到独立Bean:把A和B互相依赖的那部分功能,拆成一个新的C类,让A和B都注入C,而不是互相依赖。比如如果两者都用到了RestTemplate的自定义配置,就把这部分单独做成
RestTemplateFactory,让两个类都去注入这个Factory。 - 给其中一个依赖加
@Lazy:在循环链的任意一个注入点加上@Lazy,比如在UserService里注入RestTemplateConfig时加@Lazy。这样Spring会给你一个代理对象,只有当你第一次调用这个Config的方法时,才会真正初始化它,完美打破循环。 - 用Setter注入代替构造器注入:如果你的循环是构造器注入导致的,改成Setter注入(或者更规范的构造器+
@Lazy)。Spring对Setter注入的循环依赖兼容性更好,而且代码可读性也不差。 - 用
ObjectProvider延迟获取依赖:在其中一个Bean里注入ObjectProvider<依赖类>,而不是直接注入依赖。比如在RestTemplateConfig里写@Autowired ObjectProvider<UserService> userServiceProvider,需要用的时候调用userServiceProvider.getObject()——这时候UserService已经初始化完成了,自然不会有循环问题。
内容的提问来源于stack exchange,提问作者likejudo
相关产品推荐
相关产品推荐

