Spring DI 循环依赖

1.什么是循环依赖

就是我们的A类依赖(比如@Autowired B b)B类,这个时候我们去对A类的属性注入时,我们发现B可能没有,怎么办,去实例化,getBean();
但是我在getBean()B的时候,我们又发现A也还没实例化完!!
这个时候就A依赖B,B依赖A,就循环依赖了!!

2.三级缓存
//对于单例模式的Bean整个IOC容器中只创建一次,不需要重复创建
Object sharedInstance = getSingleton(beanName);

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
        //判断当前单例Bean是否正在创建中,也就是没有初始化完成
        //比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。
   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;
}

所以一级缓存为singletonObjects,就是我们的单例对象工厂的cache

//单例对象工厂的cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);;

如果说,我们在一级缓存singletonFactories找不到的时候,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取
这个就是我们的二级缓存 earlySingletonObjects,提前暴光的单例对象的Cache,也是正在创建中的cache

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

如果获取到了我们再从三级缓存移除,放到二级缓存!也就是三级缓存移到二级缓存

3.三级缓存赋值
3.1 一级缓存

doGetBean方法中,Bean实例化完成后会调用getSingleton方法

sharedInstance = getSingleton(beanName, () -> {
   try {
      //创建一个指定Bean实例对象,如果有父级继承,则合并子类和父类的定义
      //----------------------------往下走创建Bean-----------------------------------
      return createBean(beanName, mbd, args);
   }

getSingleton方法中在DefaultSingletonBeanRegistry.java调用addSingleton()方法

//方法会加入一级缓存,同时清空2.3级缓存
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}

所以,一级缓存是存的我们完整的Bean对象,就是除了销毁整个生命流程结束了的Bean

3.2 三级缓存

那么我们3级缓存是个什么东西呢?我们可以看下,它是在哪里put值的,我们发现,singletonFactories是在

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

赋值,而该方法就是在doCreateBean()中调用,但是调用时间是在实例化之后,在populateBean之前!!所以,三级缓存为我们实例化了,但是没有进行DI的一个半成品的Bean的集合!!

所以为什么Spring没有解决构造方法的循环依赖问题? 因为构造方法在实例化的时候调用,所以不会存到三级缓存!

1.A实例化后放入三级缓存singletonFactories中,进行populateBean,发现我A依赖B,并且B是没有实例化完成的
2.去实例化B,发现B又依赖于A,这个时候,我去一级缓存获取A,发现A也还没有bean完成,那么我会继续去三级缓存获取,发现了A
3.那么拿到A以后,B就能顺利的实例化完成,就算A不是完全的,但是B里面有A的对象引用。
4.B实例化完成后,继续A完成实例化。
5.整个A跟B全部实例化成功

4.为什么要用三级缓存

从刚才那个例子来看,我们二级缓存完全没有任何问题,那么spring为什么还要加一个三层!!
我们忽略了一个点,就是我bean实例化后的对象后面可能会改变,也就是我们的aop,会变成我们的代理类!!
大家看过源码应该都知道,我们的aop是哪里实现的?
是在我们初始化的方法,不是实例化的方法,也不是DI的方法,而是在

initializeBean(beanName, exposedObject, mbd);  //里面的after方法,既然是代理增强,肯定得类初始化完成,不然没有任何意义
....
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
...
AbstractAutoProxyCreator.java.java的wrapIfNecessary()

所以,如果是AOP,如果只是用二级缓存的话,我们会得到一个什么问题?
singletonFactories 中是我们的原型对象,而singletonObjects中是我们的代理对象!!
因为singletonFactories是在我们代理之前加入的,而singletonObjects是在我对象初始化完后才进入的!!!
这样我缓存依赖的时候,B我拿到的是原型对象,但是其实我应该依赖的是代理对象!!所以,如果只有二级缓存依赖的话,会导致2个缓存的对象不一致

5.三级缓存为什么能解决?
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

我们发现三级缓存,跟1.2级的缓存不一样,存的不是Object,而是一个匿名内部类,调用getEarlyBeanReference方法

最终调用到AbstractAutoProxyCreator.java的wrapIfNecessary,就是我们动态代理的方法

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

推荐阅读更多精彩内容