Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)

简介: Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)

🏆 文章目标:最近因为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;
  }
}

逻辑步骤:

  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中已经几乎处于一个被弃用了的状态

自定义生成规则

编写实现类代码

实现 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 技巧 ~

相关文章
|
3月前
|
数据采集 人工智能 Java
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
DevDocs是一款基于智能爬虫技术的开源工具,支持1-5层深度网站结构解析,能将技术文档处理时间从数周缩短至几小时,并提供Markdown/JSON格式输出与AI工具无缝集成。
145 1
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
|
3月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
372 2
|
4月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `<appender>` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `<logger>` 和 `<root>` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
381 1
|
3月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
144 0
|
5月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
361 26
|
2月前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
320 8
|
3月前
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
376 12
|
3月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
268 5
|
3月前
|
安全 Java 数据安全/隐私保护
Spring Security: 深入解析 AuthenticationSuccessHandler
本文深入解析了 Spring Security 中的 `AuthenticationSuccessHandler` 接口,它用于处理用户认证成功后的逻辑。通过实现该接口,开发者可自定义页面跳转、日志记录等功能。文章详细讲解了接口方法参数及使用场景,并提供了一个根据用户角色动态跳转页面的示例。结合 Spring Security 配置,展示了如何注册自定义的成功处理器,帮助开发者灵活应对认证后的多样化需求。
112 2
|
3月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
119 0

推荐镜像

更多
  • DNS