Spring BeanName自动生成原理

news2025/1/16 5:39:19

先看代码演示

项目先定义一个User类

public class User {
    private String name;
    @Override
    public String toString() {
        return "User{" + "name='" + name + '\'' + '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

接着配置文件定义bean,注意这里的bean标签都没有设置name

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">
    <bean class="org.example.User">
        <property name="name" value="u1" />
    </bean>
    <bean class="org.example.User">
        <property name="name" value="u2" />
    </bean>
    <bean class="org.example.User">
        <property name="name" value="u3" />
    </bean>
</beans>

在代码中获取bean

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        User u1 = ctx.getBean("org.example.User", User.class);
        User u2 = ctx.getBean("org.example.User#1", User.class);
        User u3 = ctx.getBean("org.example.User#2", User.class);
        System.out.println("u1=" + u1);
        System.out.println("u2=" + u2);
        System.out.println("u3=" + u3);
    }
}

输出

u1=User{name='u1'}
u2=User{name='u2'}
u3=User{name='u3'}

问题:ctx.getBean第一个参数是bean的name,但是beans.xml中并没有设置bean标签的name或者id属性,为什么可以顺利拿到呢?

源码分析

在 Spring 中,提供了 BeanNameGenerator 用来生成 BeanName:

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 regist){}
}

在这里插入图片描述

  • DefaultBeanNameGenerator:XML 配置中,默认的 BeanName 就是在这个中自动生成的。
  • AnnotationBeanNameGenerator:Java 配置中,如果使用了 @Component 等注解标记的 Bean,没有设置默认的名称,则通过这个来生成默认的 BeanName。
public class DefaultBeanNameGenerator implements BeanNameGenerator {
    public static final DefaultBeanNameGenerator INSTANCE = new DefaultBeanNameGenerator();

    public DefaultBeanNameGenerator() {
    }

    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
    }
}

可以看到,generateBeanName 这个方法实际上代理了 BeanDefinitionReaderUtils.generateBeanName 方法的执行,真正的 BeanName 的生成是在这个方法中完成的。

public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException {
        // 这里就是获取到 XML 中 bean 标签里边配置的 class 属性的值
        String generatedBeanName = definition.getBeanClassName();
        // 判断是否有 class 这个属性值,如果没有的话,则在 parnetName 存在的情况下,
 		// 使用 parentName+$child 来作为 生成的 beanName
        if (generatedBeanName == null) {
            if (definition.getParentName() != null) {
                generatedBeanName = definition.getParentName() + "$child";
                // 如果没有 parentName,则尝试使用 factoryBeanName
            } else if (definition.getFactoryBeanName() != null) {
                generatedBeanName = definition.getFactoryBeanName() + "$created";
            }
        }
	    // 如果经过上面的处理,还是没有 generatedBeanName,那么就要抛异常了
        if (!StringUtils.hasText(generatedBeanName)) {
            throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither 'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
        } else {
            // 我们的默认 BeanName,实际上是在uniqueBeanName这个方法中生成的
            return isInnerBean ? generatedBeanName + "#" + ObjectUtils.getIdentityHexString(definition) : uniqueBeanName(generatedBeanName, registry);
        }
    }

    public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {
        String id = beanName;
        int counter = -1;
        // 所有这里是把类的全路径和 # 拼在一起
        for(String prefix = beanName + "#"; counter == -1 || registry.containsBeanDefinition(id); id = prefix + counter) {
            ++counter;
        }
		//最终生成的 id 就是 org.example.User#0
        return id;
    }

由此可以看到,默认的 BeanName 就是类的全路径+#+序列号,如 org.example.User#0 、 org.example.User#1 。

一个新的问题

对于序列号为 0 的 BeanName,似乎还有一个默认的名称,就是类的全路径,不加任何序列号?
上面这个生成 BeanName 的方法是在 BeanDefinitionParserDelegate#parseBeanDefinitionElement 方法中执行的,具体的逻辑如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        。。。。
        if (beanDefinition != null) {
           // 当前没有配置 BeanName,即 bean 标签中没有 id 或者 name 属性
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                    } else {
                        //这个地方,最终会调用到上面的逻辑去生成 BeanName
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        // 获取一个类的全路径 org.javaboy.demo.User
                        String beanClassName = beanDefinition.getBeanClassName();
                        //!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)表示beanClassName还没有作为一个beanName注册到Spring容器中
                        if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
                    }
                } catch (Exception var9) {
                    this.error(var9.getMessage(), ele);
                    return null;
                }
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        } else {
            return null;
        }
    }

