Lectura 21 del código fuente de Spring: dependencias circulares y caché L3

Este es el día 33 de mi participación en el "Nuggets Daily New Plan·Desafío de actualización de agosto", haga clic para ver los detalles del evento

Basado en Spring Framework v5.2.6.RELEASE

Continuación del artículo anterior: Código fuente de Spring, lectura 20: Obtener el nombre canónico del bean

Resumen

El artículo anterior presentó el primer paso para obtener el objeto de instancia del Bean, de acuerdo con el namenombre canónico dado del Bean. Dado nameel nombre de alias del Bean o el nombre de referencia inversa del FactoryBean, el nombre canónico obtenido es el identificador único del Bean en el contenedor.

doGetBeanEste artículo continúa describiendo la siguiente línea clave de código para los métodos en AbstractBeanFactory .

Object sharedInstance = getSingleton(beanName);
复制代码

Obtenga el objeto de instancia de Bean del caché

Vaya directamente al getSingletoncódigo fuente del método.

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
@Override
@Nullable
public Object getSingleton(String beanName) {
   return getSingleton(beanName, true);
}

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
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;
}
复制代码

La función de este método es beanNameobtener el objeto de instancia de Bean correspondiente del caché del contenedor. Hay dos cosas a tener en cuenta aquí:

  1. El parámetro que se pasa llamando al método es transformedBeanNameel nombre del bean canónico obtenido a través del método.
  2. Al llamar al método sobrecargado, el allowEarlyReferenceparámetro recibe un valor predeterminado true.

Luego mire el código en el cuerpo del método. Primero, explore aproximadamente todo el proceso del cuerpo del método. Puede encontrar que hay tres colecciones en el contenedor. Spring usará beanName para encontrar el objeto de instancia del bean de estos tres colecciones a su vez Estas tres colecciones, podemos pensar en ellas como cachés (en realidad cachés), el código para crearlas se puede encontrar en DefaultSingletonBeanRegistry:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码

Las claves de estos tres conjuntos de mapas son los nombres de los beans y los valores son las instancias del bean singleton, las instancias del bean singleton anterior y la fábrica de beans singleton, respectivamente.

getSingleton方法的开头,会尝试从singletonObjects集合获取 Bean 的实例,获取到就得到了想要的结果,直接返回。

当没有获取到时,如果isSingletonCurrentlyInCreation(beanName)的结果为true,那么尝试从下一个缓存 Map 中获取。isSingletonCurrentlyInCreation方法的源码如下:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation
/**
* Return whether the specified singleton bean is currently in creation
* (within the entire factory).
*  @param  beanName the name of the bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
   return this.singletonsCurrentlyInCreation.contains(beanName);
}
复制代码

这里还是一个集合,根据注释可以知道,这个方法的作用是判断一个单例 Bean 是不是正在被创建,也就是说这个集合中保存了所有正在被创建的 Bean 的名称。

返回到getSingleton方法中,如果从singletonObjects中获取不到,并且这个 Bean 正在被创建,那么就尝试从earlySingletonObjects集合中获取。这里存放的是早期单例 Bean 的实例对象,这些 Bean 实例被创建好,但是还没有进行完初始化,就会被放在这个集合当中,可以提前被获取到了,如果这里获取到了目标 Bean 实例,那么也会作为方法的结果返回。

如果还没有获取到,并且allowEarlyReferencetrue,那么会接着尝试从singletonFactories中获取,这里缓存的是创建早期单例的 Bean 工厂,如果从singletonFactories集合中获取到了这个工厂对象,那么就调用它的getObject方法,将早期 Bean 创建出来,并作为结果返回。

另外,如果早期 Bean 是在这里创建的,那么还需要把创建好的早期 Bean 添加到earlySingletonObjects中,并把工厂从singletonFactories移除掉,因为再次之后,它已经不会被用到了。

你可能会问了:何必呢?直接把 Bean 完整地初始化好放在一个缓存中获取,获取不到就创建新的,不就行了吗,为什么还需要早期实例和工厂实例这种中间状态呢?

这就要讲到循环依赖的问题。

循环依赖

假设接下来我们提到的类型,都是用来创建单例 Bean 的类型,思考这样三种情况:

  1. ClassA 中有一个属性的类型是 ClassA
  2. ClassA 中有一个属性的类型是 ClassB,同时,ClassB 中有一个属性的类型是 ClassA
  3. ClassA 中有一个属性的类型是 ClassB,ClassB 中有一个属性的类型是 ClassC,同时,ClassC 中有一个属性的类型是 ClassA

在之前的 Spring 源码阅读 19:如何 get 到一个 Bean? 一文中,曾经介绍过,完成一个 Bean 的初始化之前,必须要先初始化其各个属性值对应的 Bean。当出现以上三种情况或者类似的形成循环依赖关系的情况,就会出现问题。

以第 3 种情况为例,要完成 ClassA 的初始化,就要先初始化 ClassB,那么就要先初始化 ClassC,初始化 ClassC 又需要完成 ClassA 的初始化。这样就变成了「鸡生蛋蛋生鸡」的问题。

这种情况在实际的开发中,难以完全避免,因此,这是 Spring 必须要解决的问题。

三级缓存

Spring 解决这个问题的办法就是:在一个 Bean 还没有完成初始化的时候,就暴露它的引用。这里就需要用到前面介绍过的三个缓存 Map,也叫三级缓存

以刚刚描述的情况为例,当 ClassA 需要需要一个 ClassB 的单例 Bean 作为属性填充的时候,先创建一个早期的 ClassB 的 Bean,此时,ClassB 刚刚被创建出来,还没有进行属性填充等初始化的流程,就将它放在earlySingletonObjects中,这样,ClassA 的 Bean 实例就可以使用这个 ClassB 的早期实例进行属性填充,ClassB 的早期实例,可以再次之后再进行初始化,这样就不会因为循环依赖关系,导致无法初始化这三个 Bean。

暂时无法在飞书文档外展示此内容

La figura anterior muestra el proceso de inicialización de un Bean y el proceso de obtención de instancias de Bean desde varias cachés, el proceso detallado se analizará más adelante cuando se analice en profundidad el proceso de obtención de un Bean que no ha sido creado por primera vez a través de la código.

limitado

Sin embargo, la solución de Spring a este problema tiene limitaciones.

En Spring, una instancia de bean se puede inyectar como un valor de propiedad a otra instancia de bean a través de métodos y constructores de establecimiento. De acuerdo con el principio de que Spring resuelve el problema de la dependencia circular a través de la memoria caché de tercer nivel, antes de colocar un bean en la memoria caché, al menos el objeto debe crearse a través de la reflexión. Sin embargo, si un atributo solo se puede inyectar a través del constructor, entonces la creación del objeto Bean no se puede completar antes de la inyección y el problema de la dependencia circular no se puede resolver.

Por lo tanto, Spring solo puede resolver dependencias circulares a través de la inyección de setter.

Resumir

Este artículo analiza el principio de que el contenedor Spring obtiene beans singleton del caché, involucrando principalmente el mecanismo de caché de tres niveles diseñado por Spring para resolver el problema de dependencia circular. Obtenga el objeto de instancia de Bean del caché a través del getSingletonmétodo, puede obtenerse o no obtenerse porque el Bean aún no ha comenzado a crearse. En el próximo artículo, continuaremos analizando, si el bean singleton se obtiene aquí, qué procesamiento se debe realizar a continuación.

Supongo que te gusta

Origin juejin.im/post/7136589270583607309
Recomendado
Clasificación