作者:京东科技 韩国凯

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";}//输入后果zhangsanlisizhangsanzhangsan

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

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的呢?

//在上方曾经判断过,个别为trueif (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的意义。