介绍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;
    }

}

对于它的处理逻辑,可以总结为如下步骤:

  1. 读取待生成Bean实例的类名称(未必是运行时的实际类型)。
  2. 如果类型为空,则判断是否存在parent bean,如果存在,读取parent bean的name + “$child”。
  3. 如果parent bean 为空,那么判断是否存在factory bean ,如存在,factory bean name + “$created”。 到此处前缀生成完毕
  4. 如果前缀为空,直接抛出异常,没有可以定义这个bean的任何依据。
  5. 前缀存在,判断是否为内部bean(innerBean,此处默认为false),如果是,最终为前缀+分隔符+十六进制的hashcode码
  6. 如果是顶级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);
    }

}

对于它的处理逻辑,可以总结为如下步骤:

  1. 读取所有注解类型
  2. 遍历所有注解类型,找到所有为Component等所有支持的含有非空value属性的注解
  3. 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}

 

posted @   残城碎梦  阅读(420)  评论(0编辑  收藏  举报
编辑推荐:
· 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 的过期策略
点击右上角即可分享
微信分享提示