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

相关文章
|
9天前
|
小程序 数据可视化 Java
Java+后端Spring boot 开发的全套UWB定位方案,0.1米高精度定位系统源码
UWB定位系统由硬件定位设备、定位引擎和应用软件组成。该定位系统应用软件支持PC端和移动端访问,并提供位置实时显示、历史轨迹回放、人员考勤、电子围栏、行为分析、智能巡检等功能。定位精度高达10cm,同时具备高动态、高容量、低功耗的优点。应用场景包括:隧道、化工、工厂、煤矿、工地、电厂、养老、展馆、整车、机房、机场等。
34 8
|
3天前
|
XML Java API
IoC 之 Spring 统一资源加载策略【Spring源码】
IoC 之 Spring 统一资源加载策略【Spring源码】
13 2
|
5天前
|
监控 Java API
【监控】spring actuator源码速读
【监控】spring actuator源码速读
7 1
|
5天前
|
监控 Java 关系型数据库
java版MES系统源码,后端采用 Spring Boot 多模块架构
MES系统采用Vue3的vue-element-plus-admin为后台,Spring Boot多模块架构,支持MySQL、Oracle等数据库,具备SaaS多租户功能。核心功能包括车间计划排程、工艺流程配置、生产质量管理、进度追踪、库存和排班管理等,全面覆盖生产运营关键环节。
java版MES系统源码,后端采用 Spring Boot 多模块架构
|
5天前
|
Java Spring
解决 Spring 中 Prototype Bean 注入后被固定的问题
【6月更文挑战第8天】学习 Spring 框架内不原理的意义就是,当遇到问题时,分析出原因,就可以从多个切入点,利用 Spring 的特性,来解决问题。
18 2
|
5天前
|
Java Spring
Spring源码学习——(二)
第二讲——了解BeanFactory的功能
|
6天前
|
Java Spring 容器
Spring源码学习——(一)
第一讲——了解BeanFactory和ApplicationContext
|
7天前
|
运维 网络协议 JavaScript
Serverless 应用引擎产品使用合集之绑定自定义域名是否要确定解析设置
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
7天前
|
存储 算法 搜索推荐
深入解析力扣179题:最大数(自定义排序法详解及模拟面试问答)
深入解析力扣179题:最大数(自定义排序法详解及模拟面试问答)
|
10天前
|
前端开发 Java Spring
自定义 Spring MVC Controller 方法参数处理
【6月更文挑战第3天】在 Spring MVC Controller 的方法参数,Spring 会自动为我们注入一些特殊的参数值,比如 HttpServletRequest、HttpServletResponse 等对象,或者 HTTP 请求参数。
49 0

推荐镜像

更多