Spring이 "중복" 이름을 가진 빈을 생성할 수 있습니까? — 프로젝트에서 여러 빈 이름이 중복되는 문제 해결

저자: JD Technology의 Han Kai

1. 프로젝트에 이름이 중복된 Bean이 있습니다.

우리 모두 알다시피 Spring에서 같은 이름을 가진 두 개의 빈을 생성하는 것은 불가능합니다. 그렇지 않으면 시작 시 오류가 보고됩니다.

이미지-20230322100944642

하지만 봄 프로젝트에서 이름이 같은 두 개를 찾았고 bean프로젝트도 정상적으로 시작할 수 있으며 해당 빈도 정상적으로 사용할 수 있습니다.

프로젝트에서 여러 개의 redis 클러스터를 사용할 것이기 때문에 여러 개의 redis 환경이 구성되고 id에서 구분됩니다.

그러나 redis 환경을 구성할 때 두 환경은 동일 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을 선언할 수 있다는 것을 알고 있습니다. bean은 확실히 파싱되고 spring에 의해 사용될 것입니다. 그런데 왜 두 개의 동일한 bean 이름에 대해 오류가 보고되지 않을까요?

이미지-20230322103204708

우리가 만든 빈이 정상적이고 기능적으로 사용 가능한 것을 볼 수 있습니다.

2. 문제 해결 프로세스

2.1 중복 빈을 직접 생성할 위치를 찾아보세요.

아이디어가 있는지 확인하기 위해 중복 빈을 생성할 때 관련 정보를 찾기 위한 첫 번째 디버그

이미지-20230322103624912

그런 다음 프로젝트를 다시 시작하고 디버그 모드를 선택하지만 실행 후 IDEA는 중단점을 건너뛴다는 메시지를 표시합니다.

이미지-20230322104033613

몇 가지 정보와 방법을 상담한 결과 작동하지 않아 이 아이디어를 포기했습니다.

2.2 부모 bean 생성에서 아이디어 찾기

위의 아이디어를 포기하고 나서 코드 레벨에서 이 문제를 해결하기 위해 이전에 배웠던 스프링 소스 코드를 사용할 수 있다고 생각했습니다.

reids bean 생성을 위한 중단점 설정

이미지-20230322104714244

아니나 다를까 중단점이 여기에 올 수 있습니다.

이미지-20230322104804469

그렇다면 우리의 생각은 매우 간단합니다.

Spring에서 Attribute를 모으는 과정은 다음과 같은 과정을 거친다. populateBean(beanName, mbd, instanceWrapper)자신의 Attribute도 Bean인 경우 먼저 Bean을 획득하고 존재하지 않는 경우 해당 Attribute 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);
   }
}

또한 디버그에서 우리 빈의 속성이 하나만 있다는 것을 볼 수 있습니다. 즉 , 위의 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)이 코드 라인은 스프링 소스코드를 읽어본 사람이라면 익숙할 텐데, 이 방법을 통해 생성할 빈 객체를 얻을 수 있다.

이미지-20230322152949119

디버그에서 생성될 beanName이 우리가 어셈블하려는 속성으로 대체된 것을 볼 수도 있습니다. ccProvider

지금까지 우리는 <bean>레이블이 어디에 있든 우리의 예상과 일치하게 bean 객체를 생성한다는 것을 발견했습니다.

그렇다면 여기서 beanName이 반복을 두려워하지 않는 이유는 무엇입니까?

2.3 여기에 있는 콩에 중복 문제가 없는 이유는 무엇입니까?

앞에서 언급한 중복 이름의 Bean을 허용하지 않는 Spring을 살펴보면 Bean을 생성하는 과정에서 생성된 Bean을 BeanName을 Key로 하여 Cached Map에 넣기 때문에 이해하기 쉽습니다. 동일한 이름을 가진 두 개의 Bean이 있는 경우 중복된 Bean이 있을 때 두 번째 Bean이 첫 번째 Bean을 덮어씁니다.

이 경우 고유성이 없으며 다른 빈이 반복되는 빈에 의존해야 하는 경우 동일한 빈을 반환하지 않을 수 있습니다.

그렇다면 여기서 두 개의 콩이 반복되지 않는 이유는 무엇입니까?

사실, 주의깊은 독자들은 이미 여기서 변수 이름이 내부 빈임을 나타내는 이라는 것을 발견했습니다. 그렇다면 일반 변수와 다른 점은 무엇입니까 ? 이름 중복 문제가 발생하지 않는 이유는 무엇 입니까? innerBean innerBean bean innerBean

일반 프로세스 생성을 재구성해 보겠습니다 . bean

innerbean.drawio

사실 답은 이미 자명합니다.

일반 bean을 생성하면 생성 완료 후 bean이 캐시에 저장되며, 사용할 다른 bean이 있는 경우 캐시에서 직접 가져올 수 있으며 이러한 고려 사항에 따라 beanName을 반복할 수 없습니다.

생성은 원자 연산을 전제 innerBeancreateBean()생성된 빈만 반환되며 스프링의 빈 캐시에 추가되지 않으므로 beanName이 반복되는 문제가 없습니다.

3. 요약

3.1 봄에 "중복" 이름을 가진 콩이 있을 수 있는 이유

여기에서 빈 생성 프로세스를 재구성해 보겠습니다.

일반적인 bean을 spring inject하는 과정에서 리플렉션으로 생성된 빈 속성 객체를 할당하게 되며, 자신이 의존하는 속성이 역시 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>

위의 예에서 우리는 하나를 정의 普通bean하고 원하는 속성을 참조했습니다.

이때 일반 bean과 마찬가지로 다른 bean에서 참조할 수 있지만 이때 bean의 이름을 반복할 수는 없습니다. ccProviderRef

{{o.이름}}
{{이름}}

рекомендация

отmy.oschina.net/u/4090830/blog/8591360