🏆 文章目标:最近因为Spring Bean名称遇到一些问题,特地做了一些更深入的理解,需要能够帮助到大家。
🍀 Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
✅ 创作者:Jay…
🎉 个人主页:Jay的个人主页
🍁 展望:若本篇讲解内容帮助到您,请帮忙点个赞吧,您的支持是我继续写作的最大动力,谢谢。🙏
介绍
Spring容器通俗描述,我们把它理解成一个Map,那Map里面的key-value。
- key:beanName
- value:单例bean对象
那么基于以上的理解,我们的beanName是否可能会重复呢,那么接下来我们来探索一下Bean名称的生成规则:
BeanNameGenerator
该类为bean定义生成bean名称的策略接口。
源码位置
接口实现关系
规则1 - AnnotationBeanNameGenerator (默认规则)
Springboot可以通过启动类上,查看
@ComponentScan
中属性nameGenerator
的配置,默认为AnnotationBeanNameGenerator
。
它能够处理@Component 以及它所有的派生注解,并且还支持JavaEE的javax.annotation.@ManagedBean、以及JSR 330的javax.inject.@Named注解。
案例1
不指定bean名称,则将基于类的短名称(小写的第一个字母)生成适当的名称,当出现重复时,将无法启动工程。
类名 com.doc.service.impl.InstanceServiceImpl
,bean 名称 叫 instanceServiceImpl
案例2
指定了bean名称,则以自定义的bean名称为准。当出现重复时,将无法启动工程。
如下bean名称叫myBean
package com.doc.service.impl; @Service("myBean") public class InstanceServiceImpl {}
源码理解
// @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
规则2 - DefaultBeanNameGenerator
它是用来处理xml资源文件的Bean name生成器
案例1
该种方式适合bean名称存在重复的情况,按照如下的案例,则可以看到bean名称为
类路径#N
,当类路径重复,则N
进行递增。
如下bean名称叫com.doc.service.impl.InstanceServiceImpl#0
package com.doc.service.impl; @Service public class InstanceServiceImpl {}
源码讲解
// @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中已经几乎处于一个被弃用了的状态
自定义生成规则
编写实现类代码
实现 BeanNameGenerator
,重写generateBeanName
方法,按照实际需求编写规则。
/** * @author : Jay * @description : 自定义Bean * @date : 2023-03-24 17:55 **/ @SuppressWarnings("all") public class MyBeanNameGenerator implements BeanNameGenerator { @Override public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) { String beanClassName = beanDefinition.getBeanClassName(); // int index = beanClassName.lastIndexOf("."); beanClassName = "jay" + beanClassName.substring(index + 1); String customizedBeanName = Introspector.decapitalize(beanClassName); System.out.println(customizedBeanName); return customizedBeanName; } }
配置器 nameGenerator 为自定义类
@SpringBootApplication @ComponentScan(nameGenerator = MyBeanNameGenerator.class) public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } }
结果
Bean自定义名称的结果:jayDemoConfig
/** * @author : Jay * @description : * @date : 2022-10-14 13:42 **/ @Configuration public class DemoConfig {}
总结
如果某天碰到一个问题,最终定位到原因是BeanName重复导致的,那我敢猜测您为了找到这个问题,应该是花费了不少时间的。理解Spring Bean的生成原理,会帮助我们解决更多类似的问题。
关注公众号:熊猫Jay字节之旅,了解更多 AI 技巧 ~