Spring是怎么解决循环依赖的? (装载)

首先回顾下Bean加载的主流程:

  1.如果是单例模式,从factoryBeanInstanceCache 缓存中获取BeanWrapper 实例对象并删除缓存

  2.调用 createBeanInstance() 实例化 bean

  3.后置处理

  4.单例模式的循环依赖处理

  5.属性填充

  6.初始化 bean 实例对象

  7.依赖检查

  8.注册 DisposableBean

  这里我们主要分析第4步(单例模式的循环依赖处理):

一、循环依赖是什么?

  循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:

  Spring中的循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现依赖了 B,这时就会去初始化 B,然后又发现 B 依赖 C,跑去初始化 C,初始化 C 的时候发现依赖了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。

  一般来说,Spring 循环依赖的情况有三种:

  1. 构造器的循环依赖。              Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖
  2. field 属性的循环依赖(setter)      spring支持的默认方式,使用到spring缓存
  3. setter原型模式(prototype)    Spring 是无法解决的,也会跑出异常    

  为什么 Spring 不处理 prototype bean 呢?其实如果理解 Spring 是如何解决 singleton bean 的循环依赖就明白了。这里先留个疑问,我们先来看下 Spring 是如何解决 singleton bean 的循环依赖的。

二、解决singleton循环依赖

  在AbstractBeanFactory 的 doGetBean()方法中,我们根据BeanName去获取Singleton Bean的时候,会先从缓存获取。

 1 //DefaultSingletonBeanRegistry.java@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {    
 2 // 从一级缓存缓存 singletonObjects 中加载 bean
 3     Object singletonObject = this.singletonObjects.get(beanName);    
 4     // 缓存中的 bean 为空,且当前 bean 正在创建
 5     if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {        
 6     // 加锁
 7         synchronized (this.singletonObjects) {            
 8         // 从 二级缓存 earlySingletonObjects 中获取
 9             singletonObject = this.earlySingletonObjects.get(beanName);            
10             // earlySingletonObjects 中没有,且允许提前创建
11             if (singletonObject == null && allowEarlyReference) {                
12             // 从 三级缓存 singletonFactories 中获取对应的 ObjectFactory
13               ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);                
14               if (singletonFactory != null) {                    
15               //从单例工厂中获取bean
16                     singletonObject = singletonFactory.getObject();                    
17                     // 添加到二级缓存
18                     this.earlySingletonObjects.put(beanName, singletonObject);                    
19                     // 从三级缓存中删除
20                     this.singletonFactories.remove(beanName);
21                 }
22             }
23         }
24     }    
25     return singletonObject;
26 }

  这段代码涉及的3个关键的变量,分别是3个级别的缓存,定义如下:

    /** Cache of singleton objects: bean name --> bean instance */
    //单例bean的缓存 一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);    
    /** Cache of singleton factories: bean name --> ObjectFactory */
    //单例对象工厂缓存 三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);    
    /** Cache of early singleton objects: bean name --> bean instance */
    //预加载单例bean缓存 二级缓存
    //存放的 bean 不一定是完整的
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

getSingleton()的逻辑比较清晰:

  • 首先,尝试从一级缓存singletonObjects中获取单例Bean
  • 如果获取不到,则从二级缓存earlySingletonObjects中获取单例Bean。
  • 如果仍然获取不到,则从三级缓存singletonFactories中获取单例BeanFactory。
  • 最后,如果从三级缓存中拿到了BeanFactory,则通过getObject()把Bean存入二级缓存中,并把该Bean的三级缓存删掉。

2.1、三级缓存

  看到这里可能会有些疑问,这3个缓存怎么就解决了singleton循环依赖了呢?
  先别着急,我们现在分析了获取缓存的代码,再来看下存储缓存的代码。 在 AbstractAutowireCapableBeanFactory 的 doCreateBean() 方法中,有这么一段代码:

 1 // AbstractAutowireCapableBeanFactory.javaboolean earlySingletonExposure = (mbd.isSingleton() 
 2 // 单例模式
 3         && this.allowCircularReferences 
 4         // 允许循环依赖
 5         && isSingletonCurrentlyInCreation(beanName)); 
 6         // 当前单例 bean 是否正在被创建if (earlySingletonExposure) {    
 7         if (logger.isTraceEnabled()) {
 8         logger.trace("Eagerly caching bean '" + beanName +                
 9         "' to allow for resolving potential circular references");
10     }    
11     // 为了后期避免循环依赖,提前将创建的 bean 实例加入到三级缓存 singletonFactories 中
12     addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
13 }

这段代码就是put三级缓存singletonFactories的地方,其核心逻辑是,当满足以下3个条件时,把bean加入三级缓存中:

  • 单例
  • 允许循环依赖
  • 当前单例Bean正在创建

addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 方法,代码如下:

// DefaultSingletonBeanRegistry.javaprotected 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);
        }
    }
}

  从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的关键。同时这段代码发生在 createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还没有完善(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(已经有内存地址了,可以根据对象引用定位到堆中对象),能够被认出来了。

2.2、一级缓存

  到这里我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 DefaultSingletonBeanRegistry 中,可以发现这个 addSingleton(String beanName, Object singletonObject) 方法,代码如下:

1 // DefaultSingletonBeanRegistry.javaprotected void addSingleton(String beanName, Object singletonObject) {    
2 synchronized (this.singletonObjects) {            
3 //添加至一级缓存,同时从二级、三级缓存中删除。
4         this.singletonObjects.put(beanName, singletonObject);        
5         this.singletonFactories.remove(beanName);        
6         this.earlySingletonObjects.remove(beanName);        
7         this.registeredSingletons.add(beanName);
8     }
9 }

该方法是在 #doGetBean(...) 方法中,处理不同 scope 时,如果是 singleton调用的,如下图所示:

 

 也就是说,一级缓存里面是完整的Bean。

小结:

  • 一级缓存里面是完整的Bean,是当一个Bean完全创建后才put
  • 三级缓存是不完整的BeanFactory,是当一个Bean在new之后就put(没有属性填充、初始化)
  • 二级缓存是对三级缓存的易用性处理,只不过是通过getObject()方法从三级缓存的BeanFactory中取出Bean

总结:

  现在我们再来回顾下Spring解决单例循环依赖的方案:

  • Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三级缓存中)。
  • 这样,一旦下一个 bean 创建的时候需要依赖 bean ,则从三级缓存中获取。

  举个栗子:
    比如我们团队里要报名参加活动,你不用上来就把你的生日、性别、家庭信息什么的全部填完,你只要先报个名字,统计下人数就行,之后再慢慢完善你的个人信息。

  核心思想:提前暴露,先用着

  最后来描述下就上面那个循环依赖 Spring 解决的过程:

  1.   首先 A 完成初始化第一步并将自己提前曝光出来(通过 三级缓存 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来
  2.   然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来
  3.   这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中
  4.   回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了

  最后,为什么多例模式不能解决循环依赖呢?
  因为多例模式下每次new() Bean都不是一个,如果按照这样存到缓存中,就变成单例了。

 

转载自:http://www.662p.com/article/793.html

 

posted @ 2020-12-24 17:10  lomo_cgx  阅读(65)  评论(0编辑  收藏  举报