IoC循环依赖

什么是循环依赖

  循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。

img.png

注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。

  Spring中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)
  • scope为prototype原型的bean循环依赖(⽆法解决):对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都会直接报错处理。

  其中,构造器的循环依赖问题⽆法解决,只能拋出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。

  构造器的循环依赖问题⽆法解决的原因是:Bean加载流程需要先实例化,如果构造器就有依赖会实例化失败,创建流程直接就结束了;

解决方案

prototype 原型

  通过查看AbstractBeanFactory类中的doGetBean()方法,可以发现源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
.......

// 如果这个原型bean正在被创建则直接抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

.........
.......
// scope为prototype类型
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

.......
.......

return (T) bean;
}

  通过源码可知,在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记

Spring 不⽀持原型 bean 的循环依赖。

singleton 单例

  单例bean通过setXxx或者@Autowired产生循环依赖时,其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。

  通过Bean加载源码解析中可以知道,Bean创建流程最终到达AbstractAutowireCapableBeanFactory类中doCreateBean()方法中,其中populateBean()方法就是处理属性的自动注入的,相关源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
.........
PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// 获取需要注入的bean
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
}
if (needsDepCheck) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
checkDependencies(beanName, mbd, filteredPds, pvs);
}
........
}

属性注入流程的时序图如下所示:
populateBean.png

而解决在属性注入时的循环依赖,Spring用到了三级缓存:

  • singletonObjects:一级缓存,存放的是完成创建的单例对象;
  • earlySingletonObjects:二级缓存,存放的是中间态的对象;
  • singletonFactories:三级缓存,存放的是工厂对象;

doGetBean方法中,可以看到缓存的具体使用逻辑:

getBean.png

其中,getSingleton方法源码如下,其中使用到了三级缓存:

getSingleton.png

其次,getObjectForBeanInstance方法主要是判断是否是工厂Bean(FactoryBean)

其次,在创建Bean的时候,会将Bean主动存入缓存中,源码如下:

createBean.png

其中,放入缓存的源码如下:

bean_cache.png

  可以看到放入了三级缓存,且放入的是一个lambda表达式addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));,当获取时调用getObject方法时,其实就是执行了getEarlyBeanReference方法。

img.png

  由此可见,在解决循环依赖的时候,会把需要执行的后置处理逻辑都执行完,这样在有AOP逻辑时,会在此时生成代理对象。