来聊聊Spring的循环依赖
文章目录
首先了解一下什么是循环依赖
如下代码所示,我们编写了一个A对象,A对象在new的时候要new一个B对象。
而我们new的B时,它会去new一个A对象,两者new的时候互相依赖,来来回回,就造成了大名鼎鼎的循环依赖问题。
public class ABTest {
public static void main(String[] args) {
new ClazzA();
}
}
class ClazzA {
private ClazzB b = new ClazzB();
}
class ClazzB {
private ClazzA a = new ClazzA();
}
简述解决循环依赖全过程
在debug之前,我们不妨了解一下,要解决循环依赖,那么代码该怎么写?其实计算机设计中就有一个不错的思想。觉得顶不住的时候,加个缓存试试看。
所以循环依赖问题也是同理。
如下代码所示,Obj1和Obj2是两个互相依赖的类,我们在创建对象Obj1时,不妨在他new的时候先将其放到缓存中。然后他发现它依赖Obj2,我们递归再去new Obj2,然后Obj2填充属性的时候发现他依赖Obj1,于是先去缓存中看看有没有Obj1有的话填充上去(虽然这时候Obj1还是个半成品,但是先解决燃眉之急要紧),然后代码递归回到Obj1填充Obj2的代码段,完成Obj1属性填充。
/**
* 循环依赖解决的示例代码
*/
public class CircleDepSolution {
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private static <T> T getBean(Class<T> beanClass) throws Exception {
//拿到这个bean的小写
String beanName = beanClass.getSimpleName().toLowerCase();
//去map中捞看看有没有
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
//没有就自己去创建一个,并且塞到map中
T obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
//然后捞出这个bean的所有成员属性
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> filedCLass = field.getType();
String filedName = filedCLass.getSimpleName().toLowerCase();
//去map中捞,如果有就set,没有就递归调用一下再set
field.set(obj, singletonObjects.containsKey(filedName) ? singletonObjects.get(filedName) : getBean(filedCLass));
}
//最终再返回这个bean
return obj;
}
public static void main(String[] args) throws Exception{
System.out.println(getBean(Obj1.class).getObj2());
System.out.println(getBean(Obj2.class).getObj1());
}
}
这种方式虽然解决了问题,但是我们却发现这种方案的一个特点,依赖的只有set方式的情况下才能解决,因为set使得依赖对象的创建和new分为两步骤,流程可以把控,我们完全可以先搞个半成品放到缓存中给别人取
通过debug了解Spring解决循环依赖全过程
首先编写循环依赖的对象AService和BService
@Service("aService")
public class AService {
@Autowired
private BService bService;
public BService getbService() {
return bService;
}
public void setbService(BService bService) {
this.bService = bService;
}
}
@Service("bService")
public class BService {
@Autowired
private AService aService;
public AService getaService() {
return aService;
}
public void setaService(AService aService) {
this.aService = aService;
}
}
然后我们开始debug
Aservice的创建
首先我们在预实例化的方法里面看到Aservice的beanName
然后我们回去尝试拿这个bean对象
点入发现真正做事的doGetBean方法
可以看到doGetBean方法,先会调用一个getSingleton
的方法,我们点入这个方法debug时候发现他不过是从一级缓存(即存放完全体的bean容器)是狗有aService,若没有且这个bean正处于创建中就执行创建并返回。若不存在且也没在创建中,那么就返回空对象
我们接着往下走,至于看到一个创建bean的核心逻辑,它会判断这个bean是不是单例的,若是则传beanName和一个创建bean的lambda表示式到getSingleton
中。
我们点入时发现他的核心调用singletonFactory.getObject()
这个方法就会执行上一步传入的lambda的createBean,而createBean会调用doCreateBean
然后完成bean的创建
然后再判断这个bean是否不是完全体,若不是则放到三级缓存中
放到三级缓存后在进行属性填充
而在调用属性填充过程中,我们发现一个和自动注入相关的bean后置处理
可以看到他在尝试着将BService注入
点入就发现注入的核心逻辑
注入回去beanFactory拿到建议的bean,然后点入我们发现核心的do逻辑
点入do方法后我们发现它内部调用了一个findValue,返回了一个null
这时候他就会去解决循环依赖问题,从bean容器中寻找候选人
有一次的调用了getBean
递归来到Bservice的创建
可以发现我们以递归的方式回到了原点,只不过这次是替Aservice找Bservice
一顿和Aservice差不多的操作后又来到属性填充的环节
然后有一次来到解决循环依赖的环节
然后BService递归回到了getAservice的doGetBean中
这时候容器发现Aservice被放在三级容器中处于被创建中的状态
然后他就会调用早期的存到三级缓存中的lambda搞出Aservice
然后将其存到二级缓存中
自此我们解决Bservice循环依赖的Aservice的问题,就是将一个半成品的Aservice给Bservice先用着
自此Bservice成为完全体
这时候Bservice就会被放到一级缓存中
故事再次回到Aservice填充BService的步骤
自此aService也可以在一级缓存中找到Bservice解决了循环依赖问题
总结成流程图
为什么二级就能解决循环依赖问题,而我们却要用三级缓存解决循环依赖问题呢
循环依赖思想在生活中的运用
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!