Spring循环依赖
Posted 反光的小鱼儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring循环依赖相关的知识,希望对你有一定的参考价值。
什么是循环依赖?
怎么检测是否存在循环依赖
检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。
Spring怎么解决循环依赖
Spring的循环依赖的理论依据基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。(但是构造器必须是在获取引用之前)。
完整实例bean,需要通过上面三个步骤:
1. createBeanInstance方法: 得到一个实例bean,但是没有进行属性值的注入
2. populateBean方法:就是对bean进行属性值注入。
3. initializeBean方法:如果配置文件里面有init方法,需要执行init方法。
可以看出第一步和第二步比较重要,这里面就是解决bean依赖的关键。
我们可以看出进行createBeanInstance方法,得到了bean实例对象,但是没有属性注入。把没有完全实例化的bean,放到addSinletonFactory方法里面去,这样相当于就是提前暴露bean,接下来addSinletonFactory方法,这里面使用三个Map类型的,及三级缓存来解决循环依赖。接下我们看一下addSingletonFactory方法实现。
singleObjects--》单例对象的cache:一级缓存
earlySingletonObjects --》提前暴光的单例对象的Cache :二级缓存
singletonFactories --》单例对象工厂的cache :三级缓存
首先解释两个参数:
- isSingletonCurrentlyInCreation 判断对应的单例对象是否在创建中,当单例对象没有被初始化完全(例如A定义的构造函数依赖了B对象,得先去创建B对象,或者在populatebean过程中依赖了B对象,得先去创建B对象,此时A处于创建中)
- allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
Spring解决循环依赖的诀窍就在于singletonFactories这个cache,这个cache中存的是类型为ObjectFactory,其定义如下
public interface ObjectFactory<T> { T getObject() throws BeansException;}
在bean创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是
new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }
另一处就是
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); } } }
这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
为啥要三级缓存
必须使用三级缓存吗 两级不行吗 提前暴露时直接放到二级缓存earlySingletonObjects里会有什么问题呢?
answer:如果不存在循环依赖,那么就没必要提早曝光放到earlySingletonObjects里。如果像你说的只有两级缓存,那简单的A依赖B,A在第一步初始化未注入field的时候就放到了earlySingletonObjects,显然不符合常理,只能说放到三级缓存中预防循环依赖的产生。singletonFactories 并没有提早曝光的意思,只是为了缓存,earlySingletonObjects才为了提早曝光,所以才要三级的。
以上是关于Spring循环依赖的主要内容,如果未能解决你的问题,请参考以下文章