这就是为什么默认生成的 BeanName 中, #0 可有可无的原因。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/926775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ensp启动设备AR1失败,错误代码: 40.详细:启动失败!串口登录端口号2000 冲突请指定新的端口。

1.重新打开ensp,点击注册设备&#xff0c;勾选全部&#xff0c;注册 2.关闭虚拟化&#xff0c;输入cmd,管理员身份运行命令提示符 输入一下代码&#xff0c;回车&#xff0c;然后重启电脑 bcdedit /set hypervisorlaunchtype off 3.重装ensp及其组件 eNSP下载链接&#xff1a…

集成学习:Bagging, Boosting,Stacking

目录 集成学习 一、bagging 二、boosting Bagging VS Boosting 1.1 集成学习是什么&#xff1f; Bagging Boosting Stacking 总结 集成学习 好比人做出一个决策时&#xff0c;会从不同方面&#xff0c;不同角度&#xff0c;不同层次去思考&#xff08;多个自我&am…

常见前端面试之VUE面试题汇总八

22. Vue 子组件和父组件执行顺序 加载渲染过程&#xff1a; 1.父组件 beforeCreate 2.父组件 created 3.父组件 beforeMount 4.子组件 beforeCreate 5.子组件 created 6.子组件 beforeMount 7.子组件 mounted 8.父组件 mounted 更新过程&#xff1a; 1. 父组件 befor…

【LeetCode-中等题】189. 轮转数组

题目 题解一&#xff1a;开辟数组 取模运算寻找位置(ik)mod n 新位置 思路&#xff1a;通过&#xff0c;开辟数组 取模运算寻找新位置------位置(ik)mod n 新位置 int[] newNums new int[nums.length];for(int i 0;i<nums.length;i){newNums[(ik)%nums.length] nums[i…

京东面试题:java中static 应用场景有哪些?

大家好&#xff0c;我是你们的小米&#xff01;今天我要和大家聊一个在Java中非常重要的关键词——static&#xff01;在京东的面试中&#xff0c;经常会遇到与static相关的问题&#xff0c;而我们今天就要揭开它的神秘面纱&#xff0c;深入探讨它在Java中的应用场景。无论你是…

IoTDB 集群环境搭建

什么是IoTDB IoTDB&#xff08;Internet of Things Database&#xff09;是一个专门设计用于存储和管理大规模物联网&#xff08;IoT&#xff09;数据的开源时序数据库系统。它专注于高效地存储、查询和分析时间序列数据&#xff0c;特别适用于物联网应用中的大量实时数据。 Io…

1146:判断字符串是否为回文

#include <iostream> #include <string> using namespace std; int main() {string str;// 输入一个字符串cin>>str;int nstr.length();for(int i0;i<n;i){if(str[i]!str[n-1-i]){cout<<"no"; // 如果发现不对称的字符&#xff0c;则输出…

系统学习Linux-LVS集群

集群概述 负载均衡技术类型 四层负载均衡器 也称为 4 层交换机&#xff0c;主要通过分析 IP 层及 TCP/UDP 层的流量实现基于 IP 加端口的负载均衡&#xff0c;如常见的 LVS、F5 等&#xff1b; 七层负载均衡器 也称为 7 层交换机&#xff0c;位于 OSI 的最高层&#xff0c;即…

第二讲Java基本语法(变量、数据类型、运算符)

一、前言导读 上一讲,我们安装java的开发工具idea,并且简单介绍如何使用,初步认识了Java的helloworld,我们写了第一行代码,有了初步的印象,接下来我们将真正展开对于java的了解,从这一讲开始,请大家做好笔记,改背的背。为什么说Java是一门编程语言呢,主要是他跟英语一…

控制疫情蔓延嵌入式物联网能帮大忙

