【Spring源码三千问】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 到底是如何生成的。

methodBeanName

所以,@Bean 定义的 bean name 的生成是:优先取注解中 name 指定的名字做为 bean name。如果注解中没有指定的话,就取 methodName 做为 bean name。

小结

@Service、@Componet 定义的 bean name 的生成规则如下:
优先取注解中的 value 指定的名字做为 bean name。
如果注解中没有指定的话,默认情况下是类名小写,例如: “mypackage.MyJdbcDao” -> “myJdbcDao”
注意有两种特殊的情况:

  1. 如果 bean 是内部类的话,因此将会是 “outerClassName.InnerClassName” 形式的名称
  2. 如果类名是连续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源码分析&测试代码下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值