DefaultSingletonBeanRegistry
Spring中解决循环依赖问题
Spring循环依赖包括构造器循环依赖和setter循环依赖。
创建3个互相引用的类
public class TestA {
private TestB testB;
public TestA(TestB testB) {
this.testB=testB;
}
public TestB getTestB() {
return testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
public class TestB {
private TestC testC;
public TestB(TestC testC) {
this.testC=testC;
}
public TestC getTestC() {
return testC;
}
public void setTestC(TestC testC) {
this.testC = testC;
}
}
public class TestC {
private TestA testA;
public TestC(TestA testA) {
this.testA=testA;
}
public TestA getTestA() {
return testA;
}
public void setTestC(TestA testA) {
this.testA = testA;
}
}
Spring循环依赖的3中情况
-
构造器循环引用:无法解决
表示通过构造器注入构成循环依赖,这种依赖无法解决,只能抛出 BeanCurrentlyInCreationException异常表示循环依赖 Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中 将一直保持这个状态,此时如果在创建bean的过程中发现自己已经在“当前创建bean池”里是时。将会抛 出BeanCurrentlyInCreationException异常表示循环依赖,而对创建完成的bean将从“当前创建 bean池”中清除掉
实例演示
<bean id="testA" class="com.lv.ref.TestA">
<constructor-arg index="0" ref="testB"></constructor-arg>
</bean>
<bean id="testB" class="com.lv.ref.TestB">
<constructor-arg index="0" ref="testC"></constructor-arg>
</bean>
<bean id="testC" class="com.lv.ref.TestC">
<constructor-arg index="0" ref="testA"></constructor-arg>
</bean>
- Spring容器在创建“testA”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testB“,并将“testA”放到“当前创建bean池”中,去创建testB
- 然后Spring容器在创建“testB”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testC“,并将“testB”放到“当前创建bean池”中,去创建testC。
- 在然后Spring容器在创建“testC”bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数”testA“,并将“testC”放到“当前创建bean池”中,去创建”testA“。
-到此位置在此回到创建”testA“,发现该bean标识符在”当前创建bean池“中,因为循环依赖,抛出
BeanCurrentlyInCreationException
2.setter循环依赖
表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖时通过Spring容器提前暴露刚完成
构造器注入但还未完成其他步骤(如setter注入)的bean来完成的,而是只能解决单例作用域的bean循环依赖
。通过提前暴露一个单例工厂方法,从而使其它bean能引用到该bean
这个方法将完成构造器调用的bean提前暴露封装成ObjectFactory到三级缓存中,之后可以直接三级缓存中通过传入beanName到ObjectFactory#getBean()中获取bean
- Spring容器创建单例”testA“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testA“标识符放到”当前创建bean池“,然后进行setter注入”testB“。
- Spring容器创建单例”testB“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testB“标识符放到”当前创建bean池“,然后进行setter注入”testC“。
- Spring容器创建单例”testC“bean,首先根据无参构造器创建bean,并暴露一个”ObjectFactory“用于返回一个提前暴露一个正在创建中的bean,并将”testC“标识符放到”当前创建bean池“,然后进行setter注入”testA“,进行注入”testA“时由于提前暴露了”ObjectFactory“工厂,从而使用它返回提前暴露一个创建中的bean。
- 最后在依赖注入”testB“,”testC“。
-
对于prototype范围的依赖处理
对于”prototype“作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存”prototype“ 作用域的bean,因此无法提前暴露一个正在创建中的bean。
对于”singleton“作用域bean,可以通过”setAllowCircularReferences(false);“来禁止循环引用
画图解决
三级缓存
debug测试
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
<bean id="a" class="com.lv.circulardependency.A">
<property name="b" ref="b"></property>
</bean>
<bean id="b" class="com.lv.circulardependency.B">
<property name="a " ref="a"></property>
</bean>
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("cd.xml");
开始debug,进入实例化bean的方法
F7进入refresh(),一直f8,到下面方法,f7进入,这是实例化和初始化bean的方法。
进入后f8到**preInstantiateSingletons()**方法f7进入
循环每一个bean,根据bean的类型是否进入if条件
A对象没有实现FactoryBean,跳出循环
跳出循环后,调用getBean()和doGetBean()获取实例
f7进入
继续f7,执行doGetBean
检测缓存中有没有A对象,有则直接使用,没有就创建调用
f7进入
缓存中没有A对象,执行createBean()和doCreateBean()创建A的实例
T getObject() {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
f7进入
T getObject() {
try {
//上面调用这个createBean
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
f7进入
找到doCreateBean。
f7进入
通过反射创建bean的过程
已经获取到实例化的A了,将半成品A提前添加到三级缓存中,给其他对象注入使用
提前暴露到三级缓存中,供循环依赖使用后,可以开始初始化A实例了(populateBean和initializeBean)
1.populateBean属性赋值,给A实例中的B属性填充属性
f7进入,f8到这个位置
f7进入
f7进入
在工厂中解决对另一个bean的引用
f7进入发现,又到了doGetBean方法
去缓存获取A中属性B的实例,如果没有创建B的实例(现在还没有,所以创建)
同样先去缓存中获取B实例
缓存中没有B实例,创建B实例完成,初始化B对象之前,将B的半成品放入三级缓存
此时的三级缓存
给A中属性B的实例开始初始化
1.populateBean赋值
给A实例中的B属性赋值,这时候发现B属性中引用了A实例,则取缓存中先获取A实例将B属性初始化(如果没有则创建,此时三级缓存中有A实例)
同理也是个引用对象,这是B中的属性A实例。
f7进入
开始解析
去缓存中获取A实例
意思就是
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
singletonObject = singletonFactory.getObject();
//其中执行getObject相当于执行了getEarlyBeanReference方法
Object getObject(){
Object exposedObject=this.getEarlyBeanReference(beanName, mbd, bean);
return exposedObject;
}
获取到半成品A并将半成品A放入二级缓存后,开始初始化B实例,给B实例中的A属性赋值
解析RuntimeBeanReference
去缓存中获取A的半成品实例,发现在二级缓存中有A实例的半成品,直接使用
f8
继续f8
继续f8
返回到初始化B完成的方法
将完成品B放入一级缓存,并删除二级和三级缓存中B的信息
获得完整的B对象后,继续初始化A,A实例已经可以在一级缓存中获取B,直接使用
将A的完整实例添加到一级缓存,并删除二级和三级缓存中数据
整个缓存的流程
首先在一级缓存中查找,然后二级缓存,最后三级缓存。
1.首先在缓存中获取A对象,发现没有,则实例化A,并将A未初始化的实例添加到三级缓存,开始初始化A,发现其中有B的引用,然后去缓存中获取B实例,没有,则开始实例化B,并将B未初始化的实例添加到三级缓存,开始初始化B实例,结果又发现B中有A的引用,则去缓存中获取A实例,此时三级缓存中有半成品A实例。
2.找到半成品A后,获取A的半成品,并将A的半成品放入二级缓存,然后删除三级缓存中的A。
3.然后,B实例可以在二级缓存拿到半成品A继续初始化,B初始化完成,将B的完成品加入一级缓存,并删除关于B的二级和三级缓存的信息
虽然三级缓存中有B的信息,但是还没有使用就被删除了
4.B实例化初始化完成后,A还是半成品,还未初始化完,继续初始化A实例。
①此时一级缓存中一级有了B的实例,A初始化可以直接使用
②初始化完成A后,将A加入一级缓存,并删除关于A的二级和三级缓存的信息。此时的A是成品,B中的A引用也变成了成品
清除二级缓存中A的属性。
到此为止,此时,只有一级缓存中有A和B的完整的实例
引出的问题
1.三级缓存解决循环依赖的关键什么,为什么可以通过提前暴露对象可以解决
关键: 实例化和初始化分开处理,在中间给其他对象赋值时并不是完整的对象,将未初始化(半成品)的对象暴露出去,给其他对象使用
半成品:完成实例化但是没有初始化。
2.只使用一级缓存可以解决循环依赖。
①不能,在整个处理过程中存在半成品和成品,如果只有一级缓存,那么半成品和成品都会放到一级缓存中,这时候获取对象时,会先从一级缓存中获取,因为成品和半成品的key相同的,可能取出半成品对象。因此要把半成品和成品的存放分开。
②既然,key相同,可以给key或value做标记来分辨半成品和成品吗?
不能,因为你遍历出来的半成品和成品key相同,你不知道哪个是半成品,哪个成品,所以无法加标记来分辨半成品和成品。而根据value标记,每次都要获取value的值在标记,明显很麻烦,不如直在定义一个缓存,来区分半成品和成品
3.只使用二级缓存,不使用三级缓存可以吗?
二级缓存和三级缓存的区别,三级调用了这个lamaba表达式
①我们可以发现,只有A对象调用了这个lamaba表达式生成了二级缓存中的半成品A,而B对象没有调用。
也就是当我们确保所有的bean都不调用这个表达式时,只使用二级缓存即可解决循环依赖。
②()->getEarlyBeanReference这个方法的作用是什么?
直译:获取早期的bean引用。
//获取用于早期访问指定bean的参考,*通常是为了解决循环参考。 * @param beanName bean的名称(用于错误处理)
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* 必要时包装给定的bean,即是否有资格被代理
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
所以使用三级缓存本质,解决aop代理问题。
4.如果某个bean对象代理对象,那么会不会创建普通对象?
所以提前创建这个对象,需要代理则用代理对象覆盖这个对象,不进行代理则使用原对象。
5 为什么使用三级缓存就可以解决aop代理的问题
当一个对象需要被代理的时候,在整个创建过程中时包含两个对象的,一个普通对象,一个代理对象,bean默认是单例的,那么整个生命周期的处理环节中,一个beanName能对应两个bean对象吗?,既然不能的吗,保证我们在使用的时候加一层判断,是否需要代理。
知道什么时候使用代理对象?
不知道什么时候回调,所以我们通过匿名内部类的形式,在使用的时候直接对普通对象覆盖,保证全局唯一性!
三级缓存都存什么
第一级缓存存的是对外暴露的对象,也就是我们应用需要用到的
第二级缓存的作用是为了处理循环依赖的对象创建问题,里面存的是半成品对象或半成品对象的代理对象
第三级缓存的作用处理存在 AOP + 循环依赖的对象创建问题,能将代理对象提前创建