介绍Spring名称生成策略接口BeanNameGenerator
众所周知,Spring容器可以简单粗暴的把它理解为一个大大的Map,存储着容器所管理的所有的单实例对象。我们从使用getBean(String beanName)方法,根据bean名称就能获得容器内唯一的Bean实例就能“证明”到这一点。
可你是否曾想过:既然它是Map,那万一我们写的@Bean的beanName重名了怎么办呢?Spring框架是怎么来处理这个事的呢?
Spring容器通俗描述
我们把它理解成一个Map,那Map里面的key-value你应该知道:
- key:beanName
- value:单例bean对象
从SingletonBeanRegistry注册Bean的方法中也可以看出:
public interface SingletonBeanRegistry {
...
// ===注意它没有remove方法====
void registerSingleton(String beanName, Object singletonObject);
Object getSingleton(String beanName);
boolean containsSingleton(String beanName);
int getSingletonCount();
...
}
同样的bean定义信息的注册器BeanDefinitionRegistry也能类比的看到:
public interface BeanDefinitionRegistry extends AliasRegistry {
...
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;
// 请注意:SingletonBeanRegistry 可没有移除方法~
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
...
}
在我们现在的Spring应用中,动不动容器就需要管理上千个Bean(更有甚者在过去的one in all应用中可能出现上万个Bean的情况)。
既然Spring容器是个Map,那key的重要性不言而喻,他指向着全局唯一的Bean实例,若key被覆盖了,就相当于Map的key被覆盖一样,旧的value值可能将永远就触达不到了~
从而可见,确保beanName的唯一性意义重大。但是管理的Bean多了,怎么去确保这件事肯定就成了一个难题,那么接下来就了解一下Spring它是怎么造的~
beanName的生成规则
我把beanName的生成规则放在最开始描述,是因为我觉得既然涉及到beanName,那么首先就应该知道beanName是怎么来的。
我们知道,Spring提供了非常非常多的方式允许我们向容器内注册一个Bean,下面总结出常用的注册Bean方式对应的BeanName如下:
- xml的<bean/>标签方式,由id属性决定(若没指定则为全类名)
- @Component模式注解方式(包含其所有派生注解如@Service、@Configuration等等)。指定了value值就是value值,否则是类名首字母小写(此种方式是最为常用,也是大批量注册Bean的首选方式)
- @Bean方式。指定了value值就是它,否则就是方法名
- FactoryBean方式。
- ......
这是一个基本的结论。其实大多数时候我们自己都并不会去指定beanName,若没有自己没有指定的话,它怎么来的呢?Spring对它的生成有什么规律可循呢?那么接下来就就研究下这个策略:名称生成策略
BeanNameGenerator
为bean定义生成bean名称的策略接口
BeanNameGenerator接口位于 org.springframework.beans.factory.support 包下面,只声明了一个方法,接受两个参数:definition 被生成名字的BeanDefinition实例;registry 生成名字后注册进的BeanDefinitionRegistry。
// @since 2.0.3 是2.0后出来的
public interface BeanNameGenerator {
// 入参竟然有两个 definition和bean定义注册器
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}
看看它的继承树:
BeanNameGenerator有两个实现版本,DefaultBeanNameGenerator和AnnotationBeanNameGenerator。其中DefaultBeanNameGenerator是给资源文件加载bean时使用(BeanDefinitionReader中使用);AnnotationBeanNameGenerator是为了处理注解生成bean name的情况。
DefaultBeanNameGenerator
它是用来处理xml资源文件的Bean name生成器。
// @since 2.0.3
public class DefaultBeanNameGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// isInnerBean 如果是内部类表示true,这个工具类也能处理
return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
}
将具体的处理方式委托给了BeanDefinitionReaderUtils.generateBeanName这个方法来处理:
public abstract class BeanDefinitionReaderUtils {
// unique, "#1", "#2" etc will be appended, until the name becomes
public static final String GENERATED_BEAN_NAME_SEPARATOR = BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR;
// isInnerBean:是为了区分内部bean(innerBean)和顶级bean(top-level bean).
public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException {
// 拿到Bean定义信息里面的BeanClassName全类名
// 注意这个不是必须的,因为如果是继承关系,配上父类的依旧行了
String generatedBeanName = definition.getBeanClassName();
if (generatedBeanName == null) {
// 若没有配置本类全类名,去拿到父类的全类名+$child"俩表示自己
if (definition.getParentName() != null) {
generatedBeanName = definition.getParentName() + "$child";
}
// 工厂Bean的 就用方法的名字+"$created"
else if (definition.getFactoryBeanName() != null) {
generatedBeanName = definition.getFactoryBeanName() + "$created";
}
}
// 若一个都没找到,抛错~
if (!StringUtils.hasText(generatedBeanName)) {
throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
}
//isInnerBean=true表示你是内部类的话,名字又增加了如下变化
String id = generatedBeanName;
if (isInnerBean) {
// Inner bean: generate identity hashcode suffix.
id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
}
// 如果不是内部类(绝大多数情况下都如此)
// 此方法注意:一定能够保证到你的BeanName是唯一的~~~~
else {
// Top-level bean: use plain class name with unique suffix if necessary.
// Top-level表示最外层的Bean,也就是说非内部类 这里生成绝对唯一的BeanName~~~~
return uniqueBeanName(generatedBeanName, registry);
}
return id;
}
public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {
String id = beanName;
int counter = -1;
// Increase counter until the id is unique.
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = beanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
}
return id;
}
}
对于它的处理逻辑,可以总结为如下步骤:
- 读取待生成Bean实例的类名称(未必是运行时的实际类型)。
- 如果类型为空,则判断是否存在parent bean,如果存在,读取parent bean的name + “$child”。
- 如果parent bean 为空,那么判断是否存在factory bean ,如存在,factory bean name + “$created”。 到此处前缀生成完毕
- 如果前缀为空,直接抛出异常,没有可以定义这个bean的任何依据。
- 前缀存在,判断是否为内部bean(innerBean,此处默认为false),如果是,最终为前缀+分隔符+十六进制的hashcode码
- 如果是顶级bean(top-level bean ),则判断前缀+数字的bean是否已存在,循环查询,知道查询到没有使用的id为止。处理完成(所以这个生成器肯定能保证Bean定义的唯一性,不会出现Bean name覆盖问题)
需要注意的是,DefaultBeanNameGenerator在Spring中已经几乎处于一个被弃用了的状态,唯一使用地方为:
// @since 11.12.2003
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {
private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : new DefaultBeanNameGenerator());
}
}
而看看这个类的实现类们:
显然我们现在几乎不会再使用XmlBeanDefinitionReader。
AnnotationBeanNameGenerator
它能够处理@Component以及它所有的派生注解,并且还支持JavaEE的javax.annotation.@ManagedBean、以及JSR 330的javax.inject.@Named注解。如果注解不指定bean名称,则将基于类的短名称(小写的第一个字母)生成适当的名称。
// @since 2.5 它的出现是伴随着@Component出现
public class AnnotationBeanNameGenerator implements BeanNameGenerator {
// 支持的最基本的注解(包含其派生注解)
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// //判断是否是否是AnnotatedBeanDefinition的子类, AnnotatedBeanDefinition是BeanDefinition的一个子类
// 显然这个生成器只为AnnotatedBeanDefinition它来自动生成名称
if (definition instanceof AnnotatedBeanDefinition) {
// determineBeanNameFromAnnotation这个方法简而言之,就是看你的注解有没有标注value值,若指定了就以指定的为准
// 支持的所有注解:上面已经说明了~~~
// 此处若配置了多个注解且都指定了value值,但发现value值有不同的,就抛出异常了~~~~~
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// Fallback: generate a unique default bean name.
// 若没指定,此处叫交给生成器来生成吧~~~
return buildDefaultBeanName(definition, registry);
}
// 它的方法是protected 由此可见若我们想自定义生成器的话 可以继承它 然后复写
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return buildDefaultBeanName(definition);
}
// 这里是先拿到ClassUtils.getShortName 短名称
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
// 调用java.beans.Introspector的方法 首字母小写
return Introspector.decapitalize(shortClassName);
}
}
对于它的处理逻辑,可以总结为如下步骤:
- 读取所有注解类型
- 遍历所有注解类型,找到所有为Component等所有支持的含有非空value属性的注解
- fallback到自己生成beanName
AnnotatedBeanDefinitionReader
public class AnnotatedBeanDefinitionReader {
...
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
...
}
bean扫描器ClassPathBeanDefinitionScanner中会使用到AnnotatedBeanDefinitionReader去读取Bean定义信息们~
ClassPathBeanDefinitionScanner
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
}
ConfigurationClassPostProcessor
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
private BeanNameGenerator componentScanBeanNameGenerator = new AnnotationBeanNameGenerator();
}
同名name覆盖的case演示
同一个配置文件内出现同名Bean
@Configuration
public class RootConfig {
@Bean("personBean")
public Person person() {
return new Person("personBean", 18);
}
@Bean("personBean")
public Person person2() {
return new Person("personBean----2222", 18);
}
}
使用@Bean若不指定value值,默认是方法名。但因为同一个类内方法名不能一样(不考虑重载情况),所以此处用手工指定同一个value值模拟
测试:
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class);
Object personBean = context.getBean("personBean");
System.out.println(personBean);
}
// 结果打印:Person{name='personBean', age=18}
调换Person定义的上下的位置(调换两个Bean上下的位置)
@Configuration
public class RootConfig {
@Bean("personBean")
public Person person2() {
return new Person("personBean----2222", 18);
}
// 注意:此处我使用的类型是Child,但是beanName没变
@Bean("personBean")
public Child person() {
return new Child();
}
}
// 结果打印:Person{name='personBean----2222', age=18}
得出结论:同一个配置文件内同名的Bean,以最上面定义的为准
不同配置文件内出现同名Bean
@Configuration
public class RootConfig {
@Bean("personBean")
public Person person() {
return new Person("RootConfig----Bean", 18);
}
}
@Configuration
public class TempConfig {
@Bean("personBean")
public Person person() {
return new Person("TempConfig----Bean", 18);
}
}
测试:
public static void main(String[] args) throws Exception {
// 注意此处传值的顺序是先rootConfig 在tempConfig
ApplicationContext context = new AnnotationConfigApplicationContext(RootConfig.class, TempConfig.class);
Object personBean = context.getBean("personBean");
System.out.println(personBean);
}
// 结果打印:Person{name='TempConfig----Bean', age=18}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-28 你知道为什么HashMap是线程不安全的吗?
2021-11-28 Java序列化与反序列化三连问:是什么?为什么要?如何做?
2021-11-28 什么情况用ArrayList or LinkedList呢?
2021-11-28 你能谈谈HashMap怎样解决hash冲突吗
2021-11-28 谈谈这几个常见的多线程面试题
2021-11-28 你能说说进程与线程的区别吗
2021-11-28 谈谈 Redis 的过期策略