深入分析 Spring 中 Bean 名称的加载机制

目录

前言

通过前文:《深入分析-Spring BeanDefinition构造元信息》一文我们可以了解到:Spring Framework共有三种方式可以定义Bean,分别为:XML配置文件、注解、Java配置类, 从Spring Framework 3.0(2019年12月发布)版本开始推荐使用注解来定义Bean,而不是XML配置文件,因此,本文的重点是放在探索Spring Framework如何从使用注解定义的Bean元数据中获取到Bean的名称。

AnnotationBeanNameGenerator类的介绍

作用

AnnotationBeanNameGenerator在Spring Framework中用于生成基于注解的Bean名称,其主要作用是根据指定的注解信息,生成符合规范的Bean名称。它在Spring容器初始化时,通过扫描注解配置的组件类,并且根据其定义的命名规则生成Bean名称,然后将这些名称与对应的Bean实例关联起来。

如:你在工程中使用@Service注解定义了一个HelloService的Bean,那么你在启动SpringBoot工程后,该Bean会以beanName为“helloService”注入到Spring容器中。

/**
 * @author 公众号:种棵代码技术树
 */
@Service
public class HelloService {

    private final Logger logger = LoggerFactory.getLogger(HelloService.class);

    private final HelloAsyncService helloAsyncService;

    /**
     * Instantiates a new Hello service.
     *
     * @param helloAsyncService the hello async service
     */
    public HelloService(HelloAsyncService helloAsyncService) {
        this.helloAsyncService = helloAsyncService;
    }
}

计算代码中用于返回Bean名称的StringUtils.uncapitalizeAsProperty(shortClassName);即可得到:

同时还可以看到上一篇文章:《深入分析-Spring BeanDefinition构造元信息》中有关BeanDefinition的内容,如:Bean的全限定类名和作用域。

继承关系

AnnotationBeanNameGeneratorBeanNameGenerator接口的实现类,该接口的主要功能是为给定的Bean生成唯一的名称。目前,BeanNameGenerator接口有两个实现,除了本篇文章介绍的AnnotationBeanNameGenerator外,还有默认实现类DefaultBeanNameGeneratorDefaultBeanNameGenerator主要用于处理通过XML文件定义的Bean,为其自动生成名称。FullyQualifiedAnnotationBeanNameGenerator继承自AnnotationBeanNameGenerator,同样属于BeanNameGenerator接口的实现类,该类覆写了AnnotationBeanNameGeneratorbuildDefaultBeanName()方法,作用是使用注解类型和注解元数据,结合其他信息(例如类名、包名等),生成带有完全限定名的Bean名称。

源码结构

  1. 类声明部分:定义了AnnotationBeanNameGenerator类,并实现了BeanNameGenerator接口。
  2. 日志处理部分:定义了一个静态的Log对象logger,用于记录日志信息。
  3. Bean名称生成方法:实现了generateBeanName()方法,用于根据给定的Bean定义生成Bean名称。如果Bean定义是一个带注解的Bean定义,会调用determineBeanNameFromAnnotation()方法来基于注解生成Bean名称;否则会使用默认的Bean名称生成策略buildDefaultBeanName()方法来生成Bean名称。
  4. 注解处理部分:定义了determineBeanNameFromAnnotation()方法和isStereotypeWithNameValue()方法,用于判断是否需要处理注解元数据,从中获取Bean名称。
  5. 默认Bean名称生成策略部分:实现了buildDefaultBeanName()方法和getComponentAnnotation()方法,用于生成默认的Bean名称。
  6. 其他辅助方法:例如isStereotypeWithNameValue()方法和getComponentAnnotation()方法,用于支持上述方法的实现。

当@value配置值时:

@Service(value = "HelloService")

实现原理

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    if (definition instanceof AnnotatedBeanDefinition) {
        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);
}

