Spring-Bean的循环依赖

文章参考:

曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存(好文)
一文告诉你Spring是如何利用“三级缓存“巧妙解决Bean的循环依赖问题的【享学Spring】(好文)

面试讲解思路:

  • 什么是循环依赖?
  • Spring怎么解决循环依赖
  • Spring对于循环依赖无法解决的场景
  • 怎么检测循环依赖

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。类似于死锁

Spring中循环依赖场景有:
(1)构造器的循环依赖 (不能被解决,加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决 
(2)field属性的循环依赖,也就是setter注入的循环依赖。(可以通过三级缓存来解决)

Spring对于循环依赖的解决

Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步:

  • createBeanInstance, 实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean,填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
  • initializeBean,调用spring xml中指定的init方法,比如一些形如initMethodInitializingBean等方法
循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
首先我们看源码,三级缓存主要指:
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
这三级缓存分别指: 
singletonObjects:单例对象的cache
earlySingletonObjects :提前暴光的单例对象的Cache,存放的是没有初始化完全的对象
singletonFactories : 单例对象工厂的cache,没有经过后置处理且没有初始化完全的对象的工厂。加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决 ,工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象,去获取包装后的bean,或者说,代理后的bean。
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:     
// allowEarlyReference: 是否允许从singletonFactories中通过getObject拿到对象
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //    isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成
    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 != NULL_OBJECT ? singletonObject : null);
}
上面的代码需要解释两个参数:
    isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
    allowEarlyReference: 是否允许从singletonFactories中通过getObject拿到对象
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取该对象的工厂,使用工厂获取对象,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

比如A、B两个bean相互依赖,他们的注入过程如下:

A首先完成了初始化的第一步,即使用无参构造创建bean,并且将自己的工厂提前曝光到singletonFactories中,然后进行初始化的第二步属性填充,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以需要先创建B,B在属性填充的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories获取到了,因为A在属性填充之前就将自己的工厂提前曝光在了ObjectFactory中,所以B能够通过A对象的工厂拿到创建了一半的A对象,B拿到A对象后顺利完成了创建对象的三个步骤,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己初始化的剩下阶段,最终A也完成了初始化,被装入了一级缓存singletonObjects中。所以循环依赖问题就被解决了。
简单来说就是:
为了避免循环依赖,在Spring中创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean则直接使用ObjectFactory 。然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);随后将对象从ObjectFactory三级缓存放入earlySingletonObjects二级缓存以提高其他对象获取该引用的效率,因为可以少尝试一次缓存。

原型模式(prototype)  循环依赖

因为原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测,发现原型模式下存在循环依赖并抛出异常

Spring对于循环依赖无法解决的场景

构造器注入没有将自己提前曝光在第三级缓存中,所以对其他依赖它的对象来说不可见,所以这种循环依赖问题不能被解决,而原型模式的循环依赖则没有用到缓存机制,更不能解决循环依赖问题,两种情况都会抛出循环依赖异常。

怎么检测是否存在循环依赖

 可以 Bean创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。

 
只有单例模式下才会尝试解决循环依赖,多例模式(原型模式)下如果存在循环依赖直接抛出异常

为什么要三级缓存,一级不行吗

一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。

如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。

所以,我们就要加一个map,这个map,用来存放那种不完整的bean。

为什么要三级缓存,两级不行吗

其实粗略划分是两种对象,已创建完成的和未创建完成的。但是再细分其实可以划分了三种对象,或者三个阶段的对象,已创建完成的,未创建完成但已经后置处理过的,未创建完成且未后置处理的。
第二级缓存中存放的是经过了后置处理但没有初始化完全的对象的工厂,如果把经过了后置处理的和没经过后置处理的对象都放在第二级缓存中,这显然会干扰我们程序的运行。所以需要先暴露在第三级缓存中,从第三级缓存中取出对象的时候判断是否需要经过后置处理,如果需要那么经过后置处理后再放入第二级缓存。
使用三级而非二级缓存并非出于IOC的考虑,而是出于AOP的考虑,即若使用二级缓存,在AOP情形下,注入到其他bean的,不是最终的代理对象,而是原始对象。
在将三级缓存放入二级缓存的时候,getEarlyBeanReference()会判断是否有SmartInstantiationAwareBeanPostProcessor这样的后置处理器,判断是否用对对这个对象进行了应用逻辑修或者应用逻辑增强,比如说判断是否实现了AOP逻辑,如果实现了AOP逻辑,那就需要执行AOP逻辑后把代理对象放入第二级缓存,而非是原始对象。改换句话说 getEarlyBeanReference() 这里是给用户提供接口扩展的,所以采用了三级缓存
加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的egg。
然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);
bean的工厂一直存在于第三级工厂缓存中,直到第一个对象需要该引用时才会获取该工厂,并将该工厂获取的对象放到第二级缓存中,这是实际上是起到了一种延时暴露的机制,在没有对象需要这个引用之前,这个对象都不会出现在第二级缓存中,可能这样可以提高性能
其实按道理是可以二级缓存就可以初始化一些内容,但是在这里
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 否则啥都不做,也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑~~~,比如在getEarlyBeanReference()里可以实现AOP的逻辑~~~ 参考自动代理创建器AbstractAutoProxyCreator 实现了这个方法来创建代理对象

Spring容器会将每一个正在创建的Bean 标识符放在一个 singletonsCurrentlyInCreation“当前创建Bean池”中Bean标识符在创建过程中将一直保持在这个池中,而对于创建完毕的Bean将从当前创建Bean池中清除掉。

posted @ 2020-12-18 21:28  Lucky小黄人^_^  阅读(1020)  评论(0编辑  收藏  举报