循环依赖是什么
在平时的面试中,只要问到Spring,那么大概率肯定会问什么是循环依赖,Sping是如何解决循环依赖的。以及三级缓存机制是什么。所以为了从根本上解决这个问题,本篇主要详细介绍一下循环依赖的问题。
Spring Bean初始化过程
Spring 在创建Bean对象的时候,会按照一定的流程进行初始化。主要的话就是如下
- 根据配置文件,扫描固定的路径 比如bean.xml ,封装成BeanDefinition
- 根据BeanDefinition 推断出Class构造方法,反射得到一个原始对象。
- 对原始对象进行属性复制。以及对应的切面处理等。生产代理对象放入BeanFactory中。
循环依赖其实就是A、B两个类,A同时引用B,B同时引用A。
A初始化的时候,需要引用到B,然后会去创建B,但是B又引用到了A。所以就会出现循环依赖。
一句话就是:循环依赖是在SpringBean初始化声明周期而产生的问题
有哪些方式的循环依赖
我们知道Bean的属性注入,1.通过构造注入 2.通过setter方式注入。
并且因为Bean scope=“singleton” 、prototype 两种方式。所以就有三种方式。
- 1.scope = prototype 构造注入 (直接报错)
- 2.score = singleton 构造注入 (直接报错)
- 3.scope = singleton setteer注入 (三级缓存解决)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serviceB' defined in class path resource [beans.xml]: Cannot resolve reference to bean 'serviceA' while setting bean property 'serviceA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation: Is there an unresolvable circular reference?
好了,来分析以下,scope = prototype的方式。对象是多例,无法解决。并且构造对象这种方式,就是鸡生蛋,蛋生鸡。无法解决。
所以大家常说的spring 循环依赖问题,默认就是单例下 使用setter方式。
Code
@Service
public class ServiceA {
private ServiceB serviceB;
public ServiceB getServiceB() {
return serviceB;
}
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
public ServiceA() {
System.out.println("serviceA构造成功");
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
public ServiceA getServiceA() {
return serviceA;
}
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
public ServiceB() {
System.out.println("serviceB构造成功");
}
}
<bean id="serviceA" class="com.qxlx.main.springbean.circulardend.ServiceA" scope="singleton">
<property name="serviceB" ref="serviceB"/>
</bean>
<bean id="serviceB" class="com.qxlx.main.springbean.circulardend.ServiceB" scope="singleton">
<property name="serviceA" ref="serviceA"/>
</bean>
三级缓存源码解读
三级缓存其实就是DefaultSingletonBeanRegistry中的三个Map。
实例化:创建了对象,但是还有属性的赋值。 new A()
初始化:将对象需要的属性赋值 a.setB(b);
三级缓存
/** 享元模式的单例。缓存所有单实例对象,单例对象池。ioc容器-单例池; Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<<String, ObiectFactory<<?>> singletonFactories,存放可以生成Bean的工厂
4大方法
整体流程
流程总结
整体流程,其实就是A创建的过程中需要B,所以将A对象自己放入三级缓存中,然后去实例化B。
2.B在实例化的时候,发现自己引用了属性A,所以从三级缓存中依此查询,查询一级缓存没有,在查询二级缓存也没有,发现三级缓存有,将A从三级缓存放入二级缓存,并将三级缓存中的A删除。
3.B创建完毕之后,将自己放入一级缓存中。然后A接着创建,直接从一级缓存中获取B。A创建完成,将自己放入一级缓存中。
整个过程其实就是依赖于如果发现是循环依赖的话,通过将对象提前暴露出来,存储缓存中,并且scope=singleton。所以可以解决这个问题。
小结
本篇主要介绍了Spring循环依赖的。为什么会有循环依赖的问题,以及循环依赖有哪些方式,Spring是如何通过三级缓存解决的循环依赖。
参考资料:
https://www.cnblogs.com/java-chen-hao/p/11139887.html
https://blog.csdn.net/qq_25448409/article/details/111247655?spm=1001.2014.3001.5502
https://blog.csdn.net/a745233700/article/details/110914620