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:
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 bean
are id
the 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?
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
Then restart the project and select debug mode, but after running, IDEA prompts that the breakpoint is skipped
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
Sure enough, breakpoints can come in here
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
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.
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
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 innerBean
is 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 innerBean
will 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 普通bean
and 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