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

相关文章
|
4天前
|
Java 应用服务中间件 Spring
解析Spring Boot自动装配的原理与机制
解析Spring Boot自动装配的原理与机制
15 4
|
2天前
|
监控 Java API
【Spring Boot】深入解密Spring Boot日志:最佳实践与策略解析
【Spring Boot】深入解密Spring Boot日志:最佳实践与策略解析
11 1
|
2天前
|
XML Java 数据格式
Spring容器启动源码解析
Spring容器启动源码解析
|
2天前
|
前端开发 JavaScript Java
【Spring Boot】 深入理解Spring Boot拦截器:自定义设计与实现全攻略
【Spring Boot】 深入理解Spring Boot拦截器:自定义设计与实现全攻略
5 0
|
2天前
|
JavaScript Java 数据库连接
【Spring Boot】掌握Spring Boot:深入解析配置文件的使用与管理
【Spring Boot】掌握Spring Boot:深入解析配置文件的使用与管理
7 0
|
3天前
|
缓存 安全 Java
【权限管理系统】Spring security(三)---认证过程(原理解析,demo)
【权限管理系统】Spring security(三)---认证过程(原理解析,demo)
|
4天前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
14 3
|
2天前
|
机器学习/深度学习 缓存 算法
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
|
3天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析
|
4天前
|
XML Java 数据格式
深度解析 Spring 源码:揭秘 BeanFactory 之谜
深度解析 Spring 源码:揭秘 BeanFactory 之谜
11 1

推荐镜像

更多