¿Puede Spring crear beans con nombres "duplicados"? —Resolución de problemas de duplicación de múltiples nombres de beans en un proyecto

Autor: Han Kai de JD Technology

1. Hay beans con nombres duplicados en el proyecto

Como todos sabemos, no es posible crear dos beans con el mismo nombre en Spring, de lo contrario, se informará un error al inicio:

imagen-20230322100944642

Pero encontré dos con el mismo nombre en nuestro proyecto de primavera bean, y el proyecto también puede comenzar normalmente, y los beans correspondientes también se pueden usar normalmente.

Debido a que se usarán varios clústeres de redis en el proyecto, se configuran y diferencian varios entornos de redis en el id.

Pero al configurar el entorno redis, los dos entornos beanson idiguales.

<bean id="cacheClusterConfigProvider" class="com.xxx.rediscluster.provider.CacheClusterConfigProvider">
    <property name="providers">
        <list>
            //创建了一个名为 ccProvider 的bean
            <bean id="ccProvider" class="com.xxx.rediscluster.provider.CCProvider">
                <!--# 替换为当前环境的R2M 3C配置中心地址(详见上方R2M 3C服务地址)-->
                <property name="address" value="${r2m.zkConnection}"/>
                <!--# 替换为R2M集群名-->
                <property name="appName" value="${r2m.appName}"/>
                <!--# 替换为当前环境的客户端对应配置中心token口令(参考上方token获取方式)-->
                <property name="token" value="${r2m.token}"/>
                <!--# 替换为集群认证密码-->
                <property name="password" value="${r2m.password}"/>
            </bean>
        </list>
    </property>
</bean>

<bean id="tjCacheClusterConfigProvider" class="com.xxx.rediscluster.provider.CacheClusterConfigProvider">
    <property name="providers">
        <list>
            //这里竟然也是 ccProvider 
            <bean id="ccProvider" class="com.xxx.rediscluster.provider.CCProvider">
                <!--# 替换为当前环境的R2M 3C配置中心地址(详见上方R2M 3C服务地址)-->
                <property name="address" value="${r2m.tj.zkConnection}"/>
                <!--# 替换为R2M集群名-->
                <property name="appName" value="${r2m.tj.appName}"/>
                <!--# 替换为当前环境的客户端对应配置中心token口令(参考上方token获取方式)-->
                <property name="token" value="${r2m.tj.token}"/>
                <!--# 替换为集群认证密码-->
                <property name="password" value="${r2m.tj.password}"/>
            </bean>
        </list>
    </property>
</bean>

Todo el mundo sabe que <bean>una etiqueta puede declarar un bean, que definitivamente se analizará y usará en primavera, entonces, ¿por qué no se informa un error para dos nombres de bean idénticos?

imagen-20230322103204708

Se puede ver que los beans que creamos son normales y funcionalmente disponibles.

2. Proceso de solución de problemas

2.1 Intente encontrar la ubicación para crear frijoles duplicados directamente

Primera depuración para tratar de encontrar información relevante al crear beans duplicados para ver si hay alguna idea

imagen-20230322103624912

Luego reinicie el proyecto y seleccione el modo de depuración, pero después de ejecutarlo, IDEA indica que se omite el punto de interrupción

imagen-20230322104033613

Después de consultar alguna información y métodos, no funcionó, así que abandoné esta idea.

2.2 Encuentra ideas a partir de la creación de su bean padre

Después de renunciar a las ideas anteriores, pensé que podía usar el código fuente de Spring que aprendí antes para solucionar este problema desde el nivel del código.

Establecer el punto de interrupción para crear el frijol reids

imagen-20230322104714244

Efectivamente, los puntos de interrupción pueden entrar aquí

imagen-20230322104804469

Entonces nuestro pensamiento es muy simple.

En primavera, el paso de ensamblar atributos ocurre en el proceso de: populateBean(beanName, mbd, instanceWrapper)Si se encuentra que su atributo también es un bean, entonces el bean se obtendrá primero, si no existe, su atributo bean se creará primero y luego el atributo bean se asignará al bean después de la creación del bean ensamblado.

//循环要装配bean的所有属性
for (PropertyValue pv : original) {
   if (pv.isConverted()) {
      deepCopy.add(pv);
   }
   else {
      String propertyName = pv.getName();
      Object originalValue = pv.getValue();
      //获取真正要装配的bean
      Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
      Object convertedValue = resolvedValue;
      boolean convertible = bw.isWritableProperty(propertyName) &&
            !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
   }
}

También se puede ver en la depuración que solo hay un atributo de nuestro bean, es decir , se ajusta al atributo que configuramos en el xml anterior. providers

imagen-20230322105338830

Comenzamos desde donde realmente creamos el bean que se ensamblará para averiguar cuándo comenzar a crear el bean.

