spring 循环依赖处理

解决 bean 之间的循环依赖分为2种:

  • 构造函数注入导致的循环依赖
    发现这种情况, spring无解, 直接抛出 BeanCurrentlyInCreationException 异常
  • setter注入导致的循环依赖
    spring 只解决 singleton bean 下的 setter 注入循环依赖, 其它 scope 下依然会抛出 BeanCurrentlyInCreationException 异常. spring 将正在创建的 singleton bean 提前暴露出来, 供其它 bean 提前引用

1. 为什么 spring 只解决 singleton scope 下的环形依赖

prototype scope 的 bean 是一个 ThreadLocal 变量. 在 AbstractBeanFactory 的 #doGetBean 方法中, 调用了 afterPrototypeCreation(beanName)
prototypesCurrentlyInCreation 是一个 ThreadLocal 集合, bean 在被创建之前加入这个集合, 创建后从这个集合中删除.(如下代码体现了这个过程)

// AbstractBeanFactory.java

private final ThreadLocal<Object> prototypesCurrentlyInCreation =
        new NamedThreadLocal<>("Prototype beans currently in creation");

/* 创建后删除 */
protected void afterPrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();  // 从 threadLocal 中获取
    if (curVal instanceof String) {
        this.prototypesCurrentlyInCreation.remove();     // bean 从 threadLocal 中删除
    }
    else if (curVal instanceof Set) {
        Set<String> beanNameSet = (Set<String>) curVal;  // bean 从 threadLocal 的 set 中删除
        beanNameSet.remove(beanName);
        if (beanNameSet.isEmpty()) {
            this.prototypesCurrentlyInCreation.remove();
        }
    }
}

/* 创建前加入 */
protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String) {
        Set<String> beanNameSet = new HashSet<>(2);
        beanNameSet.add((String) curVal);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set<String> beanNameSet = (Set<String>) curVal;
        beanNameSet.add(beanName);
    }
}

AbstractBeanFactory 类的 #doGetBean() 方法中, 首先判断是否是一个 singleton 的 bean, 如果不是, 判断该 bean 是否正在当前线程中创建, 如果是, 抛出异常

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
            (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

2. 为什么只能解决构造器出入的环形依赖

问题的答案同 prototype , 代码在 DefaultSingletonBeanRegistry 类的 #beforeSingletonCreation() 方法 . 创建 bean 之前, 会去 inCreationCheckExclusions 集合判断是否正在创建, 是的话抛出异常

// DefaultSingletonBeanRegistry.java

private final Set<String> inCreationCheckExclusions =
        Collections.newSetFromMap(new ConcurrentHashMap<>(16));
protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

3. spring 如何解决 singleton scope 下的 setter 环形依赖

之所以能够解决 setter 环形依赖, 是因为构造器构造对象后, 堆内存中已经分配了内存, 即使再设置属性, 也不会改变对象的地址引用. 为了在创建时获取 singleton 的 bean , spring 使用了三级缓存结构

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

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

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

三级缓存如下:

  • L1 cache: singletonObjects 存储已经实例化好的 bean
  • L2 cache: earlySingletonObjects 提前暴露的 singleton 缓存, 仅通过构造器反射, 还没有注入属性的 bean
  • L3 cache: singletonFactories 对创建 singleton bean 的工厂缓存

创建 bean 的第一步是创建一个同名的 single factory, 这个 factory 也是 bean,

// AbstractAutowireCapableBeanFactory.java

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    ......
    // () -> getEarlyBeanReference 表示一个匿名的ObjectFactory类, 其 getBean 方法调用了 getEarlyBeanReference() 方法
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    .....
}

// 表示调用 ObjectFactory 工厂的 getBean 时, 会执行 getEarlyBeanReference 方法   
// 该方法会处理一个 SmartInstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor, 获取 earlyReference 的代理对象  
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;
}

addSingletonFactory()方法如下. 可以发现, 创建 bean 的 single factory 被放到三级缓存

// DefaultSingletonBeanRegistry.java

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);
        }
    }
}

从缓存中获取 bean 的方法在 getSingleton(), 在获取 singleton bean 作为属性注入时,
(1) 首先从 L1 cache 中获取 singleton bean
(2) 如果找不到, 可能是正在创建, 所以去 L2 cache 中查找
(3) 如果找不到, 就去 L3 级的 cache 中找到创建 bean 的工厂, 调用工厂的 #getBean() 获取 singleton bean, 再将创建出的 bean 放到二级缓存中. 这样, 就把 bean 的缓存从三级缓存提升到二级缓存中;

// DefaultSingletonBeanRegistry.java

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;
}

问题

( 1 ) 生么时候, bean 从二级缓存提升到一级缓存中?
在 bean 完成属性注入等一些列操作后, bean 已经构建完毕, 将加入一级缓存, 并从二三级缓存中删除, 相当于缓存提升

( 2 ) 为什么要有第一级缓存singletonObjects
1.先说一级缓存 singletonObjects。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。
2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。

( 3 ) 说说第三级缓存singletonFactories
上述我们通过一级缓存已经拿到的对象有什么问题?根本问题就是,我们拿到的是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的,再比如我们经常使用的配置类注解@Configuration在缺省情况下(full mode),其内的所有@Bean都是处于动态代理模式,除非手动指定proxyBeanMethods = false将配置转成简略模式(lite mode)。所以,Spring在bean实例化后,将原始bean放入第三级缓存singletonFactories中,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现。通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。

( 4 ) 为什么需要earlySingletonObjects这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories这个缓存,即可实现代理又可以缓存数据。
从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。再次说说三级缓存的划分及其作用。
一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。

cyclicDependency.jpeg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容

  • 循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循...
    余风DR阅读 659评论 0 0
  • 背景正在学习Spring源码。微信公众号的推文中有一篇循环依赖相关文章,但是只有大概处理过程,没有具体的流程。借此...
    _初_chu阅读 617评论 0 1
  • 什么是循环依赖 循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 ...
    向梦而来阅读 210评论 0 0
  • 1.前言: 最近花了些时间去理解Spring 是如何处理循环依赖的,这部分的代码的确比较冗杂,需要静下心来去阅读,...
    FrodeWY阅读 681评论 1 2
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,529评论 28 53