Spring中的默认beanName

简介: 在Spring中每一个注册到容器中的Bean都有自己的名字(至少一个),可能不止一个(别名)。对于未明确指定name的Bean,Spring会自动为其生成一个名字。而对于在xml中配置的Bean和使用诸如Service、Component等注解标识的Bean,Spring为其生成名字的方式并不相同,下面我们一一分析。 #### 核心接口 ![核心接口.png](https://uplo

在Spring中每一个注册到容器中的Bean都有自己的名字(至少一个),可能不止一个(别名)。对于未明确指定name的Bean,Spring会自动为其生成一个名字。而对于在xml中配置的Bean和使用诸如Service、Component等注解标识的Bean,Spring为其生成名字的方式并不相同,下面我们一一分析。

核心接口

核心接口.png

BeanNameGenerator接口定义如下

public interface BeanNameGenerator {

    /**
     * Generate a bean name for the given bean definition.
     * @param definition the bean definition to generate a name for
     * @param registry the bean definition registry that the given definition
     * is supposed to be registered with
     * @return the generated bean name
     */
    String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

BeanNameGenerator是生成beanName的顶级接口,而它有两个实现类,图中左侧的DefaultBeanNameGenerator是给XML配置中Bean使用的,图中右侧的AnnotationBeanNameGenerator则是给通过注解定义的Bean使用的。

XML配置

在此不赘述XML文件中Bean的解析过程,直接来看DefaultBeanNameGenerator,其调用链路为

DefaultBeanNameGenerator#generateBeanName—>BeanDefinitionReaderUtils#generateBeanName

最后这个方法的定义如下

public static String generateBeanName(
            BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
            throws BeanDefinitionStoreException {
        // 先拿类名赋值
        String generatedBeanName = definition.getBeanClassName();
        if (generatedBeanName == null) {
            if (definition.getParentName() != null) {
                generatedBeanName = definition.getParentName() + "$child";
            }
            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");
        }

        String id = generatedBeanName;
        if (isInnerBean) {
            // 内部bean,在少数情况下走该分支,例如使用key-ref等标签时
            // Inner bean: generate identity hashcode suffix.
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
        }
        else {
            // Top-level bean: use plain class name.
            // Increase counter until the id is unique.
            // 为了保证id唯一,在其后加数字
            int counter = -1;
            while (counter == -1 || registry.containsBeanDefinition(id)) {
                counter++;
                id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
            }
        }
        return id;
    }

注释都写在了上面,逻辑很简单:类名+“#”+数字

注解配置

public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        if (definition instanceof AnnotatedBeanDefinition) {
            // 如果注解的value指定了beanName,则使用该值
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        // 如果没有指定value,则为其生成beanName
        return buildDefaultBeanName(definition, registry);
    }

继续追踪

protected String buildDefaultBeanName(BeanDefinition definition) {
        String shortClassName = ClassUtils.getShortName(definition.getBeanClassName());
        return Introspector.decapitalize(shortClassName);
    }

Introspector.decapitalize的代码如下

public static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                        Character.isUpperCase(name.charAt(0))){
            return name;
        }
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }

通过上面两段代码可以看出逻辑如下

  1. 取短类名,即不包含包路径的类名,例如com.test.Student的短类名为Student,这点跟XML配置中取全类名不一样
  2. 如果短类名长度大于1,且第一个和第二个字符为大写,则直接返回短类名,也就是说假设类为com.test.STudent,则beanName为STudent
  3. 其他情况下将短类名首字符小写后返回,假设类为com.test.Student,则beanName为student

验证

由于只为了验证beanName,简单起见,Bean类中都为空

People类

@Component
public class Pepole {
}

TNtt类

@Service
public class TNttt {
}

TestPepole类

public class TestPepole {
}

TNTt类

public class TNTt {
}

其中TestPepole和TNTt通过XML配置

<bean class="com.hust.TestPepole"></bean>
    <bean class="com.hust.TNTt"></bean>

测试主类

public class App {
    public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Pepole.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TNttt.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TestPepole.class)));
        System.out.println(new Gson().toJson(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                TNTt.class)));
    }
}

输出结果

["pepole"]
["TNttt"]
["com.hust.TestPepole#0"]
["com.hust.TNTt#0"]

总结

  • 在不指定beanName的情况下,Spring会自动为注册的Bean生成一个唯一的beanName
  • 通过注解注册的Bean和XML注册的Bean,Spring为其生成默认beanName的机制不一样
  • 不要盲目觉得通过注解注册的Bean,Spring为其生成beanName就是将短类名的首字母小写,当短类名的首字符和第二个字符均大写时,beanName就是短类名
相关文章
|
Java Spring
【实战】Spring生成beanName冲突的解决之道:附源码分析
【实战】Spring生成beanName冲突的解决之道:附源码分析
729 0
【实战】Spring生成beanName冲突的解决之道:附源码分析
|
1月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
2月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
2月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
99 0
|
8天前
|
Java 应用服务中间件 开发者
深入探索并实践Spring Boot框架
深入探索并实践Spring Boot框架
23 2
|
23小时前
|
Java 网络架构
springboot配合thymeleaf,调用接口不跳转页面只显示文本
springboot配合thymeleaf,调用接口不跳转页面只显示文本
11 0
|
29天前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
32 0
|
1月前
|
XML Java 数据库连接
Spring Boot集成MyBatis
主要系统的讲解了 Spring Boot 集成 MyBatis 的过程,分为基于 xml 形式和基于注解的形式来讲解,通过实际配置手把手讲解了 Spring Boot 中 MyBatis 的使用方式,并针对注解方式,讲解了常见的问题已经解决方式,有很强的实战意义。在实际项目中,建议根据实际情况来确定使用哪种方式,一般 xml 和注解都在用。
|
2月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
83 11
|
1月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决