如果当前BeanDefinitionAnnotationBeanNameGenerator类型,则尝试从注解中获取Bean的名称,如果找了BeanName,则直接返回。

	/**
	 * Derive a bean name from one of the annotations on the class.
	 * @param annotatedDef the annotation-aware bean definition
	 * @return the bean name, or {@code null} if none is found
	 */
	@Nullable
	protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
		AnnotationMetadata amd = annotatedDef.getMetadata();
		Set<String> types = amd.getAnnotationTypes();
		String beanName = null;
		for (String type : types) {
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);
			if (attributes != null) {
				Set<String> metaTypes = this.metaAnnotationTypesCache.computeIfAbsent(type, key -> {
					Set<String> result = amd.getMetaAnnotationTypes(key);
					return (result.isEmpty() ? Collections.emptySet() : result);
				});
				if (isStereotypeWithNameValue(type, metaTypes, attributes)) {
					Object value = attributes.get("value");
					if (value instanceof String) {
						String strVal = (String) value;
						if (StringUtils.hasLength(strVal)) {
							if (beanName != null && !strVal.equals(beanName)) {
								throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
										"component names: '" + beanName + "' versus '" + strVal + "'");
							}
							beanName = strVal;
						}
					}
				}
			}
		}
		return beanName;
	}

从某个注解中获取Bean名称,该方法是主要的BeanName获取逻辑,其大体逻辑为:

  1. 从Bean的元注解获取数据,遍历源数据中的数据。
  2. 获取元数据的类型,如果元数据已被注入到容器池中,则直接返回结果。
  3. 如果注解是否允许通过@Value注解来获取bean名称,如果可以通过@Value注解获取Bean名称,则使用元数据中@Value定义的信息为Bean名称,最后返回,放入如果元数据中未配置@Value相关数据,则返回null。
  4. 当然,@Value中是可以不配置信息的,此时执行fallBack,即调用 buildDefaultBeanName 方法生成一个默认的 Bean 名称,并返回。
	/**
	 * Derive a default bean name from the given bean definition.
	 * <p>The default implementation delegates to {@link #buildDefaultBeanName(BeanDefinition)}.
	 * @param definition the bean definition to build a bean name for
	 * @param registry the registry that the given bean definition is being registered with
	 * @return the default bean name (never {@code null})
	 */
	protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return buildDefaultBeanName(definition);
	}


	/**
	 * Derive a default bean name from the given bean definition.
	 * <p>The default implementation simply builds a decapitalized version
	 * of the short class name: e.g. "mypackage.MyJdbcDao" -> "myJdbcDao".
	 * <p>Note that inner classes will thus have names of the form
	 * "outerClassName.InnerClassName", which because of the period in the
	 * name may be an issue if you are autowiring by name.
	 * @param definition the bean definition to build a bean name for
	 * @return the default bean name (never {@code null})
	 */
	protected String buildDefaultBeanName(BeanDefinition definition) {
		String beanClassName = definition.getBeanClassName();
		Assert.state(beanClassName != null, "No bean class name set");
		String shortClassName = ClassUtils.getShortName(beanClassName);
		return Introspector.decapitalize(shortClassName);
	}

该方法的作用是:从给定的 Bean 定义派生缺省 Bean 名称。

默认实现只是构建短类名的去大写版本:例如“mypackage.MyJdbcDao“ -> ”myJdbcDao”。

经过以上代码,每个Bean均会获得其对应的BeanName。

总结

AnnotationBeanNameGenerator 的优点有:

  1. 自动生成唯一的 Bean 名称,避免了手动命名时出现重名的情况;
  2. 提高了代码可读性和可维护性,因为通过注解来指定 Bean 名称可以更直观地表达 Bean 的含义;
  3. 灵活性较高,支持多种类型的注解,例如 @Service、@Component、@Repository 等。

AnnotationBeanNameGenerator 的缺点则是:

  1. 如果注解中未指定 Bean 名称,该生成器会默认使用类名作为 Bean 名称,这可能导致出现多个类名相同的 Bean,需要特别注意;
  2. 由于生成的 Bean 名称是自动生成的,因此有时可能不太符合开发者的命名习惯,需要手动修改 Bean 的名称。

AnnotationBeanNameGenerator 在实际开发中可以帮助开发者快速生成唯一的 Bean 名称,提高代码的可读性和可维护性,但需要特别注意类名重复以及自动生成的名称是否符合需求。

后续内容文章持续更新中…

