关于架构:0源码基础学习Spring源码系列二Spring如何解决循环依赖

42次阅读

共计 9916 个字符,预计需要花费 25 分钟才能阅读完成。

作者:京东科技 韩国凯

1.1 解决循环依赖过程

1.1.1 三级缓存的作用

循环依赖在咱们日常开发中属于比拟常见的问题,spring 对循环依赖做了优化,使得咱们在无感知的状况下帮忙咱们解决了循环依赖的问题。

最简略的循环依赖就是,A 依赖 B,B 依赖 C,C 依赖 A,如果不解决循环依赖的问题最终会导致 OOM,然而也不是所有的循环依赖都能够解决,spring 只能够解决通过属性或者 setter 注入的单例 bean,而通过结构器注入或非单例模式的 bean 都是不可解决的。

通过上文创立 bean 的过程中咱们晓得,在获取 bean 的时候,首先会尝试从缓存中获取,如果从缓存中获取不到才会去创立 bean,而三层的缓存正是解决循环依赖的要害:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   // 从一级缓存中加载
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 从二级缓存中加载
      singletonObject = this.earlySingletonObjects.get(beanName);
      //allowEarlyReference 为 true 代表要去三级缓存中查找,此时为 true
      if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);
               if (singletonObject == null) {
                  // 从三级缓存中加载
                  ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                  if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                  }
               }
            }
         }
      }
   }
   return singletonObject;
}

能够看到三层缓存其实就是三个 hashmap:

