Insight Spring重复Bean 注册的过程

疑问:在业务工程代码梳理过程中,发现竟然存在xml 和 注解两种方式配置相同beanName,但是不同的Class。竟然能正常启动发布。理论上beanName 是唯一的,是怎么回事。

Insight Spring版本:3.2.0.RELEASE

明确的前提

  1. Spring Bean在容器中的唯一标识是beanName。对应到xml bean标签是id,对应到注解中是默认属性value。

  2. xml 文件内,是不允许配置多个相同id 的Bean。Ide 会提示,同时启动也会报错 SAXParseException:There are multiple occurrences of ID value 'xxx'.

  3. 基于注解的Bean 定义,是不允许配置多个相同value 的Bean。自动扫描注册的过程中,启动报错 ConflictingBeanDefinitionException: Annotation-specified bean name ‘xxx’ for bean class [com.Foo] conflicts with existing, non-compatible bean definition of same name and class [com.Too]

  4. Bean注册是面向BeanFactory 层次的操作。简单的说是存储在Map中。

    /** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
    

xml bean标签定义Bean注册实现

/**
 * xml bean 标签解析实现, 生成BeanDefinition,并注册到BeanFactory
 * 通过源码可以看到,从解析到注册,是没有唯一校验beanName,是否能注册成功,就完全依赖the registry。
 *
 * 源码:DefaultBeanDefinitionDocumentReader#processBeanDefinition
 */
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the given bean definition with the given bean factory. 直接调用,没有校验。
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			// ...
		}
	}
}

自动扫描Bean注册实现

/**
 * 扫描指定的包路径, 生成bean definitions,并注册到BeanFactory
 * 注意:checkCandidate 会对beanName 进行唯一性校验,Bean兼容判断。如果判断已存在兼容的BeanDefinition,则不再注册。
 *
 * @see ClassPathScanningCandidateComponentProvider#findCandidateComponents
 * 源码:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			// ......
			// 注意checkCandidate 的作用:beanName唯一性校验(上述的ConflictingBeanDefinitionException,就是此处出现的);Bean 兼容判断(如果是非扫描Bean,则默认兼容!!!)。
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}						
	}
	return beanDefinitions;
}

Bean注册到容器的校验

BeanFactory 有个配置allowBeanDefinitionOverriding,默认true,是支持重复注册的。

/**
 * Register a new bean definition with this registry.
 * @throws BeanDefinitionStoreException 如果beanDefinition.validate()失败,或者禁止覆盖状态下重复beanName注册
 * 
 * @see RootBeanDefinition
 * @see ChildBeanDefinition
 */
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
	// ......
	synchronized (this.beanDefinitionMap) {
		Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        // 唯一性校验,如果allowBeanDefinitionOverriding,那么会重复注册,替换原有beanDefinition。默认支持。
		if (oldBeanDefinition != null) {
			if (!this.allowBeanDefinitionOverriding) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
		}
		this.beanDefinitionMap.put(beanName, beanDefinition);
	}

	resetBeanDefinition(beanName);
}

实例case 分析

根据上述两种注册实现,实例分析配置的注册过程。

<!-- case1: 先配置自动扫描。先注册Foo,再注册Woo,最终暴露的Bean 是Woo -->
<!-- 包路径下存在beanName="foo"的Class(com.service.Foo) -->
<context:component-scan base-package="com.service.*" />
<!-- xml 中直接定义Bean,beanName="foo" -->
<bean id="foo" class="com.service.Woo"></bean>
<!-- case2: 先配置xml bean。先注册Woo,自动扫描发现同名兼容Bean,跳过Foo,最终暴露的Bean 是Woo -->
<!-- xml 中直接定义Bean,beanName="foo" -->
<bean id="foo" class="com.service.Woo"></bean>
<!-- 包路径下存在beanName="foo"的Class(com.service.Foo) -->
<context:component-scan base-package="com.service.*" />

这样的话,xml bean 配置的优先级是高于自动扫描的bean。

结论

结合上述的分析,Spring 在多个xml配置相同Bean,或者自动扫描和xml混合Bean配置的情况下,默认是允许相同beanName 多次出现的。默认可以理解为,最终解析到的BeanDefinition,会覆盖掉之前相同beanName 的所有BeanDefinition

通过上述分析,可以发现成熟框架在配置细节上都做的非常完善。对于兼容性(支持多种bean注册、支持重复配置)、扩展性(支持overwrite)、一致性(注册结果和配置顺序无关)的设计和实现,都是值得我们在日常开发中借鉴和思考的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值