spring 同名bean问题 分析和解决
不知道有没有想到过我们的配置中约定的尽量一个bean使用一个name,可是随着引入的第三方库越来越多,就有可能就会出现名字相同的bean。那么问题来了,多个name相同的bean,spring是如何处理的呢?是只保留第一个还是覆盖保留最后一个又或者是抛出异常明确名称不能重复,又或者业务的因素明确了不能一致的情况?接下来我们看看这个同名bean的问题。
说起同名bean,肯定要提到allowBeanDefinitionOverriding,这个关键字官方介绍是
Whether to allow re-registration of a different definition with the same name
,当遇到同样的名字的时候,是否允许覆盖注册。
把代码定位到具体的位置 DefaultLiatableBeanFactory 文件
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
....
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
// 当发现了重名的bean之后,而且不允许出现重名bean则抛出异常
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
// 用新的bean替换之前旧的bean
this.beanDefinitionMap.put(beanName, beanDefinition);
....
发现几个疑问,可以实践下
- 什么叫新的bean、如何界定
- 如何修改allowBeanDefinitionOverriding值
如何界定新的bean
image.png image.png已经设置好了两个xml,各自包含了一个同名的bean,只是参数不一致而已
- 情况1
image.png
context1.xml 在前, context.xml 在后
image.png
- 情况2
image.png
context.xml 在前, context1.xml 在后
image.png
看在读取xml解析的时候同样可以知道,答案已经很明显了,spring是按照文件列表一个一个扫描注册的,所以最后保留的是后一个文件的bean
如何修改allowBeanDefinitionOverriding值
解决的思路肯定是要在真正的解析bean之前修改该值,而且要在defaultlistablebeanfactory生成之后。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
// 已经存在bean工厂,现在清空
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 创建bean工厂
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 自定义设入bean工厂的
loadBeanDefinitions(beanFactory);
// 开始加载xml进行解析操作,无法修改bean工厂
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
// 把Application本身的是否可覆盖bean值赋值给bean工厂
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
// 赋值是否允许循环引用
}
}
那么我们能够动刀的地方只能是customizeBeanFactory了,代码如下
public static void main(String[] args){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext(new String[]{"context.xml", "context1.xml"}, false);
// 注意context的顺序,可以预知肯定是在context1.xml中出现冲突
// 注意这个false数据,设置为false,意味着不会主动的去刷新bean工厂以及解析xml
applicationContext.setAllowBeanDefinitionOverriding(false);
// 赋值application的参数allowBeanDefinitionOverriding
applicationContext.refresh();
// 现在需要手动的启动refresh操作
Student student = (Student)applicationContext.getBean("student");
System.out.println(student.toString());
}
如上图的applicationContext.setAllowBeanDefinitionOverriding(false);
,这一步就是为了配合在refresh中的customizeBeanFactory函数操作了,这样就完美的完成了我们的需求。
执行结果,如下图,确实是提示错误说在context1.xml 也是符合我们的预想
image.png