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

相关文章
|
人工智能 Java Serverless
【MCP教程系列】搭建基于 Spring AI 的 SSE 模式 MCP 服务并自定义部署至阿里云百炼
本文详细介绍了如何基于Spring AI搭建支持SSE模式的MCP服务,并成功集成至阿里云百炼大模型平台。通过四个步骤实现从零到Agent的构建,包括项目创建、工具开发、服务测试与部署。文章还提供了具体代码示例和操作截图,帮助读者快速上手。最终,将自定义SSE MCP服务集成到百炼平台,完成智能体应用的创建与测试。适合希望了解SSE实时交互及大模型集成的开发者参考。
14673 60
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `<appender>` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `<logger>` 和 `<root>` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2996 1
|
7月前
|
监控 安全 Java
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
Spring Boot 通过 Actuator 模块提供了强大的健康检查功能,帮助开发者快速了解应用程序的运行状态。默认健康检查可检测数据库连接、依赖服务、资源可用性等,但在实际应用中,业务需求和依赖关系各不相同,因此需要实现自定义健康检查来更精确地监控关键组件。本文介绍了如何使用 @HealthEndpoint 注解及实现 HealthIndicator 接口来扩展 Spring Boot 的健康检查功能,从而提升系统的可观测性与稳定性。
545 0
使用 @HealthEndpoint 在 Spring Boot 中实现自定义健康检查
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1236 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
512 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——拦截自定义异常
本文介绍了在实际项目中如何拦截自定义异常。首先,通过定义异常信息枚举类 `BusinessMsgEnum`,统一管理业务异常的代码和消息。接着,创建自定义业务异常类 `BusinessErrorException`,并在其构造方法中传入枚举类以实现异常信息的封装。最后,利用 `GlobalExceptionHandler` 拦截并处理自定义异常,返回标准的 JSON 响应格式。文章还提供了示例代码和测试方法,展示了全局异常处理在 Spring Boot 项目中的应用价值。
593 0
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
490 2
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。

热门文章

最新文章

推荐镜像

更多
  • DNS