在之前的博客中我们已经多次提到了循环引用,但是都没有细讲,就是希望可以单独拿出来看一下。下面就开始吧!
首先创建了循环引用的三个类,他们的引用关系如下图:
在客户端调用效果如下图:
开始啃源码吧!之前讲过的代码我们就不贴,只放我们之前没讲的代码,其他代码一带而过,直接到bean实例化后填充属性:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
//里面有一个classRoom属性值,classRoom还没有实例化
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
...
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
在这我们还要注意一点,当一个bean实例化完,还没有填充属性的时候,它会被放到singletonFactories这个三级缓存中,在我看来,主要是为了解决循环引用问题,通过分析后面的代码我们会更加清晰这样做的目的。
这个方法里面我们关注的只有上面这些代码,一个是获取要实例化的bean的要填充的属性值,另外就是开始填充属性,在这里,只有一个属性要填充,就是classRoom,但是他还没有被实例化,我们看接下来是怎么处理的。
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
//判断有没有需要填充的属性
if (pvs.isEmpty()) {
return;
}
MutablePropertyValues mpvs = null;
List original;
...
if (pvs instanceof MutablePropertyValues) {
mpvs = (MutablePropertyValues) pvs;
//需要填充的属性有没有被转换成指定的类型
if (mpvs.isConverted()) {
try {
bw.setPropertyValues(mpvs);
return;
}
catch (BeansException ex) {
...
}
}
//属性值没有被转换,需要进行类型转换
original = mpvs.getPropertyValueList();
}
else {
original = Arrays.asList(pvs.getPropertyValues());
}
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
//得到一个类型转换器,在本文要讲的循环引用的解决他是核心
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
// 实例化一个属性列表,用来保存转换完成的属性值
List deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
//如果当前属性被转换完成了直接添加进去
if (pv.isConverted()) {
deepCopy.add(pv);
}
else {
//得到属性名称
String propertyName = pv.getName();
//得到属性的原始值
Object originalValue = pv.getValue();
//到这重头戏开始了,由于给student的属性classRoom还没有实例化,所以要开始处理classRoom了
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
...
}
代码执行到这个位置,要填充classRoom属性了,可是classRoom还没有初始化,那怎么办呢?我们进入这个方法resolveValueIfNecessary():
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
// 当前的value是运行时bean的引用
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
//开始处理这个引用
return resolveReference(argName, ref);
}
...
}
这个方法里面的代码很长,但是我们现在只关心开头的这段代码,因为只有这段代码是处理bean实例注入的,剩下的代码也无非是针对不同类型的属性值进行处理,继续进入resolveReference():
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
Object bean;
//得到引用对象的名称
String refName = ref.getBeanName();
refName = String.valueOf(doEvaluate(refName));
...
else {
//获得要注入的bean的实例
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
catch (BeansException ex) {
...
}
}
这个方法的逻辑很简单,但同时像有魔力一样把我们吸引到了这行代码:
bean = this.beanFactory.getBean(refName);
这不就是获得要注入的bean的实例么,想想也是,我们要注入一个bean,肯定要先获取他,并且有现成的方法干嘛不用,从这开始,我们踏上了获取classRoom实例的路途:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
...
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
return (T) bean;
}
很显然,classRoom的bean实例Spring也还没有创建,所有也要走同样的流程:通过反射实例化bean,将bean放到singletonFactories三级缓存中,填充属性。问题又来了,classRoom中一共有两个属性啊,一个是name,这个好处理,直接赋值就好了,另外一个teacher,这也是一个bean啊,也没有被创建,得了,再来一遍吧:
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
Object bean;
String refName = ref.getBeanName();
refName = String.valueOf(doEvaluate(refName));
...
else {
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
catch (BeansException ex) {
...
}
}
不出所料,又来到这里了,继续吧
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
...
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
return (T) bean;
}
又执行到这里了,再走一遍流程:通过反射实例化bean,将bean放到singletonFactories三级缓存中,填充属性。这次就有点区别了,teacher里面也有两个属性,一个是name,可以通过直接赋值来填充,另一个是student,一个bean实例,这个我们可是一开始就创建了的,只不过没创建完而已,但我们依然有它的引用,就放在三级缓存singletonFactories中,接下来的代码,就是Spring解决循环依赖的过程了:
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
Object bean;
String refName = ref.getBeanName();
refName = String.valueOf(doEvaluate(refName));
...
else {
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
catch (BeansException ex) {
...
}
}
再一次执行到这里,但这和之前有点不一样了,我们是可以直接获取到需要的bean的,我们看一下Spring具体是怎么来处理的:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先从一级缓存中查找
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//没有获取到从二级缓存中查找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//还是没有再从三级缓存中查找
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//查找到之后获得单例对象
singletonObject = singletonFactory.getObject();
//并将这个bean从三级缓存中升级到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
一个bean的创建分三步,实例化,填充属性,初始化,Spring是在第一步完成的时候就把它放到了三级缓存中了,提前曝光,在这个地方我们先知道Spring在实例化之后的处理有很大的用处,解决了循环依赖,到结尾我们在总结。
到这,代码递归执行被终结了,接下来就要一步一步的倒退了:
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
这一步就要对teacher的属性进行填充了,因为所有的属性值都准备好了,填充完属性之后,要对bean进行初始化,主要是一些init方法等等…
还记得我们在实例化结束的时候把bean放到了三级缓存中了,那要怎么从里面去除呢:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//添加到一级缓存中
this.singletonObjects.put(beanName, singletonObject);
//从三级缓存中移除
this.singletonFactories.remove(beanName);
//从二级缓存中移除
this.earlySingletonObjects.remove(beanName);
//记录已经被实例化的bean的名字
this.registeredSingletons.add(beanName);
}
}
由于单例创建完成之后要放在一级缓存中,而在Spring中,一个bean只能放到其中一个缓存中,所以在放入一级缓存中的时候,移除了其他缓存中的记录。
到这teacher就创建结束了,这还没有结束,还有一些后续处理:
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
...
else {
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
if (bean instanceof NullBean) {
bean = null;
}
return bean;
}
...
}
由于teacher中存在引用的注入,所以还要在IOC容器中记录它依赖了哪些引用,到这teacher就创建结束了,还剩下classRoom、student,现在teacher创建完毕,classRoom所需要的属性也就齐了,classRoom创建完成,student所需要的属性也就齐全了,剩下的都是和teacher一样的流程,在这就不在记录了。
我们总结一下Spring是如何处理循环依赖的:
在一个单例bean创建的时候,Spring就把它当作存在循环依赖来处理,就是在一个bean实例化结束之后放到三级缓存中提前曝光,让其他的依赖他的bean可以获得到还没有创建完成的它。当在填充属性的时候如果发现有一个需要注入的属性值是一个引用值,在类型转换的时候就会在IOC容器中查找这个引用的bean,没有就先创建它,就这样一直递归着执行下去。由于是循环依赖,就不可能有任何一个bean可以直接创建完,所以只能先把还没有填充属性的bean的引用赋给当前bean,Spring很巧妙的用了三级缓存来保留住了这个引用。从这也可以看出,Spring中不能存在构造方法中的循环引用,因为,还没创建要注入的属性值,无法赋值。