Spring源码分析——解决循环依赖
一、循环依赖
什么是循环依赖?
存在类A实例Bean、类B实例Bean、类C实例Bean,在类A中引用了类B,类B中引用了类C,同时类C中引用了类A,此时使用Spring初始化类A、B、C便会存在循环引用的情况。当实例化A时发现其引用了B实例;于时对B进行实例化,实例化B时又对C实例进行引用;于时再实例化C,实例化C时,又引用了A实例,而此时A还在等待B初始化完成,因此出现死循环的现象。
二、循环依赖解决
1.三级缓存
在Spring中解决循环引用的方式是使用三级缓存策略,分别如下
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
- 一级缓存:singletonObjects,用于缓存单例对象实例,这里存放的是已经实例完的对象Bean
- 二级缓存:earlySingletonObjects,用于存放早期实例的单例对象,这里存放的是正在创建实例,且还没实例完成的Bean
- 三级缓存:singletonFactories,用于缓存单例工厂
2.循环依赖解决过程
(1)缓存添加过程
单例模式下,第一次获取Bean时由于Bean示例还未实例化,因此会先创建Bean然后放入缓存中,以后再次调用获取Bean方法将直接从缓存中获取,不再重新创建。Spring中,获取Bean的流程:
缓存添加过程主要发生在创建Bean也就是doCreateBean()过程中。doCreateBean()中有这样一句代码
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);
}
}
}
创建Bean主要经历了一下三个过程:
- createBeanInstance:通过构造方法创建实例
- populateBean:填充属性
- initializeBean:调用init()方法初始化Bean
从代码中可以看到在这里将beanName放入三级缓存中,并且从二级缓存中移除。这里的addSingletonFactory发生在createBeanInstance之后,此时已经有了实例对象,但是还没创建完成便放入三级缓存当中。在doCreateBean()方法执行完成之后,Bean实例创建完成,因此在第二个getSingleton()中的finally块中如果是新的单例对象则会调用addSingleton()方法。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
可以看到此时将加入一级缓存中,并且从二级、三级缓存中移除。
(2)获取缓存过程
缓存的获取是通过getSingleton(String beanName)方法获取的,其源码如下:
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();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
在代码中,首先从一级缓存singletonObjects中获取Bean;如果获取不到并且获取的Bean被标记为正在创建中,则从二级缓存earlySingletonObjects中获取;如果二级缓存中依然获取不到Bean,并且允许从三级换从中获取Bean,则从三级缓存中获取Bean,此时如果获取到了Bean,则将该Bean从三级缓存中移除,然后添加进二级缓存(缓存升级),否则返回null。
3.三级缓存解决循环依赖流程
通过使用三级缓存可以解决循环依赖的问题,以A、B、C相互依赖的情况为例,其解决循环依赖流程如下:
- 创建A对象,执行createInstance之后将对象放入三级缓存singletonFactories中提前曝光自己,并继续执行
- 执行populateBean为A对象填充属性,此时发现A依赖了B对象,于是通过getBean获取B对象,此时B还没创建,便会执行创建B的Bean对象流程
- 执行createInstance创建B对象,并放入三级缓存singletonFactories提前曝光
- 执行populateBean为B对象填充属性时,发现B对象依赖了C对象,于是getBean获取C对象
- 此时C对象未创建,于是执行创建C对象Bean过程,在填充C时发现C依赖A对象,于是通过getBean获取对象A
- 此时A对象并未初始化完成,仅仅是放入三级缓存中,在getSingleton时从三级缓存中取出A的ObjecFactory来获取对象,并且将A对象进行缓存升级,从二级缓存放入三级缓存,此时C也拿到了A对象实例
- A缓存从三级升到了二级,在Bean创建完成之后会通过addSingleton将缓存从二级再升到三级中。B、C流程相同
三、总结
- 当两个Bean相互引用依赖的情况称为循环引用,Spring使用三级缓存的方式解决循环依赖
- 循环依赖发生在创建Bean的过程中,具体发生在doCreateBean方法中调用createBeanInstance和populateBean方法时
- 由于在createBeanInstance时用构造方法提前曝光实例,之后再将实例放入三级缓存中,因此Spring不能解决构造函数中的循环依赖问题
- 在使用getSingleton获取Bean实例时,如果对象在三级缓存中,则会从三级缓存移除添加到二级缓存;如果在二级缓存中,则会从二级缓存中移除添加到一级缓存中,以达到缓存升级的目的
- 在三个级别缓存中,二级缓存和三级缓存从代码上看是互斥的,实例对象不能同时存在于二三级缓存当中