private Object resolveInnerBean(Object argName, String innerBeanName, BeanDefinition innerBd) {
   RootBeanDefinition mbd = null;
   try {
      ...
      // 真正创建bean的地方
      Object innerBean = this.beanFactory.createBean(actualInnerBeanName, mbd, null);
      if (innerBean instanceof FactoryBean) {
         boolean synthetic = mbd.isSynthetic();
         return this.beanFactory.getObjectFromFactoryBean(
               (FactoryBean<?>) innerBean, actualInnerBeanName, !synthetic);
      }
      else {
         return innerBean;
      }
   }
   catch (BeansException ex) {
      throw new BeanCreationException(
            this.beanDefinition.getResourceDescription(), this.beanName,
            "Cannot create inner bean '" + innerBeanName + "' " +
            (mbd != null && mbd.getBeanClassName() != null ? "of type [" + mbd.getBeanClassName() + "] " : "") +
            "while setting " + argName, ex);
   }
}

createBean(actualInnerBeanName, mbd, null)Esta línea de código debe ser familiar para cualquiera que haya leído el código fuente de Spring, a través de este método se puede obtener el objeto bean a crear.

imagen-20230322152949119

Desde la depuración, también podemos ver que el beanName que se creará ha sido reemplazado por la propiedad que queremos ensamblar ccProvider

Hasta ahora hemos encontrado que, de acuerdo con nuestras expectativas, <bean>sin importar dónde esté la etiqueta, de hecho creará un objeto de frijol.

Entonces, ¿por qué el beanName aquí no tiene miedo de la repetición?

2.3 ¿Por qué los beans aquí no tienen problemas de duplicación?

Mirando hacia atrás en el resorte mencionado anteriormente que no permite beans con nombres duplicados, en realidad es fácil de entender, porque en el proceso de creación de beans, colocaremos los beans creados en el mapa almacenado en caché con el beanName como clave. tiene dos Bean con el mismo nombre, entonces cuando hay un bean duplicado, el segundo bean sobrescribirá al primero.

En este caso, no hay unicidad.Cuando otros beans necesitan depender de beans repetidos, es posible que no devuelvan el mismo bean.

Entonces, ¿por qué los dos frijoles no se repiten aquí?

De hecho, los lectores cuidadosos ya han descubierto que el nombre de la variable aquí es , lo que indica que es un bean interno, entonces, ¿ cuál es la diferencia con los ordinarios ? ¿ Por qué no surge el problema de la duplicación de nombres? innerBean innerBean bean innerBean

Reorganicemos la creación de procesos ordinarios: bean

dibujo.innerbean

De hecho, la respuesta ya es obvia:

Si creamos un bean ordinario, el bean se colocará en el caché después de que se complete la creación. Si hay otros beans para usar, se pueden tomar directamente del caché y el beanName no se puede repetir en función de esta consideración.

La creación innerBeanse basa en createBean()la premisa de la operación atómica, solo se devolverá el bean creado y no se agregará al caché de bean de Spring, por lo que no hay problema de repetición de beanName.

3. Resumen

3.1 ¿Por qué puede haber frijoles con nombres "duplicados" en primavera?

Reorganicemos el proceso de creación de beans aquí:

En el proceso de inyección de primavera en un frijol común, se asignará el objeto de propiedad vacío creado por reflexión. Si encuentra que la propiedad de la que depende también es un frijol, primero obtendrá el frijol, y si no se puede obtener, lo lo creará en su lugar beans.

En este momento, el bean que se creará innerBeanno será compartido por otros frijoles de primavera, por lo que el nombre se puede repetir.

3.2 Uso de innerBean

Siendo nuestro ejemplo de ahora, podemos reescribirlo de la siguiente manera:

<bean id="cacheClusterConfigProvider" class="com.wangyin.rediscluster.provider.CacheClusterConfigProvider">
    <property name="providers">
        <list>
            <!--# 引用ccProviderRef-->
            <ref bean="ccProviderRef"></ref>
        </list>
    </property>
</bean>

<!--# 定义了一个公共的ccProviderRef-->
<bean id="ccProviderRef" class="com.wangyin.rediscluster.provider.CCProvider">
    <!--# 替换为当前环境的R2M 3C配置中心地址(详见上方R2M 3C服务地址)-->
    <property name="address" value="${r2m.zkConnection}"/>
    <!--# 替换为R2M集群名-->
    <property name="appName" value="${r2m.appName}"/>
    <!--# 替换为当前环境的客户端对应配置中心token口令(参考上方token获取方式)-->
    <property name="token" value="${r2m.token}"/>
    <!--# 替换为集群认证密码-->
    <property name="password" value="${r2m.password}"/>
</bean>

En el ejemplo anterior, definimos uno 普通beany lo referenciamos a la propiedad que queríamos.

En este momento , como un frijol ordinario, otros frijoles pueden hacer referencia a él, pero el nombre del frijol no se puede repetir en este momento. ccProviderRef

{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/8591360
Recomendado
Clasificación