/** 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 ConcurrentHashMap<>(16);

三级缓存的作用:

  • singletonObjects:用于寄存齐全初始化好的 bean,从该缓存中取出的 bean 能够间接应用
  • earlySingletonObjects:提前曝光的单例对象的 cache,寄存原始的 bean 对象(尚未填充属性),用于解决循环依赖
  • singletonFactories:单例对象工厂的 cache,寄存 bean 工厂对象,用于解决循环依赖

1.1.2 解决循环依赖的流程

依据上文,咱们都晓得创立 bean 的流程次要包含以下三步:

  1. 实例化 bean
  2. 拆卸 bean 属性
  3. 初始化 bean

例如咱们当初有 A 依赖 B,B 依赖 A,那么 spring 是如何解决三层循环的呢?

  1. 首先尝试从缓存中加载 A,发现 A 不存在
  2. 实例化 A(没有属性,半成品)
  3. 将实例化实现的 A 放入第三级缓存中
  4. 拆卸属性 B(没有属性,半成品)
  5. 尝试从缓存中加载 B,发现 B 不存在
  6. 实例化 B
  7. 将实例化实现的 B 放入第三级缓存中
  8. 拆卸属性 A
  9. 尝试从缓存中加载 A,发现 A 存在于缓存中(第 3 步),将 A 从第三级缓存中移除,放入第二级缓存中,并将其赋值给 B,B 拆卸属性实现
  10. 此时 B 的拆卸属性结束,初始化 B,并将 B 从三级缓存中移除,放入一级缓存
  11. 返回第四步,此时 A 的属性也拆卸实现
  12. 初始化 A,并将 A 放入一级缓存

自此,实例 A 于 B 都别离实现了创立的流程。

用一张图来形容:

那么此时有一个问题,在第 9 步中 B 领有的 A 是只实例化实现的对象,并没有属性拆卸以及初始化,A 的初始化是在 11 步当前,那么在最初全副创立实现此时 B 中的的属性 A 是半成品还是曾经能够失常工作的成品呢?答案是成品,因为 B 对 A 能够了解为援用传递,也就是说 B 中的属性 A 于第 11 步之前的 A 为同一个 A,那么 A 在第 11 步实现了属性拆卸,天然 B 中的属性也会实现属性拆卸。

例如咱们在一个办法中传入一个实例化对象,如果在办法中对实例化对象做了批改,那么在办法完结后该实例化对象也会做出批改,须要留神的是实例化对象,而不是 java 中的几种根本对象,根本对象是属于值传递(其实实例化对象也是值传递,不过传入的是对象的援用,能够了解为援用传递)。

private static void fun3(){Student student = new Student();
    student.setName("zhangsan");
    System.out.println(student.getName());
    changeName(student);
    System.out.println(student.getName());

    String str = "zhangsan";
    System.out.println(str);
    changeString(str);
    System.out.println(str);
}

private static void changeName(Student student){student.setName("lisi");
}

private static void changeString(String str){str = "lisi";}

// 输入后果
zhangsan
lisi
zhangsan
zhangsan

能够看出援用传递会扭转对象,而值传递不会。

2.2 为什么是三层缓存

2.2.1 三级缓存在循环依赖中的作用

有的小伙伴可能曾经留神到了,为什么须要三层缓存,两层缓存如同就能够解决问题了,然而如果不思考代理的状况下的确两层缓存就能解决问题,然而如果要援用的对象不是一般 bean 而是被代理的对象就会呈现问题。

大家须要晓得的是,spring 在创立代理对象时,首先会实例化源对象,而后在源对象初始化实现之后才会获取代理对象。

咱们先不思考为什么是三级缓存,先看一下在方才的流程中代理对象存在什么问题

回到咱们刚刚举的例子,退出当初咱们须要代理对象 A,其中 A 依赖于 B,而 B 也是代理对象,如果不进行非凡解决的话会呈现问题:

  1. 首先尝试从缓存中加载 A,发现 A 不存在
  2. 实例化 A(没有属性,半成品)
  3. 将实例化实现的 A 放入第三级缓存中
  4. 拆卸属性 B(没有属性,半成品)
  5. 尝试从缓存中加载 B,发现 B 不存在
  6. 实例化 B
  7. 将实例化实现的 B 放入第三级缓存中
  8. 拆卸属性 A
  9. 尝试从缓存中加载 A,发现 A 存在于缓存中(第 3 步),将 A 从第三级缓存中移除,放入第二级缓存中,并将其赋值给 B,B 拆卸属性实现
  10. 此时 B 的拆卸属性结束,初始化 B,并将 B 从三级缓存中移除,放入一级缓存
  11. 返回第四步,此时 A 的属性也拆卸实现
  12. 初始化 A,并将 A 放入一级缓存

跟之前一样的流程,那么此时 B 领有的对象是 A 的一般对象,而不是代理对象,这就有问题了。

可能有同学会问,不是存在援用传递吗?A 被代理实现不是还是会被 B 所领有吗?

然而答案也很简略,并不是, A 跟 A 的代理对象必定时两个对象,在内存中必定也是两个地址,因而须要解决这种状况。

2.2.2 解决代理对象的问题

咱们来看看 spring 是如何解决这个问题的:

依据 bean 创立的过程咱们晓得,bean 会首先被实例化,在实例化实现之后会执行这样一段代码:

//3. 是否须要提前曝光,用来解决循环依赖时应用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));

if (earlySingletonExposure) {if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean'" + beanName +
            "'to allow for resolving potential circular references");
   }
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

这也是咱们 2.2.5(详见上篇文章)中的代码,次要有两局部

首先会判断是否须要提前曝光,判断后果由三局部组成,别离是:

  1. 是否是单例模式,默认状况下都是单例模式,spring 也只能解决这种状况下的循环依赖
  2. 是否容许提前曝光,默认是 true,也能够更改
  3. 是否正在创立,失常来说一个 bean 在创立开始该值为 true,创立完结该值为 false

能够看到失常状况下一个 bean 的这些后果都为 true,也就是会进入到上面的办法中,该办法中有一个 lamda 表达式,为了可读性这里进行了拆分。

ObjectFactory<Object> objectFactory = new ObjectFactory<Object>() {
   @Override
   public Object getObject() throws BeansException {return getEarlyBeanReference(beanName, mbd, bean);
   }
} ;
addSingletonFactory(beanName, objectFactory);

该办法的次要内容就是创立了一个 ObjectFactory,其中 getObject()办法返回了 getEarlyBeanReference(beanName, mbd, bean)这个办法调用后果。

看看 addSingletonFactory()这个办法都干了什么

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

其中最次要的一行代码就是向三级缓存中增加了对象,而增加的对象就是传入的 objectFactory,留神,此处增加的不是 bean,而是 factory。这也是咱们上文解决循环依赖过程中第三步的操作。

退出存在循环依赖的话,此时 A 曾经被退出到缓存中,当 A 被作为依赖被其余 bean 应用时,依照咱们之前的逻辑会调用缓存

// 从三级缓存中加载
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
   // 从刚刚增加的 objectFactory 获取其中的对象,也就是调用 getObject,也就是获取 getEarlyBeanReference 办法的内容
   singletonObject = singletonFactory.getObject();
   this.earlySingletonObjects.put(beanName, singletonObject);
   this.singletonFactories.remove(beanName);
}

从刚刚增加的 objectFactory 获取其中的对象,也就是调用 getObject,也就是获取 getEarlyBeanReference 办法的内容

那么咱们看看为什么会返回一个 factory 而不是一个 bean

exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);

return wrapIfNecessary(bean, beanName, cacheKey);

依据链路能够看到,最终调用到这个办法中返回的,也就是说当实例 A 被别的 bean 依赖时,返回的其实是这个办法中的后果

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 如果是一个须要被代理对象的话,会在此处返回被代理的对象
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);
      Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

看到这里应该就是高深莫测了 spring 是如何解决存在代理对象且存在循环依赖的状况的。

回到之前的逻辑,例如此时实例 B 须要装填属性 A,会从缓存中查问 A 是否存咋,查问到 A 曾经存在,则调用 A 的 getObject()办法,如果 A 是须要被代理的对象则返回被代理过得对象,否则返回一般 bean。

此外还须要留神的是:先创建对象,再创立代理类,再初始化原对象,和初始化之后再创立代理类,是一样的,这也是能够提前裸露代理对象的根底。

2.2.3 二级缓存的作用

那么二级缓存是干什么用的呢

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

能够在源代码中看出每调用一次 getObject() 而后调用 getEarlyBeanReference() 中 createProxy()都会产生一个新的代理对象,并不不合乎单例模式。

(网上有很多文章说是因为调用 lamda 表达式所以会产生新的对象,其实如果非代理 bean 并不会产生新的对象,因为 objectFactory 所持有的是有原始对象的,即时屡次调用也会返回雷同的后果。然而对于代理对象则会每次新 create 一个,所以其实会产生新的代理对象而不会是新产生一般对象。所以就其本质为什么应用二级缓存的起因是因为创立代理对象是应用 createProxy()的办法,每次调用都会产生新的代理对象,那么其实只有有一个中央能依据 beanName 返回同一个代理对象,也就不须要二级缓存了,这也是二级缓存的实质意义。其实也能够在 getObject()办法中去缓存创立实现的代理对象,只不过这样做就太不优雅,不太合乎 spring 的编码标准。

Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

例如 A 依赖于 B,B 依赖与 A、C、D,而 C、D 又依赖于 A,如果不进行解决的话,A 实例化实现之后,在 B 创立过程中获取 A 的代理对象 A1,而后 C、D 获取的代理对象就是 A2、A3,显然不合乎单例模式。

因而须要有一个中央存储从 factory 中获取到的对象,也就是二级缓存的性能:

if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
   // 存储获取到的代理对象或一般 bean
   this.earlySingletonObjects.put(beanName, singletonObject);
   // 此时三级缓存的工厂曾经没有意义了
   this.singletonFactories.remove(beanName);
}

2.2.4 代理对象什么时候被初始化的

原本认为到这里就完结了,然而在梳理有代理对象的循环依赖时,忽然又发现一个问题:

还是 A、B 都有代理相互依赖的例子,在 B 拆卸完 A 的代理对象后,B 初始化实现,A 开始初始化,然而此时的 A 是原始 beanA1,并不是代理对象 A2,而 B 持有的是代理对象 A2,那么原始对象 A1 初始化 A2 并没有初始化,这不是有问题的吗?

在通过一天的查找以及搜查材料还有 debug 后,终于在一篇文章中到答案:

不会,这是因为不论是 cglib 代理还是 jdk 动静代理生成的代理类,外部都持有一个指标类的援用,当调用代理对象的办法时,理论会去调用指标对象的办法,A 实现初始化相当于代理对象本身也实现了初始化

也就是说原始对象 A1 初始化实现后,因为 A2 是对 A1 的封装以及加强,也就代表着 A2 也实现了初始化。

2.2.5 什么时候返回代理对象

此外还有一点须要留神的是,在 A1 拆卸完之后,当前其余 bean 依赖的应该是 A2,并且退出到一级缓存中的也应该是 A2,那么什么时候 A1 被换成 A2 的呢?

// 在上方曾经判断过,个别为 true
if (earlySingletonExposure) {
   //1. 留神此处传入的是 false,并不会去三级缓存中查找,并且如果是代理对象的话此时会返回代理对象
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
      //2. 判断通过后置处理器后对象是否被扭转 == 的话阐明没有被扭转 那么如果是代理对象的话返回被代理的 bean
      if (exposedObject == bean) {exposedObject = earlySingletonReference;}
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name'" + beanName + "'has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] in its raw version as part of a circular reference, but has eventually been" +
                  "wrapped. This means that said other beans do not use the final version of the" +
                  "bean. This is often the result of over-eager type matching - consider using" +
                  "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
         }
      }
   }
}

以上有两个须要留神的中央:

  1. 因为在之前在 B 拆卸属性 A 的时候,即从三级缓存中查找 A 的时候,如果查到了会将 A 的 bean(有可能是代理对象)放入二级缓存,而后删除三级缓存,那么此时 getSingleton() 返回的就是二级缓存中的 bean。
  2. 这里判断通过后置处理器的 bean 是否被扭转,个别是不会扭转的,除非实现 BeanPostProcessor 接口手工扭转。如果没有扭转的话则将缓存中的数据取出返回,也就是说如果此时二级缓存中的是代理 beanA2,此时会返回 A2 而不是原始对象 A1,如果是一般 bean 的话则都一样。而如果对象曾经被扭转则走上面的判断有没有可能报错的逻辑。

3.1 循环依赖总结

在学习代理在循环依赖的,发现其实并不太须要二级缓存,能够在 bean 实例化实现之后就抉择要不要生成代理对象,如果要生成的话就往三级缓存中放入代理对象,否则的话就放入一般 bean,这样他人过去拿的时候就不必判断是否须要返回代理对象了。

前面发现在网络上有很多跟我想得一样的人,目前参考他人的想法以及本人进行了总结大略是这样子的:

无论是实例化实现之后就进行对象代理还是抉择返回一个 factory 在应用的时候进行代理其实效率上都没有什么区别,只不过一个是提前做一个是滞后做,那么为什么 spring 抉择滞后做的这件事呢?我本人的思考是:

情理也很简略,既然效率没有什么晋升的话,为什么要毁坏一般 bean 的创立流程,原本循环依赖就是一件十分小概率的事,没必要因为小概率事件并且滞后也能够解决,从而抉择须要批改一般 bean 的创立逻辑,这无异于是轻重倒置,而这也是二级缓存或者说三级缓存中寄存的是 factory 的意义。

正文完
 0