Can Spring create beans with "duplicate" names? —Troubleshooting the problem of duplication of multiple bean names in a project

Author: Han Kai of JD Technology

1. There are beans with duplicate names in the project

As we all know, it is not possible to create two beans with the same name in Spring, otherwise an error will be reported at startup:

image-20230322100944642

But I found two with the same name in our spring project bean, and the project can also start normally, and the corresponding beans can also be used normally.

Because multiple redis clusters will be used in the project, multiple redis environments are configured and differentiated on the id.

But when configuring the redis environment, the two environments beanare idthe same.

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

Everyone knows that <bean>a tag can declare a bean, which will definitely be parsed and used by spring, so why won't an error be reported for two identical bean names?

image-20230322103204708

It can be seen that the beans we created are normal and functionally available.

2. Troubleshooting process

2.1 Try to find the location to create duplicate beans directly

First debug to try to find relevant information when creating duplicate beans to see if there is any idea

image-20230322103624912

Then restart the project and select debug mode, but after running, IDEA prompts that the breakpoint is skipped

image-20230322104033613

After consulting some information and methods, it didn't work, so I gave up this idea.

2.2 Find ideas from creating its parent bean

After giving up the above ideas, I thought that I can use the spring source code I learned before to troubleshoot this problem from the code level

Set the breakpoint to create the reids bean

image-20230322104714244

Sure enough, breakpoints can come in here

image-20230322104804469

Then our thinking is very simple.

In spring, the step of assembling attributes occurs in the process of: populateBean(beanName, mbd, instanceWrapper)If it is found that its attribute is also a bean, then the bean will be obtained first, if it does not exist, its attribute bean will be created first, and then the attribute bean will be assigned to the bean after creation The assembled 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);
   }
}

It can also be seen from the debug that there is only one attribute of our bean, that is , it conforms to the attribute we configured in the above xml providers

image-20230322105338830

We start from where we actually create the bean to be assembled to find out when to start creating the 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)This line of code must be familiar to anyone who has read the spring source code. Through this method, the bean object to be created can be obtained.

image-20230322152949119

From the debug, we can also see that the beanName to be created has been replaced by the property we want to assemble ccProvider

So far we have found that, consistent with our expectations, <bean>no matter where the label is, it will indeed create a bean object.

So why is the beanName here not afraid of repetition?

2.3 Why don't the beans here have duplicate problems

Looking back at the spring mentioned earlier that does not allow beans with duplicate names, it is actually easy to understand, because in the process of creating beans, we will put the created beans into the cached map with the beanName as the key. If we have two Bean with the same name, then when there is a duplicate bean, the second bean will overwrite the first bean.

In this case, there is no uniqueness. When other beans need to rely on repeated beans, they may not return the same bean.

So why are the two beans not repeated here?

In fact, careful readers have already discovered that the variable name here is , indicating that it is an internal bean, so what is the difference from ordinary ones ? Why doesn't the problem of duplication of names arise? innerBean innerBean bean innerBean

Let's reorganize the creation of ordinary processes: bean

innerbean.drawio

In fact, the answer is already obvious:

If we create an ordinary bean, the bean will be placed in the cache after the creation is completed. If there are other beans to be used, it can be taken directly from the cache, and the beanName cannot be repeated based on this consideration.

The creation innerBeanis based on createBean()the premise of atomic operation, only the created bean will be returned, and it will not be added to the bean cache of spring, so there is no problem of repeated beanName

3. Summary

3.1 Why can there be beans with "duplicate" names in spring

Let's reorganize the bean creation process here:

In the process of spring injecting a common bean, the empty property object created by reflection will be assigned. If it finds that the property it depends on is also a bean, it will first obtain the bean, and if it cannot be obtained, it will create it instead. beans.

At this time, the bean to be created innerBeanwill not be shared by other spring beans, so the name can be repeated.

3.2 Usage of innerBean

Still our example just now, we can rewrite it as follows:

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

In the example above we defined one 普通beanand referenced it to the property we wanted.

At this time , as an ordinary bean, it can be referenced by other beans, but the name of the bean cannot be repeated at this time. ccProviderRef

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

Guess you like

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