Springboot又循环依赖了?!介绍6种解决方案
前言
大家好,我是大鱼七成饱。这敲代码敲的手快秃噜皮,一不小心就环引用了,恰好旁边同事也遇到,为解决这个问题,稍微花了点时间。俗话说好记性不如烂笔头,这次整理下循环引用的各种解决方案,下次谁问就假装大神,拍下脑门就出答案。
一、啥是循环依赖
简单来说就是A依赖B,B也依赖A。也可能是A依赖C,C依赖B,B依赖A。对于Java或者go等开发者来说,这并不是个新问题,面试也经常问到。Spring框架通过一个称为"三级缓存"的机制来处理循环依赖,以此来保证Spring容器可以正确地解析和初始化所有的beans。
二、为什么么Springboot要禁止循环依赖
先说正常的依赖,比如上面的A依赖B,B依赖C。这个时候spring可以分析出依赖关系,判断出要先创建C,然后创建B,将C注入B,最后创建A注入。
当存在循环依赖的时候,且是构造器注入的时候,Spring不好判断先创建哪个,从而报BeanCurrentlyInCreationException异常。因此,从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动。
三、六种解决方案
三个Service的初始代码如下(A->B->C->A):
@Service
public class ServiceA {
@Resource
private ServiceC serviceC;
}
@Service
public class ServiceB {
@Resource
private ServiceC serviceC;
}
@Service
public class ServiceC {
@Resource
private ServiceA serviceA;
}
1、添加允许循环引用配置
spring.main.allow-circular-references = true
缺点:不支持构造方法注入
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceC serviceC;
@Autowired
public ServiceC(ServiceA serviceC) {
this.serviceC = serviceC;
}
}
@Service
public class ServiceC {
@Resource
private ServiceA serviceA;
@Autowired
public ServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
2、添加@Lazy注解
构造方法注入或者Set注入的将其中一个添加@Lazy,注解,这样有一个延迟注入,从而打破了循环引用的环。
@Service
public class ServiceC {
private ServiceA serviceA;
// @Autowired
// public ServiceC(@Lazy ServiceB serviceB) {
// this.serviceB = serviceB;
// }
@Autowired
public void setServiceA(@Lazy ServiceA serviceA) {
this.serviceA = serviceA;
}
}
3、添加@PostContruct注解延迟注入
这种方法更方便两个类互相依赖的处理,A和B互相依赖。A延迟到PostConstruct注入
@Service
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
@Resource
private ServiceA serviceA;
@PostConstruct
public void init() {
serviceA.setServiceB(this);
}
}
4、afterPropertiesSet之后注入
这个方法需要继承ApplicationContextAware和InitializingBean接口。属于另一种延后注入的方法。
@Service
public class ServiceB implements ApplicationContextAware, InitializingBean {
private ServiceA serviceA;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
serviceA = applicationContext.getBean(ServiceA.class)
}
}
5、使用时获取
这个方法使用了ApplicationContext,实际调用方法时直接获取该service.
@Service
public class ServiceA {
@Resource
private ServiceB serviceB;
public void sayHello() {
System.out.println("hi");
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Resource
private ApplicationContext applicationContext;
public void helloWorld() {
ServiceA bean = applicationContext.getBean(ServiceA.class);
bean.sayHello();
}
}
6、使用消息机制解耦引用关系
相互依赖的原因大部分是需要互相调用方法,那么可以用消息的解耦特性,打破互相调用。
以下是示例:
@Service
public class ServiceA {
@EventListener
public void sayHello(HiEvent event) {
System.out.println("hi");
}
}
@Service
public class ServiceB {
@Resource
private ApplicationContext applicationContext;
public void helloWorld() {
applicationContext.publishEvent(new HiEvent());
}
}
四、总结
本文梳理了解决循环依赖的几种方法。大体分三类:配置解决,延后注入使用和解耦调用关系。如果出现了循环依赖,大体是设计有点问题,是软件开发中的坏味道,如果暂时无法从架构层面解决,那可以根据场景选择适合的方案。
转载自:https://juejin.cn/post/7388328892309504034