真没想到,阻止框架升级的不仅仅是某个框架被弃用,还有让人恶心的循环调用。
如果在启动项目时看到类似这样循环的标识,恭喜你,项目中出现循环依赖了。
循环依赖普遍出现在service层。ServiceA调用了ServiceB,ServiceB调用了ServiceC,ServiceC又调用了ServiceA,形成了一个调用上的循环。
静态代码有循环依赖,并不代表代码执行后还有循环依赖!
我想做点辩解,并想允许它的存在。
同时呢,我还有一个疑问:同样的代码,为什么历史版本没有出现循环依赖?项目升级后就出现了循环依赖?
原来是:从Spring Boot 2.6开始,默认禁止了循环依赖(spring.main.allow-circular-references=false),导致原本被允许的循环依赖在启动时直接报错。这就导致即使代码中存在隐式的循环依赖(如A依赖B,B依赖A),也会在启动时被检测并抛出异常。
原因知道了。循环依赖应该怎么解决呢?
一、临时解决方案
报错提示信息已经给出了答案。
在application.properties或application.yml中添加:
spring.main.allow-circular-references=true
如果短期内无法重构代码,可以临时允许循环引用。
或者在启动类中添加:
@SpringBootApplication
public class MyApp{
public static void main(String[] args){
new SpringApplicationBuilder(MyApp.class)
.allowCircularReferences(true)
.run(args);
}
}
二、从程序设计上
从程序设计上调整改动有点大,看看即可。
1、重构设计,打破循环
检查Service之间的相互依赖是否必要。
考虑引入第三方服务或事件机制来解耦。
使用接口隔离原则,将大接口拆分为更小、更专注的接口。
2、使用Setter注入代替字段注入
// 例如在VoteNoticeServiceImpl中
private IClusterNoticeService clusterNoticeService;
@Autowired
publicvoidsetClusterNoticeService(IClusterNoticeService clusterNoticeService){
this.clusterNoticeService = clusterNoticeService;
}
3、使用@Lazy注解
// 在循环依赖的一侧添加@Lazy
@Autowired
@Lazy
private IClusterNoticeService clusterNoticeService;
这三个建议在新建项目编码的时候可以考虑考虑。
三、长期建议
循环依赖通常是设计问题,建议:
1、重新审视服务层的职责划分。
2、考虑使用领域驱动设计(DDD)来明确各服务的边界。
3、对于通知类服务,可以考虑使用事件驱动架构,通过Spring事件机制解耦。
最佳实践是重构代码消除循环依赖,而不是依赖框架的宽松配置。
尴尬了,我就想依赖框架的宽松配置!
四、最后总结
如果是新项目,即可以使用长期建议,也可以从程序设计上考虑考虑。
如果是升级老项目,还是用临时解决方案吧,又快又省麻烦还少!
领取专属 10元无门槛券
私享最新 技术干货