近期发布。


关于我

👋🏻你好,我是Debug.c。微信公众号:种棵代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。

🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。

📞如果您对我感兴趣,请联系我。

若有收获,就点个赞吧,喜欢原图请私信我。

wallhaven-exrqrr.jpg

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

node-sass@4.7.2 postinstall: `node scripts/build.js`

Can‘t find Python executable “D:\Python36\python.EXE“, you can set the PYTHON env variable.-CSDN博客 gyp ERR! build error gyp ERR! stack Error: C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe failed with exit code: 1 gyp ERR! stack at Chil…

解密Mybatis-Plus:优雅简化你的数据访问层!

目录 1、引言 2、什么是Mybatis-Plus 3、Mybatis-Plus的特点和优势 4、安装和配置Mybatis-Plus 5、使用Mybatis-Plus进行数据库操作 6、Mybatis-Plus的高级功能 7、Mybatis-Plus的扩展和插件 8、与Spring Boot集成 9、结语 1、引言 Mybatis-Plus是一个强大而优雅的Jav…

阿里云ingress配置时间超时的参数

一、背景 在使用阿里云k8s集群的时候&#xff0c;内网API网关&#xff0c;刚开始是用的是Nginx&#xff0c;后面又搭建了ingress。 区别于nginx配置&#xff0c;ingress又该怎么设置参数呢&#xff1f;比如http超时时间等等。 本文会先梳理nginx是如何配置&#xff0c;再对比…

Elasticsearch:是时候离开了! - 在 Elasticsearch 文档上使用 TTL

作者&#xff1a;来自 Elastic David Pilato 想象一下&#xff0c;圣诞老人必须向世界上所有的孩子们分发礼物。 他有很多工作要做&#xff0c;他需要保持高效。 他有一份所有孩子的名单&#xff0c;并且知道他们住在哪里。 他很可能会将礼物按区域分组&#xff0c;然后再交付。…

Qt QSQlite数据库插入字符串中存在单个双引号或单个单引号解决方案

1. 前言 当进行数据库写入或更新时&#xff0c;有时会遇到存在字符串中包含单个双引号或者单引号。 2. 单引号和双引号""作用 在数据库中&#xff0c;字符串常量时需要用一对英文单引号或英文双引号""将字符串常量括起来。 比如&#xff1a; select * …

stable-diffusion 学习笔记

从效果看Stable Diffusion中的采样方法 参考&#xff1a;Ai 绘图日常 篇二&#xff1a;从效果看Stable Diffusion中的采样方法_软件应用_什么值得买 大概示例&#xff1a;

Java Web课设——个人博客(双端系统)

项目演示 先看看演示视频吧 演示图 简单介绍 个人博客管理系统采用Springboot2.4.5框架开发&#xff0c;是标准的MVC模式&#xff0c;将这个系统划分为View层、Controller层、Service层、DAO层和持久层五层。其中&#xff0c;Spring MVC负责请求的转发和视图管理&#xff0c;S…

蓝桥杯单片机组备赛——数码管动态显示

✨文章内容会不断优化&#xff0c;如果你感兴趣的话&#xff0c;欢迎点藏收藏关注我哟 &#x1f9e8;如果文章有哪里看不懂的欢迎评论区或私信留言&#xff0c;我会及时回复的 ⏰如果文章出现错误&#xff0c;欢迎指正&#xff0c;看到后我会马上改正 文章目录 一、动态显示原理…

Java SE入门及基础(10)

switch选择结构 1. 概念 switch 表示开关的意思&#xff0c;为了帮助理解&#xff0c;下面以线路为例&#xff0c;进行解释说明 上图中表示一条带有多个开关的线路&#xff0c;当开关打开时&#xff0c;该开关所控制的灯即被点亮。 2. 语法规则 switch ( 表达式 ){ //…

SECS/GEM的变量SVID是什么?JAVA SECS通信 JAVA与SECS集成资料大全JAVA开发SECS快速入门资料

Java与SECS基础通信 Java实现SECS指令S2F17获取时间 Java实现SECS指令 S10F3 终端单个显示例子 工艺配方管理S7FX Java实现SECS指令 S5F1报警/取消报警上传 实例源码及DEMO请查阅 变量可以是设备的状态信息 定义&#xff1a; 此功能允许主机查询设备数据变量&#x…

