Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入分析 Spring 中 Bean 名称的加载机制

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

作者头像
关忆北.
发布于 2024-01-14 02:20:27
发布于 2024-01-14 02:20:27
29500
代码可运行
举报
文章被收录于专栏:关忆北.关忆北.
运行总次数:0
代码可运行

目录

前言

通过前文:《深入分析-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容器中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @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")

实现原理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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,则直接返回。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 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 名称,并返回。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	/**
	 * 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 名称,提高代码的可读性和可维护性,但需要特别注意类名重复以及自动生成的名称是否符合需求。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Spring 中三种 BeanName 生成器!
无论我们是通过 XML 文件,还是 Java 代码,亦或是包扫描的方式去注册 Bean,都可以不设置 BeanName,而 Spring 均会为之提供默认的 beanName,今天我们就来看看 Spring 中三种处理不同情况的 beanName 生成器。
江南一点雨
2023/09/20
4600
Spring 中三种 BeanName 生成器!
聊聊spring bean名称命名的那些事儿
用了多年spring,一直想当然把spring默认的beanName当成是类名的首字母小写,比如HelloService其beanName为helloService。直到有天对接了供方厂商的接口,他有个类形如ABService,于是用
lyb-geek
2021/06/24
1.3K0
聊聊spring bean名称命名的那些事儿
聊聊Spring的bean覆盖(存在同名name/id问题),介绍Spring名称生成策略接口BeanNameGenerator【享学Spring】
众所周知,Spring容器可以简单粗暴的把它理解为一个大大的Map,存储着容器所管理的所有的单实例对象。我们从使用getBean(String beanName)方法,根据bean名称就能获得容器内唯一的Bean实例就能“证明”到这一点。
YourBatman
2019/09/03
15.3K3
聊聊Spring的bean覆盖(存在同名name/id问题),介绍Spring名称生成策略接口BeanNameGenerator【享学Spring】
团队协作中如何处理ConflictingBeanDefinitionException异常
当使用Spring框架进行Java应用程序开发时,可能会遇到ConflictingBeanDefinitionException异常。
关忆北.
2023/10/11
9680
团队协作中如何处理ConflictingBeanDefinitionException异常
Spring - AnnotationBeanNameGenerator Bean命名规则
文章目录 org.springframework.context.annotation.AnnotationBeanNameGenerator org.springframework.context.annotation.AnnotationBeanNameGenerator public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instan
小小工匠
2022/12/18
4530
Spring - AnnotationBeanNameGenerator Bean命名规则
Spring IoC 源码分析 (基于注解) (三)之 Bean的解析与注册
在上一篇文章Spring IoC 源码分析 (基于注解) 之 包扫描中,我们介绍了Spring基于注解扫描包获取bean的过程。本文我们将一起探讨spring对bean解析,并注册到IOC容器的过程。
周同学
2019/08/29
1.1K0
Spring IoC 源码分析 (基于注解) (三)之 Bean的解析与注册
Spring高手之路10——解锁Spring组件扫描的新视角
首先,我们将探讨一些Spring框架中IOC(Inversion of Control)的高级特性,特别是组件扫描的相关知识。组件扫描是Spring框架中一个重要的特性,它可以自动检测并实例化带有特定注解(如@Component, @Service, @Controller等)的类,并将它们注册为Spring上下文中的bean。这里,我们会通过一些详细的例子来阐明这些概念,并且展示如何在实际的代码中使用这些特性。
砖业洋__
2023/07/28
9870
Spring高手之路10——解锁Spring组件扫描的新视角
【小家Spring】Spring IoC容器中核心定义之------BeanDefinition深入分析(RootBeanDefinition、ChildBeanDefinition...)
在前面分析Spring IoC容器的时候,贯穿全文的一个概念:Bean定义信息。它是Spring容器的一个核心概念,那么本文就深入分析一下BeanDefinition这个接口(类)。
YourBatman
2019/09/03
7.5K1
【小家Spring】Spring IoC容器中核心定义之------BeanDefinition深入分析(RootBeanDefinition、ChildBeanDefinition...)
Spring5.0源码深度解析之Spring基于注解启动流程分析
下面来分析下,通过AnnotationConfigurationContext类图可知:
须臾之余
2019/07/30
1.2K0
Spring5.0源码深度解析之Spring基于注解启动流程分析
彻底讲清Spring Bean
Spring管理的这些bean由配置元数据创建,如被@Bean注解。Spring 内部又是如何存储这些信息的?
JavaEdge
2021/02/23
5460
彻底讲清Spring Bean
Spring源码:bean的生命周期(一)
本节将正式介绍Spring源码细节,将讲解Bean生命周期。请注意,虽然我们不希望过于繁琐地理解Spring源码,但也不要认为Spring源码很简单。在本节中,我们将主要讲解Spring 5.3.10版本的源代码。如果您看到的代码与我讲解的不同,也没有关系,因为其中的原理和业务逻辑基本相同。为了更好地理解,我们将先讲解Bean的生命周期,再讲解Spring的启动原理和流程,因为启动是准备工作的一部分。
努力的小雨
2024/04/30
1580
从Spring源码探究IOC初始化流程
本文是基于注解的IOC初始化,不是XML!!! 代码的含义我都以注释的形式写在代码块中了,请放心查阅
向着百万年薪努力的小赵
2022/12/02
5170
逐行阅读Spring5.X源码(七)扫描和注册神器 ConfigurationClassPostProcessor ,学此类者,胜过学九阳神功!胆小勿入!
ConfigurationClassPostProcessor是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。 完成扫描啊!你说重要不重要!!!
源码之路
2020/09/04
7650
逐行阅读Spring5.X源码(七)扫描和注册神器 ConfigurationClassPostProcessor ,学此类者,胜过学九阳神功!胆小勿入!
Mybatis整合Spring
前面我们已经知道如果mybatis中进行查询时,在getMapper之后,会执行查询动作,而执行查询动作的时候,会触发动态代理操作,这个过程是在binding中完成的,即此时会触发MapperMethod#invoke操作->mapperMethod.execute(sqlSession, args),这个操作的过程中,会执行到具体的查询操作,而具体的查询动作中,我们可以此时会走到一个sqlCommandType的判断中,也即case语句中:
路行的亚洲
2021/03/04
9440
你有没有掉进去过这些Spring的“陷阱“(上)
使用IDEA创建一个Spring Boot工程spring-traps,选择基本依赖
RiemannHypothesis
2022/08/19
2390
你有没有掉进去过这些Spring的“陷阱“(上)
Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor
具体原因,因为在postProcessBeanFactory方法中对Full类型(即被@Configuration修饰的配置类)的配置类进行了动态代理
Java微观世界
2025/01/21
4960
Spring源码解析(六):bean工厂后置处理器ConfigurationClassPostProcessor
Spring源码解析之BeanFactoryPostProcessor(二)
上一章,我们介绍了在AnnotationConfigApplicationContext初始化的时候,会创建AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner两个对象:
huofo
2022/03/18
2680
Spring源码解析之BeanFactoryPostProcessor(二)
你知道Spring是怎么解析配置类的吗?
这个流程图会随着我们的学习不断的变得越来越详细,也会越来越复杂,希望在这个过程中我们都能朝着精通Spring的目标不断前进!
程序员DMZ
2020/07/09
1.6K0
Spring5源码分析之@Configuration注解的详解。希望读者能够耐着性子看完
对于Spring创建Bean的方式我相信大家 并不陌生,绝大数同学其实都知道Spring最初就是通过xml的方式去初始化Bean并完成依赖注入的工作,但是在Spring3.0之后,在spring framework模块中提供了了@Confirguration这个注解,并通过搭配@Bean等注解,可以完全不依赖xml配置,在运行时完成Bean的创建和初始化工作。
@派大星
2023/06/28
9580
Spring5源码分析之@Configuration注解的详解。希望读者能够耐着性子看完
源码揭秘!Spring中Bean扫描的原理!
Spring和MyBatis整合的时候用到的Bean扫描是它Spring本身提供的。这一篇文章就写一下Spring是如何实现Bean扫描的。 不得不说Bean扫描是一个很重要的技术,在SpringMVC中的Controller扫描,和SpringBoot中的Bean扫描,Component扫描,Configuration扫描,原理应该都是由这个实现的。
PHP开发工程师
2022/04/07
6290
源码揭秘!Spring中Bean扫描的原理!
推荐阅读
相关推荐
Spring 中三种 BeanName 生成器!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验