Spring は「重複した」名前の Bean を作成できますか? - プロジェクト内の複数の Bean 名の重複の問題のトラブルシューティング

著者: JD Technology の Han Kai

1. プロジェクト内に重複した名前の Bean があります

ご存知のように、Spring で同じ名前の 2 つの Bean を作成することはできません。そうしないと、起動時にエラーが報告されます。

画像-20230322100944642

しかし、春のプロジェクトで同じ名前のものが 2 つ見つかりましたbean。プロジェクトも正常に開始でき、対応する Bean も正常に使用できます。

プロジェクトでは複数の redis クラスターが使用されるため、複数の redis 環境が構成され、id で区別されます。

ただし、redis 環境を構成する場合、2 つの環境は同じbeanです。id

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

<bean>タグで Bean を宣言できることは誰もが知っていますが、これは確実に解析され、Spring によって使用されます。なぜ、2 つの同一の Bean 名に対してエラーが報告されないのでしょうか?

画像-20230322103204708

作成した Bean は正常で、機能的に利用可能であることがわかります。

2. トラブルシューティングのプロセス

2.1 重複する Bean を直接作成する場所を見つけようとする

最初にデバッグして、重複する Bean を作成するときに関連情報を見つけて、アイデアがあるかどうかを確認します

画像-20230322103624912

次に、プロジェクトを再起動してデバッグ モードを選択しますが、実行後、IDEA はブレークポイントがスキップされることを要求します。

画像-20230322104033613

いくつかの情報と方法を調べた後、うまくいかなかったので、このアイデアをあきらめました。

2.2 親 Bean の作成からアイデアを見つける

上記のアイデアをあきらめた後、以前に学んだSpringソースコードを使用して、コードレベルからこの問題をトラブルシューティングできると考えました

Reids Bean を作成するためのブレークポイントを設定します。

画像-20230322104714244

案の定、ブレークポイントはここに入ることができます

画像-20230322104804469

すると、私たちの考え方は非常に単純です。

春には、属性を組み立てるステップが次のプロセスで発生します。populateBean(beanName, mbd, instanceWrapper)その属性も Bean であることが判明した場合は、最初に Bean が取得され、存在しない場合は、その属性 Bean が最初に作成され、次に属性 Bean は、作成後に Bean に割り当てられます。 アセンブルされた 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);
   }
}

デバッグから、Bean の属性が 1 つしかないこともわかります。つまり、上記の xml で構成した属性に準拠しています。 providers

画像-20230322105338830

実際に組み立てる Bean を作成するところから始めて、いつ 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)このコード行は、Spring のソース コードを読んだことがある人なら誰でも知っているはずですが、このメソッドを使用して、作成する Bean オブジェクトを取得できます。

画像-20230322152949119

デバッグから、作成される beanName が、アセンブルしたいプロパティに置き換えられていることもわかります ccProvider

これまでのところ、予想どおり、<bean>ラベルがどこにあっても、実際に Bean オブジェクトが作成されることがわかりました。

では、なぜ beanName は繰り返しを恐れないのでしょうか?

2.3 ここの Bean に重複した問題がないのはなぜですか

前述の重複した名前の Bean を許可しないスプリングを振り返ってみると、Bean を作成するプロセスで、作成した Bean を beanName をキーとしてキャッシュされたマップに入れるため、実際には理解しやすいです。同じ名前の Bean が 2 つある場合、重複する Bean があると、2 番目の Bean が最初の Bean を上書きします。

他の Bean が繰り返し Bean に依存する必要がある場合、それらは同じ Bean を返さない可能性があります。

では、なぜ 2 つの豆がここで繰り返されないのでしょうか。

実際、注意深い読者は、ここの変数名が内部 Bean であることを示す であることをすでに発見しているので、通常のものとの違いは何ですか? 名前の重複の問題が発生しないのはなぜですか? innerBean innerBean bean innerBean

通常のプロセスの作成を再編成しましょう bean

innerbean.drawio

実際、答えはすでに明らかです。

通常の Bean を作成すると、その Bean は作成が完了した後にキャッシュに配置されます. 使用する他の Bean がある場合は、キャッシュから直接取得でき、この考慮事項に基づいて beanName を繰り返すことはできません.

作成はアトミック操作をinnerBean前提としておりcreateBean()、作成したbeanのみが返却され、Springのbeanキャッシュには追加されないため、beanNameが繰り返される問題はありません

3. まとめ

3.1 春に「重複」した名前の Bean が存在する理由

ここで Bean 作成プロセスを再編成しましょう。

Spring が一般的な Bean を注入する過程で、リフレクションによって作成された空のプロパティ オブジェクトが割り当てられ、依存するプロパティも Bean であることがわかった場合は、最初に Bean を取得し、取得できない場合はそれを取得します。代わりに作成します。

このとき、作成する Bean はinnerBean他の Spring Bean と共有されないため、名前を繰り返すことができます。

3.2 innerBean の使い方

先ほどの例でも、次のように書き直すことができます。

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

上記の例では、1 つを定義し普通bean、それを必要なプロパティに参照しました。

この時点で、通常の Bean として、他の Bean から参照できますが、この時点で Bean の名前を繰り返すことはできません。 ccProviderRef

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

おすすめ

転載: my.oschina.net/u/4090830/blog/8591360
おすすめ