C#基于ScottPlot进行可视化

前言 上一篇文章跟大家分享了用NumSharp实现简单的线性回归&#xff0c;但是没有进行可视化&#xff0c;可能对拟合的过程没有直观的感受&#xff0c;因此今天跟大家介绍一下使用C#基于Scottplot进行可视化&#xff0c;当然Python的代码&#xff0c;我也会同步进行可视化。 P…

虚幻引擎nDisplay教程:如何同步nDisplay节点与Switchboard + Helix Core

对于使用大型LED屏幕进行拍摄的虚拟制作团队来说&#xff0c;虚幻&#xff08;Unreal&#xff09;的nDisplay是一个重要的工具。但是&#xff0c;在nDisplay中将正确版本的文件发送到每个节点会非常耗时。立即阅读本文&#xff0c;您将了解到如何使用Perforce Helix Core版本控…

【b站咸虾米】新课uniapp零基础入门到项目打包(微信小程序/H5/vue/安卓apk)全掌握

课程地址&#xff1a;【新课uniapp零基础入门到项目打包&#xff08;微信小程序/H5/vue/安卓apk&#xff09;全掌握】 https://www.bilibili.com/video/BV1mT411K7nW/?p12&share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 三、vue语法 继续回到官…

Python之jieba分词相关介绍

1.jieba分词的安装 直接在cmd窗口当中pip install即可 2.jieba分词的介绍 jieba分词是目前比较好的中文分词组件之一&#xff0c;jieba分词支持三种模式的分词(精确模式、全模式、搜索引擎模式)&#xff0c;并且支持自定义词典(这一点在特定的领域很重要&#xff0c;有时候…

VMware workstation搭建与安装AlmaLinux-9.2虚拟机

VMware workstation搭建与安装AlmaLinux-9.2虚拟机 适用于需要在VMware workstation平台安装AlmaLinux-9.2&#xff08;最小化安装、无图形化界面&#xff09;虚拟机。 1. 安装准备 1.1 安装平台 Windows 11 1.2. 软件信息 软件名称软件版本安装路径VMware-workstation 1…

【前端素材】bootstrap5实现美食餐饮网站RegFood

一、需求分析 美食餐饮网站是指专门提供关于美食和餐饮的信息、服务和资源的在线平台。这类网站通常提供以下功能&#xff1a; 餐厅搜索和预订&#xff1a;用户可以在网站上搜索附近的餐厅&#xff0c;并预订桌位。网站会提供餐厅的详细信息&#xff0c;包括菜单、地址、电话号…

Jmeter 性能-监控服务器

Jmeter监控Linux需要三个文件 JMeterPlugins-Extras.jar (包&#xff1a;JMeterPlugins-Extras-1.4.0.zip) JMeterPlugins-Standard.jar (包&#xff1a;JMeterPlugins-Standard-1.4.0.zip) ServerAgent-2.2.3.zip 1、Jemter 安装插件 在插件管理中心的搜索Servers Perform…

JDK介绍

JDK(Java Development Kit)是Sun Microsystems针对Java开发员的产品。自从Java推出以来&#xff0c;JDK已经成为使用最广泛的Java SDK&#xff08;Software development kit&#xff09;&#xff0c;JDK是一个写Java的applet和应用程序的程序开发环境。它由一个处于操作系统层之…

vue3-计算属性

计算属性 模板中的表达式虽然方便&#xff0c;但也只能用来做简单的操作。如果在模板中写太多逻辑&#xff0c;会让模板变得臃肿&#xff0c;难以维护。 根据作者今年是否看过书展示不同信息 <script lang"ts" setup> import { ref, reactive } from "…

C++11 14 17内存管理

智能指针 unique_ptr 初始化 访问和移动赋值 重置和移动内存资源 自定义删除器 shared_ptr 原理 自定义删除器 分配器allocator和new重载 new表达式原理 operator new delete placement new new (buf) 是一种 "placement new" 的使用方式&#xff0c;它允许在已…