当spring创建的对象A依赖于对象B,同时对象B又依赖于对象A,那么他们之间的相互依赖就会形成闭环,那么此时就会出现栈溢出异常。假设没有三级缓存,首先spring创建对象A,完成实例化后开始初始化注入属性,发现属性需要B对象,那么spring就应该先查看容器中是否含有创建好的B对象,很显然此时没有,那么spring就会转去创建B对象,当spring开始创建B对象时又发现B对象需要注入A对象,那么spring就会查看容器中是否有创建好的A对象,很显然没有(上一个A对象还在创建着呢)所有又会去创建A对象......如此循环往复。
spring如何使用三级缓存完成闭环依赖对象的创建呢?
基础准备
类A
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
类B
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="a" class="com.ling.spring5.domain.A">
<property name="b" ref="b"></property>
</bean>
<bean id="b" class="com.ling.spring5.domain.B">
<property name="a" ref="a"></property>
</bean>
</beans>
测试
public void test18(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean4.xml");
A a = ac.getBean(A.class);
B b = ac.getBean(B.class);
System.out.println(a);
System.out.println(b);
System.out.println(a.getB());
System.out.println(b.getA());
}
开始debug
直接快进到创建对象finishBeanFactoryInitialization(beanFactory);
beanFactory.preInstantiateSingletons();
this.getBean(beanName);
return doGetBean(name, null, null, false);
此时位于doGetBean中,我在学习过spring的工作流程之后可以知道,doGetBean就是用来获取bean对象的方法,具体步骤如下:首先getBean-->doGetBean-->createBean-->doCreateBean
进入后重点来了,此时还没有创建对象,我们看看在createBean之前spring做了什么,目光聚集到getSingleton方法,进入。
继续进入
这个方法就尤为重要了,首先spring从一级缓存singletonObjects集合中拿出a对象,判断a对象是否为空,如果为空那么在判断a对象是否在创建中。我们知道此时我们还没有开始创建a对象,所以a对象时不可能在创建中的。所以方法直接return了
回到上一个方法,后面他使用了匿名内部类的形式创建了一个对象,并且将当前对象,a的名称,a的beanName和BeanDefinition都传入了进去,在getSingetlon中又调用了该匿名内部类的getObject方法,也就是Lambda表达式(特殊的匿名内部类,必须是接口,且该接口只需重写一个方法),所以在getSingleton方法中调用getObject方法时,又会回到Lambda表达式这里,开始执行createBean方法。
进入createBean开始创建A对象
doCreateBean
doCreateBean方法中前半部分都是创建对象的,所以我就略过了,那部分已经看了很多次了,直接看注入属性
在方法的最后有个applyPropertyValues(beanName, mbd, bw, pvs);
获取属性list
开始注入属性,a对象只有一个属性值,那就是b
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
来到1699行,本行是注入属性中的重要行,他的方法直译一下就是resolveValueIfNecessary如果可能的话就解决属性值的问题
观察控制台中的pv属性和originalValue属性,发现都是与a的属性b相关的,那么进入这个方法来一探究竟。
很明显,originalValue是RuntimeBeanReference类型的,方法第二步是类型转换,那么重点内容就是返回的时候调用的方法。
该方法的330行,出现了通过BeanFactory获取对象的,那么这句话的意图就十分明显了,这步就是获取b对象的操作,点进去发现,又是getBean。
现在又开始重复调用doGetBean方法了,甚至传参都一样的,回忆一下doGetBean方法,方法进入时会首先调用getSingleton方法,从一二三级缓存中获取,如果获取不到,那么就创建对象,所以此时对于对象b来说,和a对象创建一样的。又是getBean-->doGetBean-->createBean-->doCreateBean
最后在doCreateBean中完成层对象的创建final Object bean = instanceWrapper.getWrappedInstance();此时的bean就是b对象,那么后面就是给b对象赋属性了
在注入属性前先把b对象的转化为匿名内部类的lambda对象,然后同样的存入三级缓存中。
同样的向下进入pupolate方法的applyPropertyValues方法,同样的开始注入属性,调用resolveValueIfNecessary方法,开始向b对象中注入a属性。
进入return的方法
这是第二次调用BeanFactory的getBean方法。第一次是a对象注入b对象时调用了该方法。
进入后又是doGetBean方法,这就是第三次调用doGetBean方法了,第一次是获取a对象时调用,第二次是a需要注入b时调用。本次是b需要注入a时调用
此时的三级缓存是有东西的,所以getSingleton方法的结局已经不同了
进去 看看到底哪里不一样了。
进入后首先从一级缓存中去取名称为a的键对应的value值,然后发现if中一级缓存没取到,同时a正在创建中(a实力化好了之后注入b,发现没有b,那么创建b,开始在b中注入a,才到了这一步,所以a还正在初始化过程中)
接着向下,这里从三级缓存中确实取出了一个东西
看控制台,取出的正是在初始化a的时候传入的匿名内部类对象,而Lambda表达式的匿名内部类重写了父接口唯一的getObject方法,该方法将匿名内部类对象转化为了真正的A对象,那么后面就是把这个真正实例化后的a注入b对象中,同时把a放入二级缓存(这是a对象的半成品)然后除去三级缓存中的匿名内部类对象。
回到注入属性的applyPropertyValues方法,此时已经拿到了注入属性所必要的a对象,一直向下,直到发现setPropertyValues方法,经过此方法后,b对象的a属性就被赋值成功了
赋值成功后,我们可以展开想想了,赋值成功后放哪呢?三级缓存存储了创建半成品对象用的Lambda表达式匿名内部类对象,二级缓存存储了半成品对象,而一级缓存至今没有使用过,那一级缓存是不是将要存放完成品对象呢?
继续往后,回到a为了注入b而调用的创建b的doCreateBean方法中 ,这不是doGetBean中啊,这是doCreateBean中,doGetBean中调用的是getSingleton(String),然后在此方法中调用重载的getSingleton(String,boolean),从二级缓存中获取b,而此时b只有在三级缓存中才存在,那么肯定是获取不到的,返回null。
随后他将完成品的bean拿去注册了,难道要放在一级缓存了吗?我们进去看看。
发现没有,虚惊一场,来到了返回。一直返回来到初始化a时获取b的doGetBean方法,此时正在获取单实例b,getSingleton方法中,刚刚的一系列操作都发生在匿名内部类的getObject方法中
最后我们看到了addSingleton
进入该方法后发现,他把完成品b放在了一级缓存中并且清空了二级和三级缓存
方法执行完回到初始化a时的doGetBean,此时a拿到了彻底完成初始化后的b,也就是sharedInstance对象,并将其加工后返回
一路返回。
方法终于回到了a在注入属性时调用的applyPropertyValues方法中,此时刚执行完 解决属性注入 方法。
此时已经拿到了b完整对象的值
最后来到熟悉的setPropertyValues方法。
执行前:
执行后:
a对象也完成了赋值,循环依赖问题解决完成。
现在来总结一下所扒源码的具体流程,首先容器启动,开始创建a对象,循环遍历需要创建的名单,拿到了第一个beanName:a开始调用getBean方法创建a,getBean中调用doGetBean完成整体创建,doGetBean中首先调用getSingleton方法,当一级缓存(完成品集合)中存在了就直接获取a,显然没有。那么此时调用getSingleton方法的目的就结束了(此时只是看看一级缓存中有没有a对象,至于二三级怎么样完全可以不看)。没取到a对象,那么就开始创建a对象,创建a对象同样用到了getSingleton方法,此方法不是上文重载的从缓存中查找a对象的方法,而是创建a对象的方法,方法传入了两个值,前者是需要创建的bean的名称,也就是a,后面是ObjectFactory<?>接口的匿名内部类对象(该对象重写了getObject方法,里面加装了createBean的操作)。getSingleton中调用了匿名内部类重写的getObject方法,方法中调用的createBean方法,createBean方法中调用了doCreateBean方法,此方法中的getWrappedInstance方法完成了bean的创建实例化。此时a已经被实例化了,只是还没有属性。此后spring想把半成品对象a加入到三级缓存中,于是就调用了addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)方法,传入了匿名内部类(同样是ObjectFactory<?>接口的实现类,该匿名内部类在getObject方法中调用了 getEarlyBeanReference)将该匿名内部类存在了三级缓存中。(匿名内部类的getEarlyBeanReference方法用于获取对应的真实对象)。
然后开始populateBean注入b属性。populateBean调用了applyPropertyValues方法。注入b时发现b对象不存在,那么就resolveValueIfNecessary解决b不存在问题,resolveValueIfNecessary中调用了resolveReference,在该方法中调用了BeanFactory对象的getBean方法,这个方法中又像刚创建a时一样,调用了doGetBean方法,然后就是同样重复的操作。
重复创建b后,b的Lambda对象也像a的Lambd对象一样,放到了三级缓存中,此时开始为b的a属性注入依赖。此时有来到注入属性时所需要的resolveValueIfNecessary来获取属性对象,resolveValueIfNecessary中调用了resolveReference,在该方法中调用了BeanFactory对象的getBean方法,这个方法中又像刚创建a时一样,调用了doGetBean方法,然后就是同样重复的操作。
此时理一理思路,为什么我们有来到了getBean。第一次是创建a的时候我们路过了getBean,第二次是a注入属性b的时候,调用了getBean获取b,此时是第三次,由于没获取b,在创建b的时候注入a时又使用了doGetBean。
第三次进入doGetBean后一切变得不一样了。首先就是getSingleton,此时三级缓存中已经有了两个对象所以getSingleton是可以获取到与a对象有关的匿名内部类对象的。获取了匿名内部类对象后调用了getObject方法,重写后的getObject方法中只有一个getEarlyBeanReference方法,此方法就是用于获取真正的a对象,getSingleton方法中取出了三级缓存的a匿名内部类,转化为了a半成品对象,将其作为返回值,并且将a的半成品对象放到了二级缓存中。拿到bean后一路返回,回到了给b注入属性的applyPropertyValues方法中,调用了最后的setPropertyValues完成了a的注入。
调用完毕后带着完整的b对象一路返回,返回到createBean创建b,此时b已经完成了创建,addSingleton随后使用addSingleton,将b放入一级缓存,放入后带着完成品b一路返回。返回到a注入b时的applyPropertyValues调用了最后的setPropertyValues完成了b的注入。