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冲突的解决之道:附源码分析
796 0
【实战】Spring生成beanName冲突的解决之道:附源码分析
|
21天前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
2月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
1天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
3月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
3月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
105 0
|
7天前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
77 2
|
9天前
|
数据采集 监控 Java
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
本文是关于SpringBoot日志的详细教程,涵盖日志的定义、用途、SLF4J框架的使用、日志级别、持久化、文件分割及格式配置等内容。
18 0
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
|
13天前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
38 2
|
21天前
|
XML Java 关系型数据库
springboot 集成 mybatis-plus 代码生成器
本文介绍了如何在Spring Boot项目中集成MyBatis-Plus代码生成器,包括导入相关依赖坐标、配置快速代码生成器以及自定义代码生成器模板的步骤和代码示例,旨在提高开发效率,快速生成Entity、Mapper、Mapper XML、Service、Controller等代码。
springboot 集成 mybatis-plus 代码生成器