Spring的循环依赖,到底是什么样的
前一段时间,循底阿粉的环依读者给阿粉留言,说在面试的循底时候,有个面试官就问她,环依Spring 的循底各种知识,Spring 的环依生命周期, Spring 的循底循环依赖是如何解决的。
就这么几个问题,环依虽然回答的循底不是很好,但是环依也是很幸运的接到了 offer ,毕竟面试一般很少会因为一两个面试题回答的循底不好,就直接 pass 的环依,还是循底看综合表现的,既然问到阿粉这个 Spring 是环依如何处理循环依赖的了,那么阿粉就得来解释一下,循底Spring 是亿华云计算如何处理循环依赖的。
循环依赖什么是循环依赖,说到循环依赖,这个实际上是没有那么复杂的,就比如很简单的说,A 引用了 B ,而这个时候 B 也引用了 A ,那么这种情况实际上就是出现了循环依赖的问题了,实际上也可以把循环依赖称之为循环引用,两个或者两个以上的bean互相持有对方,最终形成闭环。
这就是循环依赖,也就是循环引用,
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。香港云服务器否则的话,他就是一个死循环.
Spring 中的循环依赖那么 Spring 的循环依赖都有什么呢?
构造器的循环依赖field属性的循环依赖那么针对这两种循环依赖,Spring 它是如何解决的呢?这就很特殊了,构造器的循环依赖问题实际上算是个无解的操作,只能拋出 BeanCurrentlyInCreationException 异常,也就是说,这个构造器导致的循环依赖,Spring 是没有办法来处理的,也只是给抛出了异常,但是对于 字段属性 的循环依赖,还是有解决办法的。
Spring怎么解决循环依赖这个时候,我们就得看看 Spring 的服务器托管对象初始化的过程了,
Spring的单例对象的初始化主要分为三步:
createBeanInstance 实例化populateBean 填充属性initializeBean 初始化createBeanInstance 实例化实际上就是调用对象的构造方法实例化对象,populateBean 实际上就是对 bean 的依赖属性进行一个赋值填充,而 initializeBean 则是调用 Spring xml 中的 init 方法。
这个时候,我们看到这个初始化的过程,一般就应该能猜到会发生 循环依赖 的位置是哪一步了,而单从 bean 的初始化来看,循环依赖发生的位置就是在 createBeanInstance 实例化 以及 populateBean 填充属性 当中,
发生的循环依赖也是
构造器的循环依赖field属性的循环依赖那么 Spring 又是怎么解决这种单例的循环依赖的问题的呢?
三级缓存
那么这三级缓存分别是哪三级的缓存呢?又分别代表了什么含义?
singletonFactories :单例对象工厂的cache,用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects :提前暴光的单例对象的Cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖singletonObjects:单例对象的cache,存放 bean 工厂对象,用于解决循环依赖private final Map
private final Map
如果要分析这个 三级缓存 如何解决循环依赖,那么势必需要知道 Spring 中对象的创建的过程。
对象创建过程,可以大致分为五个步骤,
1.protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly)
AbstractBeanFactory 中的 doGetBean()方法
2.protected Object getSingleton(String beanName, boolean allowEarlyReference)
DefaultSingletonBeanRegistry 中的 getSingleton()方法
在这个方法中,先从一级缓存 singletonObjects 中去获取。(如果获取到就直接return)如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。如果还是获取不到且允许 singletonFactories 通过 getObject() 获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取如果获取到了则:从 singletonFactories 中移除,并放入 earlySingletonObjects 中这就相当于 ctrl+x ,把三级缓存中的数据剪切到了二级缓存。源码如下:
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;
}3.protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
AbstractAutowireCapableBeanFactory 中的 doCreateBean() 方法
//添加到三级缓存
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean " + beanName +
" to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}4.protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)
AbstractAutowireCapableBeanFactory 中的 populateBean() 方法进行属性赋值
5.protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
AbstractAutowireCapableBeanFactory 中的 initializeBean() 初始化对象
源码部分阿粉就不再往上贴那么多了,大家找源码肯定很简单,内部也有具体方法的注释,
Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级cache。
这个 cache 的类型是 ObjectFactory。这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。
这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
如果你能在面试的时候,回答成这个样子,那么这个问题,你至少已经算是回答的比较好了。
但是如果问到这里,面试官有意想要继续深挖一下,你既然知道使用三级缓存解决了这个循环依赖的问题了,那么是不是必须三级缓存才能解决,二级缓存不能解决吗?
这就另外又给你引申出一个问题来了,二级缓存到底能不能解决呢?
其实,二级缓存也是能够实现的,如果自己想要实现,那么就得去改写 AbstractAutowireCapableBeanFactory 的 doCreateBean 的方法了,
//添加到三级缓存
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean " + beanName +
" to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
//从三级缓存中取出立刻放入二级缓存
getSingleton(beanName, true);
}如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。
Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。
如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
所以,你知道为什么不使用二级缓存直接来处理了,而是增加了三级缓存来处理这个循环依赖了吧!