目录
前言
通过前文:《深入分析-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的全限定类名和作用域。
继承关系
AnnotationBeanNameGenerator
是BeanNameGenerator
接口的实现类,该接口的主要功能是为给定的Bean生成唯一的名称。目前,BeanNameGenerator
接口有两个实现,除了本篇文章介绍的AnnotationBeanNameGenerator
外,还有默认实现类DefaultBeanNameGenerator
,DefaultBeanNameGenerator
主要用于处理通过XML文件定义的Bean,为其自动生成名称。FullyQualifiedAnnotationBeanNameGenerator
继承自AnnotationBeanNameGenerator
,同样属于BeanNameGenerator
接口的实现类,该类覆写了AnnotationBeanNameGenerator
的buildDefaultBeanName()
方法,作用是使用注解类型和注解元数据,结合其他信息(例如类名、包名等),生成带有完全限定名的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);
}
如果当前BeanDefinition
是AnnotationBeanNameGenerator
类型,则尝试从注解中获取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 名称,提高代码的可读性和可维护性,但需要特别注意类名重复以及自动生成的名称是否符合需求。
后续内容文章持续更新中…
近期发布。
关于我
👋🏻你好,我是Debug.c。微信公众号:种棵代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。
🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。
📞如果您对我感兴趣,请联系我。
若有收获,就点个赞吧,喜欢原图请私信我。