前
从源码说一下spring解决循环依赖的问题
正
循环依赖
循环依赖,在spring中比较好理解,就是不同bean之间的依赖关系形成环,例如A依赖B,B依赖C,C又依赖A,按照正常的注入,就形成了死循环,而spring为这种问题提供了解决方案。值得注意的是,构造器注入是无法解决的,这里只是解决了field的注入方式,同时,也只能解决单例bean的构造,其他作用域的无法解决。至于为什么,会在下面解释。
源码
spring解决循环依赖的入口如何找,很好理解,我们是要获取一个bean,所以就在获取bean的地方,找到加载bean的入口AbstractBeanFactory#doGetBean
.
在这个方法中,在获取beanName后的第一步,就是从缓存中获取bean,这个缓存其实就是spring存放bean实例的地方。
找到DefaultSingletonBeanRegistry#getSingleton
,这是从缓存中获取bean的方法
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从单例缓冲中加载 bean
Object singletonObject = this.singletonObjects.get(beanName);
// 缓存中的 bean 为空,且当前 bean 正在创建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 加锁
synchronized (this.singletonObjects) {
// 从 earlySingletonObjects 获取
singletonObject = this.earlySingletonObjects.get(beanName);
// earlySingletonObjects 中没有,且允许提前创建
if (singletonObject == null && allowEarlyReference) {
// 从 singletonFactories 中获取对应的 ObjectFactory
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获得 bean
singletonObject = singletonFactory.getObject();
// 添加 bean 到 earlySingletonObjects 中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从 singletonFactories 中移除对应的 ObjectFactory
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
首先是singletonObjects
这个类变量是一个map,存放的是beanName->单例bean实例的缓存,然后如果在这个缓存中有实例,则直接返回。
如果一级缓存中没有的话,并且这个bean正在创建,然后锁住一级缓存(保证只有一个线程进入即可),然后从二级缓存中获取bean实例。
这个二级缓存是核心,变量名是earlySingletonObjects
,提前的单例实例,顾名思义,就是提前暴露的单例实例,什么叫提交暴露呢,很简单,就是完成了bean的构造,但是没有进行属性注入和初始化,这个可以记一下,下面详细介绍,(标记1)
然后如果从二级缓存中也没有,并且允许提交创建,则从三级缓存中获取,三级缓存是singletonFactories
,看名字是单例bean的工厂类,如果从三级缓存中拿到了,就从工厂类中拿到实例,放到2级缓存,删除三级缓存中的bean工厂。这里比较疑惑的点是,为什么要用三级缓存,看代码只是从工厂中拿到bean实例,那么只用二级缓存或者只用三级缓存不行吗,这个也记下,下面详细介绍,(标记2)
标记1
下面介绍下标记1的问题,找到三级缓存的来源处,可以看下面3段代码
boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式
&& this.allowCircularReferences // 运行循环依赖
&& isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 是否正在被创建
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 提前将创建的 bean 实例加入到 singletonFactories 中
addSingletonFactory(beanName, () -> 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);
}
}
}
......
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
首先第一段代码是创建bean的时候的内容,主要的是它的位置,它位于AbstractAutowireCapableBeanFactory.doCreateBean
方法中,前面是createBeanInstance,后面是populationBean和initializeBean,了解bean创建流程的就知道了,这个位置就是刚根据构造器创建了bean实例,但是还没有把依赖注入,也没有初始化bean。
然后看第二段代码,第二段代码是将提交暴露的bean工厂加入到三级缓存中,没有特别的
最后看第三段代码,是生成提前暴露的bean工厂。
这下应该解释了提前暴露的问题
标记2
然后说标记2的问题,初步看下来,感觉二级缓存和三级缓存是有一点冲突的,这个就和spring的重要特性aop有关了,我们知道spring-aop的实现就是生成代理类,不管用动态代理还是cglib,都是生成一个代理类,那么提前暴露bean,万一这个bean是有代理的怎么办。只有两种办法,要不就是提前暴露bean的时候,就生成代理后的bean,但是循环依赖毕竟是少部分情况,而且也不是很推荐,所以没有必要,还有一种就是发生循环依赖的时候,我们再生成代理类。而spring实现的方式就是三级缓存,用工厂类来封装,如果发生了循环依赖,就调用工厂类的工厂方法,获取代理bean,也即是上面第三段代码的部分。
而spring选择第二种方法的原因,想来应该也是和设计原则之类的原因,毕竟两种方案其实差不太多,spring启动本来就很慢了,不差那几秒钟。
总结
循环依赖的解决看下来简单,但那是从代码来看的,如果从设计角度,还是很巧妙的,也很值得见鉴。
而一开始说的两种情况,一个是构造器的无法使用,那是因为添加提前暴露bean在构造之后,还有就是原型作用域的无法使用,因为原型作用域的bean不受spring管控,spring也没有加缓存。这两种情况发生循环依赖都会直接报错,启动失败。