Spring bean name的生成规则
系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入
相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?
前言
以前总认为 bean name 的生成规则就是类名的首字母小写。
直到我遇到了下面的问题之后,我发现 bean name 的生成规则没有这么简单:
我定义了一个 bean:
@Service
class BKYInfoServiceImpl implements BKYInfoService {
......
}
然后,我想在 dubbo xml 中将其暴露成一个 dubbo 服务:
<dubbo:service interface="com.kvn.service.BKYInfoServcie" ref="bKYInfoServiceImpl" />
结果启动报错:找不到名为 bKYInfoServiceImpl 的 bean
通过上面这个问题可以看出,bean 的名字肯定不是类名的首字母小写这么简单。
那么,我们就来研究一下,bean name 究竟是怎么生成的?
版本约定
Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文
通过前面对 BeanDefinition扫描注册 的源码分析,我们得知,bean name 是在扫描出 BeanDefinition 之后,通过 BeanNameGenerator#generateBeanName()
生成的。
扫描注册 BeanDefinition 的源代码如下:
// ClassPathBeanDefinitionScanner#doScan()
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 通过 beanNameGenerator 来生成 bean name
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
BeanNameGenerator 有三个实现:
DefaultBeanNameGenerator
AnnotationBeanNameGenerator
FullyQualifiedAnnotationBeanNameGenerator
通常我们都是使用的 AnnotationBeanNameGenerator 来生成 bean name 的:
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
// @Component、@Service 如果指定了 value 的话,那么 bean name 就是指定的值
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// @Component、@Service 没有指定 value 的话,就使用默认的生成规则来生成 bean name
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
/**
* 默认生成的 bean name 是类名小写:例如“mypackage.MyJdbcDao” -> “myJdbcDao”。
* 请注意:
* 1. 如果是内部类的话,因此将会是 “outerClassName.InnerClassName” 形式的名称
* 2. 类名如果是连续2个首字母大写的话,bean name 就是类名:例如 “mypackage.MYJdbcDao” -> “MYJdbcDao”
*/
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
// 根据类名来生成 bean name
return Introspector.decapitalize(shortClassName);
}
通过对源码的分析,上面例子中 BKYInfoServiceImpl 对应的 bean 的名称应该是 BKYInfoServiceImpl,而不是类名首字母小写。
扩展: @Bean 定义的 bean 的 name 是什么生成规则?
我们除了使用 @Component、@Service 定义 bean 之外,还可以在方法上使用 @Bean 来定义一个 bean,那么,这时 bean name 的生成规则又是什么呢?
根据前面 BeanDefinition扫描注册 的分析,我们知道 bean 最终是通过 DefaultListableBeanFactory#registerBeanDefinition() 来注册的。
我们不防在 DefaultListableBeanFactory#registerBeanDefinition()
打一个断点,然后手动定义一个这样的 bean,来观察下 bean name 到底是如何生成的。
所以,@Bean 定义的 bean name 的生成是:优先取注解中 name 指定的名字做为 bean name。如果注解中没有指定的话,就取 methodName 做为 bean name。
小结
@Service、@Componet 定义的 bean name 的生成规则如下:
优先取注解中的 value 指定的名字做为 bean name。
如果注解中没有指定的话,默认情况下是类名小写,例如: “mypackage.MyJdbcDao” -> “myJdbcDao”
注意有两种特殊的情况:
- 如果 bean 是内部类的话,因此将会是 “outerClassName.InnerClassName” 形式的名称
- 如果类名是连续2个首字母大写的话,bean name 就是类名,例如:“mypackage.MYJdbcDao” -> “MYJdbcDao”
@Bean 定义的 bean name 的生成规则是:
优先取注解中 name 指定的名字做为 bean name。
如果注解中没有指定的话,就取 methodName 做为 bean name。
SpringIoC源码视频讲解:
课程 | 地址 |
---|---|
SpringIoC源码解读由浅入深 | https://edu.51cto.com/sd/68e86 |
如果本文对你有所帮助,欢迎 点赞收藏
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载