死磕源码:2.spring如何解决bean的循环依赖问题
前言
循环依赖这个问题虽然不提倡大家在编程的过程中设计这样的编码结构,但是有些时候可能还是会不可避免地出现这样地问题,那么循环依赖有哪几种形势呢?spring又是怎样解决这个问题的呢?
什么叫循环依赖?
所谓循环依赖,就是你中有我,我中有你。什么叫依赖,就是你要完成一个动作需要其他得东西来进行辅助完成,这个关系就叫依赖,可以用如下得UML图来表示。
如上图,A依赖B,B依赖C,C又依赖A。可以看到这个一个很操蛋,而且很麻烦得问题,当然在编码中我们是不提倡这样得结构设计得,但是计划赶不上变化,有些十分复杂得场景下,这种情况还是有可能发生得。
循环依赖的种类
构造器循环依赖
这就是一个典型的构造器循环依赖的代码
public class A {
public A(B b) {}
}
public class B {
public B(A a) {}
}
然后再编写spring的配置文件
<bean id="a" class="com.simple.demo.bean.A">
<constructor-arg name="b" ref="b"/>
</bean>
<bean id="b" class="com.simple.demo.bean.B">
<constructor-arg name="a" ref="a"/>
</bean>
可以与想得到,这种循环依赖是绝对不允许的,你想想,创建A的时候需要先创建B,创建B又要先创建A,这就变成一个死循环了。当我们执行这个程序尝试获取a或者b的时候,毫无疑问程序报错了
那么spring是怎么样的一个检测机制呢,它是怎样发现构造器循环依赖问题的呢?从打印出的错误,我们可以看到他直接报错的地方在DefaultSingletonBeanRegistry的beforeSingletonCreation中
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans currently excluded from in creation checks. */
private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
...
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
这里又涉及到两个集合,inCreationCheckExclusions按照注释就是被排除掉的正在创建检查的beanName的集合,singletonsCurrentlyInCreation这个集合前边接触的比较多了,就是创建bean之前就会先标记这个bean已被创建了,会在这个集合中有记录。inCreationCheckExclusions这个集合目前遇到的比较少,暂时还没有找到用到的地方,因该是针对某种特殊处理的,spring框架过于复杂,这个集合暂时放在这儿,不影响我们研究spring的主要流程。这个检查的意思,就是当这个bean没有被排除检查而且它当前处于正在创建的状态,那么表明产生了构造器循环依赖了,这句话比较绕,大家好好琢磨一下。那么这个检查在哪里生效的呢?它是怎么样的一个检查过程呢?首先来说初次调用getBean(“a”)会有下边这样的流程
具体代码大家可以从AbstractAutowireCapableBeanFactory.doCreateBean往进找,this.createBeanInstance(beanName, mbd, args)这个方法就是通过不同的策略创建一个bean实例,在这个方法中跳转到了this.autowireConstructor(beanName, mbd, ctors, args)方法中,然后呢会调用this.resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues)这个方法,这个方法里边就是解析构造器参数的代码,然后又跳转到valueResolver.resolveValueIfNecessary(“constructor argument”, valueHolder.getValue())方法中,在这个方法中可以看到value又好几种类型,后边会做一下详细的总结,而b是一个引用类型也就是RuntimeBeanReference类型,然后我们就可以看到了,这里调用了什么方法呢?
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
...
if (ref.isToParent()) {
...
bean = this.beanFactory.getParentBeanFactory().getBean(refName);
} else {
bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
}
...
return bean;
} catch (BeansException var5) {
throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName, "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, var5);
}
}
别的代码咱也不看了,就看这里this.beanFactory.getParentBeanFactory().getBean(refName)和this.beanFactory.getBean(refName),是不是又走了一遍创建和获取b实例的流程。按照这个思路,因为B的构造器中也有引用A的依赖,那么创建B的之前又要创建A,那么继续去创建A,这个时候这个创建A的流程就会被这个判断给拦截掉了,为什么呢? !this.singletonsCurrentlyInCreation.add(beanName),这句代码大家注意到了吗,第一创建A的时候,就会放到这个里边标记A正在被创建,当你再一次来的时候,发现这个集合中已经有了,是不是表明有循环依赖了,依靠这种机制,spring检测出了构造器的循环依赖问题。
单例的property循环依赖
<bean id="userZ" name="zhangsan" class="com.simple.demo.bean.User">
<property name="name" value="张三"/>
<property name="age" value="19"/>
<property name="parent" ref="parent"/>
</bean>
<bean id="parent" class="com.simple.demo.bean.Parent">
<property name="name" value="张三的爸爸"/>
<property name="user" ref="userZ"/>
</bean>
这段代码就是一段典型的property循环依赖的例子,首先申明一下spring是不解决原型bean的property的循环依赖的,因为这个也和构造器上的循环依赖一样基本是个死循环,那么spring是怎么解决单例bean的循环依赖的呢?首先我们首次调用getBean(“userZ”)的时候,没有创建这个bean那么是要走创建bean的流程的,上一章节讲过这个基本流程,也强调过下边这串代码是解决spring单例bean循环依赖的关键
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
...
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
这段代码是spring根据不同策略创建了bean的实例之后,在没有完成bean的初始化之前调用的,在addSingletonFactory这个方法中,spring将这个bean塞到了singletonFactories这个集合中,这个集合有什么用呢?大家在回顾一下获取单例bean的一个关键方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
原来singletonFactories这个集合在这个地方用到了呀,之前可能大家对这个集合可能还有点迷糊,不知道有什么用,大家先弄清楚了为什么吗?回到前边的讨论,我们来梳理一下。首先,我们通过getBean(“userZ”)来获取userZ这个bean,当创建完成userZ这个bean的实例之后(注意这个创建完只是说创建完成这个对象,而这个对象的初始化操作还没有完成),然后将userZ提前暴露到singletonFactories这个集合中,不过这里暴漏的是一个ObjectFactory。然后就会去初始化userZ的属性,发现有一个属性叫parent,这是一个引用类型,引用了一个单例bean,然后spring去从缓存中找这个单例bean,缓存中没找到,那么就开始创建parent,然后在初始化parent的又发现了parent引用了userZ这个bean,再去调用getSingleton来找userZ,这个时候我们就可以发现,虽然userZ没有完全创建成功,但是由于之前spring提前将userZ暴露到singletonFactories,那这个时候就可以先获取到userZ让parent先引用,然后parent就可以创建成功,创建成功,然后userZ初始化完成parent这个属性,over。