联合国所订定之永续发展目标之一&#xff0c;便是针对防治传染病的蔓延做好准备。在新型冠状病毒(COVID-19)流行期间&#xff0c;防疫已成为当前最重要目标&#xff0c;科技在对抗传染病方面扮演重要角色&#xff0c;而物联网(IoT)相关技术正是我们重要的防疫武器──降低成本、…

网络渗透day2-Windows服务器服务管理相关

1.在Windows Server中&#xff0c;用于监视网络连接和流量的工具是&#xff1f; A.Event Viewer B.Performance Monitor C.Task Scheduler D.Resource Monitor 正确答案&#xff1a;D 你的答案&#xff1a;B 解析&#xff1a; 答案解析&#xff1a;Resource Monitor用于监…

【Jetpack】Navigation 导航组件 ④ ( Fragment 跳转中使用 safe args 安全传递参数 )

文章目录 一、页面跳转间的传统的数据传递方式1、传统的数据传递方式 - Bundle 传递数据1、Navigation 组件中的 Bundle 数据传递2、传统数据传递实现步骤3、FragmentA 完整代码示例4、FragmentB 完整代码示例5、执行结果 2、使用 Bundle 传递数据安全性差 二、页面跳转间的传统…

Linux详解(包含Linux安装教程)

文章目录 Linux详解一、安装Linux操作系统VMware介绍安装虚拟机VMware下载centos 7系统安装centos 7系统 二、Linux基础命令Linux的目录结构Linux命令入门目录切换相关命令 cd、pwd相对路径、绝对路径和特殊路径符掌握通过mkdir命令创建文件夹文件操作命令touch、cat、more文件…

APT80DQ60BG-ASEMI新能源功率器件APT80DQ60BG

编辑&#xff1a;ll APT80DQ60BG-ASEMI新能源功率器件APT80DQ60BG 型号&#xff1a;APT80DQ60BG 品牌&#xff1a;ASEMI 芯片个数&#xff1a;2 封装&#xff1a;TO-3P 恢复时间&#xff1a;&#xff1e;50ns 工作温度&#xff1a;-55C~150C 浪涌电流&#xff1a;600A …

Python“牵手”易贝(Ebay)商品列表数据,关键词搜索ebayAPI接口数据,ebayAPI接口申请指南

Ebay平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c; EbayAPI接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问Ebay平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现Ebay平…

Etsy如何安全养店?7个因素你要知道

Etsy是全球大型的创意市场电商平台&#xff0c;很多跨境玩家在开店之后&#xff0c;兴致冲冲开始上架&#xff0c;结果流量没有不说&#xff0c;很快店铺就被封禁。注意了&#xff01;Etsy也是一个规则比较严格的平台&#xff0c;想要做好Etsy&#xff0c;一定要看好下面这7个因…

【Java 动态数据统计图】前后端对接数据格式(Map返回数组格式数据)六(120)

说明&#xff1a; 前端使用&#xff1a;vue3.0 前后端对接数据格式&#xff1a;无非就是前端把后端返回的数据处理为自己想要的格式&#xff0c;或者&#xff0c;后端给前端处理好想要的格式&#xff1b; 针对前后端的柱状图&#xff0c;趋势图等数据对接&#xff0c;前端一般需…

Java网络编程(二)经典案例[粘包拆包]

粘包拆包 概述 TCP是面向流的协议,TCP在网络上传输的数据就是一连串的数据,完全没有分界线。 TCP协议的底层并不了解上层业务的具体定义,它会根据TCP缓冲区的实际情况进行包的划分。 在业务层面认为一个完整的包可能会被TCP拆分成多个小包进行发送,也可能把多个小的包封装成一…

Qt(C++)计算一段程序执行经过的时间

一、前言 在许多应用程序和系统中,需要对经过的时间进行计算和记录。例如 可能想要测量某个操作的执行时间,或者记录一个过程中经过的时间以进行性能分析。在这些场景下,准确地计时是非常重要的。 Qt提供了一个功能强大的计时器类QElapsedTimer,可以方便地记录经过的时间…

SSL核心概念 SSL类型级别

SSL&#xff1a;SSL&#xff08;Secure Sockets Layer&#xff09;即安全套接层&#xff0c;及其继任者传输层安全&#xff08;Transport Layer Security&#xff0c;TLS&#xff09;是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。 H…