什么是循环依赖
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,最终会导致内存溢出错误,除非有终结条件。
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。
Spring怎么解决循环依赖
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。
Spring的单例对象的初始化主要分为三步:
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:调用spring xml中的init 方法。
从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二部。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
三级缓存是哪三级?
首先我们看源码,三级缓存主要指:
1 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { 2 /** Cache of singleton objects: bean name --> bean instance */ 3 //单例bean的cache 一级缓存 存的是Bean(Bean生命周期完成之后,被spring容器管理的对象) 4 private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); 5 /** Cache of singleton factories: bean name --> ObjectFactory */ 6 //单例对象工厂的cache 二级缓存 存的是工厂 (循环依赖存放,用于Aop,在工厂中对象被创建时可以做其他的事情) 7 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); 8 /** Cache of early singleton objects: bean name --> bean instance */ 9 //提前暴光的单例对象的Cache 三级缓存 存的是对象(new Object()) (二级缓存工厂创建一个对象之后,存入三级缓存) 10 private final Map<String, Object> earlySingletonObjects = new HashMap(16); 11 }
创建bean的时候,首先是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:
//DefaultSingletonBeanRegistry protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从 单例对象中获取bean Object singletonObject = this.singletonObjects.get(beanName);
// 获取到对象为空 并且当前对象正在创建中(new 对象(完成)) if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { Map var4 = this.singletonObjects; synchronized(this.singletonObjects) {
//从earlySingletonObjects中获取 三级缓存 singletonObject = this.earlySingletonObjects.get(beanName);
//获取不到且允许singletonFactories通过getObject()获取,就从二级缓存singletonFactory.getObject()(二级缓存)获取 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; }
上面的代码需要解释两个方法:
isSingletonCurrentlyInCreation()
判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。
allowEarlyReference
是否允许从singletonFactories中通过getObject拿到对象
整个过程如下:
获取到了则:
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
如何解决循环依赖的?
从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个二级cache。这个cache的类型是ObjectFactory,定义如下:
public interface ObjectFactory<T> { T getObject() throws BeansException; }
这个接口在下面被引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); Map var3 = this.singletonObjects; synchronized(this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
如A引用B ,B引用A:
- A对象初始化,提前暴露对象ObjectFactory到 "当前正在创建的bean池->singletonFactories" 三级缓存
- A对象开始第二步设置属性,发现自己依赖对象B,尝试singletonObjects.get(B),没有获取到对象B,创建对象B,ObjectFactory并放入到“当前正在创建的bean池” 三级缓存
- 对象B开始设置自己的属性,发现依赖了A对象,开始singletonObjects.get(A),没有获取到,因为A对象才创建了第一步,二和三还没有走,尝试从earlySingletonObjects(二级缓存),没有获取到,尝试从三级缓存获取,得到对象(虽然A没有初始化完成,但是能得到对象:对象引用能定位到堆中的对象)(并从三级对象移除,放入到二级对象中此时A在二级缓存中),此时B对象初始化完成,将自己存入一级缓存中
- A此时从earlySingletonObjects(二级缓存)得到对象B,完成自己的二,三步
为什么A的构造函数中不能依赖对象B?
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
为什么要将二级缓存remove并且放入到三级缓存?
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { Map var4 = this.singletonObjects; 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(); //问题1为什么要从二级缓存删除 放入三级缓存? this.earlySingletonObjects.put(beanName, singletonObject); //问题2 为什么要删除? this.singletonFactories.remove(beanName); } } } } return singletonObject; }
问题1:解决性能问题:二级缓存的value是一个创建Bean的工厂,工厂创建bean肯定是耗时的,如果当前的Bean被很多地方循环依赖了,那么无需多次通过工厂创建对象,如果当前对象已经
被工厂创建过了,就从二级缓存移除放入三级缓存,下次再有该bean的获取,就可以直接从三级缓存里面获取了,符合源码中的从三级缓存获取(
singletonObject = this.earlySingletonObjects.get(beanName);
)
问题2:其实三级缓存的最终目的的都是保存同一个对象,所以三个缓存里面不可能都存放一样的对象,既然已经存放到三级缓存了,二级缓存也没有必要了,从map里面remove是为了GC。
一级缓存(对象Bean)的存放
protected void addSingleton(String beanName, Object singletonObject) { Map var3 = this.singletonObjects; synchronized(this.singletonObjects) { //存放一级缓存 this.singletonObjects.put(beanName, singletonObject); //把 二级,三级缓存 当前的对象是删除 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
二级缓存(工厂)的存放
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); Map var3 = this.singletonObjects; synchronized(this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }