Kann Spring Beans mit "doppelten" Namen erstellen? – Fehlerbehebung des Problems der Duplizierung mehrerer Bean-Namen in einem Projekt

Autor: Han Kai von JD Technology

1. Es gibt Beans mit doppelten Namen im Projekt

Wie wir alle wissen, ist es in Spring nicht möglich, zwei Beans mit demselben Namen zu erstellen, da sonst beim Start ein Fehler gemeldet wird:

Bild-20230322100944642

Aber ich habe in unserem Frühlingsprojekt zwei mit dem gleichen Namen gefunden bean, und das Projekt kann auch normal starten, und die entsprechenden Bohnen können auch normal verwendet werden.

Da im Projekt mehrere Redis-Cluster verwendet werden, werden mehrere Redis-Umgebungen konfiguriert und anhand der ID unterschieden.

Bei der Konfiguration der Redis-Umgebung beansind die beiden Umgebungen jedoch ididentisch.

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

Jeder weiß, dass <bean>ein Tag eine Bean deklarieren kann, die definitiv bis zum Frühjahr geparst und verwendet wird, warum wird also kein Fehler für zwei identische Bean-Namen gemeldet?

Bild-20230322103204708

Es ist ersichtlich, dass die von uns erstellten Beans normal und funktional verfügbar sind.

2. Fehlerbehebungsprozess

2.1 Versuchen Sie, den Speicherort zu finden, um direkt doppelte Beans zu erstellen

Debuggen Sie zuerst, um zu versuchen, relevante Informationen zu finden, wenn Sie doppelte Beans erstellen, um zu sehen, ob es eine Idee gibt

Bild-20230322103624912

Starten Sie dann das Projekt neu und wählen Sie den Debug-Modus, aber nach der Ausführung fordert IDEA auf, dass der Haltepunkt übersprungen wird

Bild-20230322104033613

Nachdem ich einige Informationen und Methoden konsultiert hatte, funktionierte es nicht, also gab ich diese Idee auf.

2.2 Finden Sie Ideen beim Erstellen der Eltern-Bean

Nachdem ich die obigen Ideen aufgegeben hatte, dachte ich, dass ich den Spring-Quellcode verwenden kann, den ich zuvor gelernt habe, um dieses Problem auf Codeebene zu beheben

Legen Sie den Haltepunkt fest, um die Reid-Bohne zu erstellen

Bild-20230322104714244

Sicher genug, Breakpoints können hier eintreten

Bild-20230322104804469

Dann ist unser Denken sehr einfach.

Im Frühjahr erfolgt der Schritt des Assemblierens von Attributen im Prozess: populateBean(beanName, mbd, instanceWrapper)Wenn festgestellt wird, dass sein Attribut auch eine Bohne ist, wird zuerst die Bohne erhalten, wenn sie nicht existiert, wird zuerst ihr Attribut Bohne erstellt, und dann Das Attribut Bean wird der Bean nach der Erstellung zugewiesen. Die zusammengesetzte Bean.

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

Aus dem Debug ist auch ersichtlich, dass es nur ein Attribut unserer Bean gibt, das heißt , es entspricht dem Attribut, das wir in der obigen XML-Datei konfiguriert haben providers

Bild-20230322105338830

Wir beginnen dort, wo wir die zu montierende Bohne tatsächlich erstellen, um herauszufinden, wann wir mit der Erstellung der Bohne beginnen müssen

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)Diese Codezeile muss jedem bekannt sein, der den Spring-Quellcode gelesen hat.Durch diese Methode kann das zu erstellende Bean-Objekt erhalten werden.

Bild-20230322152949119

Aus dem Debug können wir auch sehen, dass der zu erstellende beanName durch die Eigenschaft ersetzt wurde, die wir zusammenbauen möchten ccProvider

Bisher haben wir festgestellt, dass es unseren Erwartungen entsprechend unabhängig <bean>davon, wo sich das Etikett befindet, tatsächlich ein Bohnenobjekt erstellt.

Warum also hat der beanName hier keine Angst vor Wiederholung?

2.3 Warum haben die Beans hier keine doppelten Probleme

Wenn wir auf den zuvor erwähnten Frühling zurückblicken, der keine Beans mit doppelten Namen zulässt, ist dies eigentlich leicht zu verstehen, da wir beim Erstellen von Beans die erstellten Beans mit dem BeanName als Schlüssel in die zwischengespeicherte Map einfügen zwei Beans mit dem gleichen Namen haben, dann überschreibt die zweite Bean die erste Bean, wenn es eine doppelte Bean gibt.

In diesem Fall gibt es keine Eindeutigkeit. Wenn andere Beans sich auf wiederholte Beans verlassen müssen, geben sie möglicherweise nicht dieselbe Bean zurück.

Warum werden die beiden Bohnen hier nicht wiederholt?

Tatsächlich haben aufmerksame Leser bereits entdeckt, dass der Variablenname hier lautet , was darauf hinweist, dass es sich um eine interne Bean handelt. Was ist also der Unterschied zu gewöhnlichen Beans ? Warum tritt das Problem der Doppelnamen nicht auf? innerBean innerBean bean innerBean

Lassen Sie uns die Erstellung gewöhnlicher Prozesse neu organisieren: bean

innerbean.drawio

Eigentlich liegt die Antwort schon auf der Hand:

Wenn wir ein gewöhnliches Bean erstellen, wird das Bean nach Abschluss der Erstellung in den Cache gestellt.Wenn andere Beans verwendet werden sollen, kann es direkt aus dem Cache genommen werden, und der beanName kann aufgrund dieser Überlegung nicht wiederholt werden.

Die Erstellung innerBeanbasiert auf createBean()der Prämisse der atomaren Operation, nur die erstellte Bean wird zurückgegeben und nicht zum Bean-Cache von spring hinzugefügt, sodass es kein Problem mit wiederholtem beanName gibt

3. Zusammenfassung

3.1 Warum kann es im Frühjahr Bohnen mit „doppelten“ Namen geben?

Lassen Sie uns den Bean-Erstellungsprozess hier neu organisieren:

Bei der Frühlingsinjektion einer gewöhnlichen Bohne wird das durch Reflexion erzeugte leere Eigenschaftsobjekt zugewiesen.Wenn es feststellt, dass die Eigenschaft, von der es abhängt, auch eine Bohne ist, wird es zuerst die Bohne erhalten, und wenn sie nicht erhalten werden kann, sie wird es stattdessen erstellen.

Zu diesem Zeitpunkt wird die zu erstellende Bohne innerBeannicht von anderen Frühlingsbohnen geteilt, sodass der Name wiederholt werden kann.

3.2 Verwendung von innerBean

Immer noch unser Beispiel gerade jetzt, wir können es wie folgt umschreiben:

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

Im obigen Beispiel haben wir eine definiert 普通beanund auf die gewünschte Eigenschaft verwiesen.

Zu diesem Zeitpunkt kann es als gewöhnliche Bean von anderen Beans referenziert werden, aber der Name der Bean kann zu diesem Zeitpunkt nicht wiederholt werden. ccProviderRef

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

Ich denke du magst

Origin my.oschina.net/u/4090830/blog/8591360
Empfohlen
Rangfolge