Spring peut-il créer des beans avec des noms "dupliqués" ? —Dépannage du problème de duplication de plusieurs noms de bean dans un projet

Auteur : Han Kai de JD Technology

1. Il y a des beans avec des noms en double dans le projet

Comme nous le savons tous, il n'est pas possible de créer deux beans avec le même nom dans Spring, sinon une erreur sera signalée au démarrage :

image-20230322100944642

Mais j'en ai trouvé deux portant le même nom dans notre projet de printemps bean, et le projet peut également démarrer normalement, et les beans correspondants peuvent également être utilisés normalement.

Étant donné que plusieurs clusters Redis seront utilisés dans le projet, plusieurs environnements Redis sont configurés et différenciés sur l'ID.

Mais lors de la configuration de l'environnement redis, les deux environnements beansont ididentiques.

<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>

Tout le monde sait qu'une <bean>balise peut déclarer un bean, qui sera certainement analysé et utilisé au printemps, alors pourquoi une erreur ne sera-t-elle pas signalée pour deux noms de bean identiques ?

image-20230322103204708

On peut voir que les beans que nous avons créés sont normaux et fonctionnellement disponibles.

2. Processus de dépannage

2.1 Essayez de trouver l'emplacement pour créer directement des beans en double

Déboguez d'abord pour essayer de trouver des informations pertinentes lors de la création de beans en double pour voir s'il y a une idée

image-20230322103624912

Redémarrez ensuite le projet et sélectionnez le mode débogage, mais après l'exécution, IDEA indique que le point d'arrêt est ignoré

image-20230322104033613

Après avoir consulté quelques informations et méthodes, cela n'a pas fonctionné, j'ai donc abandonné cette idée.

2.2 Trouver des idées en créant son bean parent

Après avoir abandonné les idées ci-dessus, j'ai pensé que je pouvais utiliser le code source de printemps que j'avais appris auparavant pour résoudre ce problème au niveau du code.

Définir le point d'arrêt pour créer le bean reids

image-20230322104714244

Effectivement, les points d'arrêt peuvent venir ici

image-20230322104804469

Ensuite, notre réflexion est très simple.

Au printemps, l'étape d' assemblage des attributs se produit dans le processus de : populateBean(beanName, mbd, instanceWrapper)S'il s'avère que son attribut est également un bean, alors le bean sera obtenu en premier, s'il n'existe pas, son bean d'attribut sera créé en premier, puis l'attribut bean sera assigné au bean après la création du bean assemblé.

//循环要装配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);
   }
}

On peut également voir à partir du débogage qu'il n'y a qu'un seul attribut de notre bean, c'est-à-dire qu'il est conforme à l'attribut que nous avons configuré dans le xml ci-dessus providers

image-20230322105338830

Nous partons de l'endroit où nous créons réellement le bean à assembler pour savoir quand commencer à créer le 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)Cette ligne de code doit être familière à tous ceux qui ont lu le code source de Spring.Grâce à cette méthode, l'objet bean à créer peut être obtenu.

image-20230322152949119

Dès le debug, on peut aussi voir que le beanName à créer a été remplacé par la propriété que l'on veut assembler ccProvider

Jusqu'à présent, nous avons constaté que, conformément à nos attentes, <bean>peu importe où se trouve l'étiquette, cela créera effectivement un objet haricot.

Alors pourquoi le beanName ici n'a-t-il pas peur de la répétition ?

2.3 Pourquoi les beans ici n'ont-ils pas de problèmes de duplication

En repensant au ressort mentionné plus tôt qui n'autorise pas les beans avec des noms en double, c'est en fait facile à comprendre, car dans le processus de création des beans, nous mettrons les beans créés dans la carte en cache avec le beanName comme clé. avoir deux Bean avec le même nom, alors quand il y a un bean en double, le deuxième bean écrasera le premier bean.

Dans ce cas, il n'y a pas d'unicité. Lorsque d'autres beans doivent s'appuyer sur des beans répétés, ils peuvent ne pas renvoyer le même bean.

Alors pourquoi les deux haricots ne sont-ils pas répétés ici ?

En fait, des lecteurs attentifs ont déjà découvert que le nom de la variable ici est , indiquant qu'il s'agit d'un haricot interne, alors quelle est la différence avec les haricots ordinaires ? Pourquoi le problème de la duplication des noms ne se pose-t-il pas ? innerBean innerBean bean innerBean

Réorganisons la création des processus ordinaires : bean

innerbean.drawio

En fait, la réponse est déjà évidente :

Si nous créons un bean ordinaire, le bean sera placé dans le cache une fois la création terminée. S'il y a d'autres beans à utiliser, il peut être extrait directement du cache et le beanName ne peut pas être répété en fonction de cette considération.

La création innerBeanest basée sur createBean()la prémisse d'un fonctionnement atomique, seul le bean créé sera renvoyé et il ne sera pas ajouté au cache de bean du printemps, il n'y a donc pas de problème de beanName répété

3. Résumé

3.1 Pourquoi peut-il y avoir des haricots avec des noms "dupliqués" au printemps

Réorganisons ici le processus de création du bean :

Dans le processus d'injection d'un haricot commun par le printemps, l'objet de propriété vide créé par réflexion sera affecté. S'il constate que la propriété dont il dépend est également un haricot, il obtiendra d'abord le haricot, et s'il ne peut pas être obtenu, il le créera à la place.

À ce stade, le bean à créer innerBeanne sera pas partagé par d'autres beans spring, de sorte que le nom peut être répété.

3.2 Utilisation de innerBean

Toujours notre exemple de tout à l'heure, on peut le réécrire comme suit :

<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>

Dans l'exemple ci-dessus, nous en avons défini un 普通beanet l'avons référencé à la propriété que nous voulions.

À ce moment , en tant que haricot ordinaire, il peut être référencé par d'autres haricots, mais le nom du haricot ne peut pas être répété à ce moment. ccProviderRef

{{o.name}}
{{m.name}}

Je suppose que tu aimes

Origine my.oschina.net/u/4090830/blog/8591360
conseillé
Classement