一文带你了解Spring中Bean名称加载机制

 更新时间:2024年01月14日 09:13:00   作者:FirstMrRight  
这篇文章主要给大家介绍了Spring Framework如何从使用注解定义的Bean元数据中获取到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名称。

源码结构

  • 类声明部分:定义了AnnotationBeanNameGenerator类,并实现了BeanNameGenerator接口。
  • 日志处理部分:定义了一个静态的Log对象logger,用于记录日志信息。
  • Bean名称生成方法:实现了generateBeanName()方法,用于根据给定的Bean定义生成Bean名称。如果Bean定义是一个带注解的Bean定义,会调用determineBeanNameFromAnnotation()方法来基于注解生成Bean名称;否则会使用默认的Bean名称生成策略buildDefaultBeanName()方法来生成Bean名称。
  • 注解处理部分:定义了determineBeanNameFromAnnotation()方法和isStereotypeWithNameValue()方法,用于判断是否需要处理注解元数据,从中获取Bean名称。
  • 默认Bean名称生成策略部分:实现了buildDefaultBeanName()方法和getComponentAnnotation()方法,用于生成默认的Bean名称。
  • 其他辅助方法:例如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获取逻辑,其大体逻辑为:

  • 从Bean的元注解获取数据,遍历源数据中的数据。
  • 获取元数据的类型,如果元数据已被注入到容器池中,则直接返回结果。
  • 如果注解是否允许通过@Value注解来获取bean名称,如果可以通过@Value注解获取Bean名称,则使用元数据中@Value定义的信息为Bean名称,最后返回,放入如果元数据中未配置@Value相关数据,则返回null。
  • 当然,@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 的优点有:

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

AnnotationBeanNameGenerator 的缺点则是:

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

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

最后

以上就是一文带你了解Spring中Bean名称加载机制的详细内容,更多关于Spring Bean名称加载机制的资料请关注脚本之家其它相关文章!

相关文章

  • Java线程状态及jstack命令详解

    Java线程状态及jstack命令详解

    jstack是Java虚拟机(JVM)提供的一个非常有用的命令行工具,它允许开发人员和系统管理员在运行时获取Java应用程序的线程堆栈跟踪,在某些情况下,可能需要以管理员或root用户的身份运行jstack命令,这篇文章主要介绍了Java线程状态及jstack命令详解,需要的朋友可以参考下
    2024-03-03
  • SpringBoot实现列表数据导出为Excel文件

    SpringBoot实现列表数据导出为Excel文件

    这篇文章主要为大家详细介绍了在Spring Boot框架中如何将列表数据导出为Excel文件,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2024-02-02
  • 分析Netty直接内存原理及应用

    分析Netty直接内存原理及应用

    Netty作为一个流行的应用框架,它的强悍之处是性能强悍,可以轻松承载数万并发; 其编程模型简单,容易上手; 这就给大家打开了一扇通向高性能的大门。高效io模型略去不说,我们今天主要来看看内存控制这块的强大之处
    2021-06-06
  • springboot+springJdbc+postgresql 实现多数据源的配置

    springboot+springJdbc+postgresql 实现多数据源的配置

    本文主要介绍了springboot+springJdbc+postgresql 实现多数据源的配置,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Java 中使用数组存储和操作数据

    Java 中使用数组存储和操作数据

    本文将介绍Java中常用的数组操作方法,通过详细的示例和解释,帮助读者全面理解和掌握这些方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Maven版本冲突的三种解决方法

    Maven版本冲突的三种解决方法

    在Maven项目中,依赖传递可能导致Jar包版本冲突,常见的解决策略包括依赖排除、版本锁定和使用maven-shade-plugin插件,本文就来介绍一下这三种解决方法,感兴趣的可以了解一下
    2024-10-10
  • 详解Spring Boot 使用slf4j+logback记录日志配置

    详解Spring Boot 使用slf4j+logback记录日志配置

    本篇文章主要介绍了Spring Boot 使用slf4j+logback记录日志配置,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Restful传递数组参数及注解大全

    Restful传递数组参数及注解大全

    这篇文章主要介绍了Restful传递数组参数及注解大全的相关资料,需要的朋友可以参考下
    2015-12-12
  • mybatis的insert插入后获取自增id的方法详解(从controller到mapper)

    mybatis的insert插入后获取自增id的方法详解(从controller到mapper)

    这篇文章主要介绍了mybatis的insert插入后获取自增id的示例代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-10-10
  • java+jsp+struts2实现发送邮件功能

    java+jsp+struts2实现发送邮件功能

    这篇文章主要为大家详细介绍了java+jsp+struts2实现发送邮件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03

最新评论