做盗版频网站,山西百度公司做网站的,深圳做微信网站设计,wordpress自动下载图片在 Spring 框架中#xff0c;定义 Bean 时不一定需要指定名称#xff0c;Spring 会智能生成默认名称。本文将介绍 Spring 的三种 BeanName 生成器#xff0c;包括在 XML 配置、Java 注解和组件扫描中使用的情况#xff0c;并解释它们如何自动创建和管理 Bean 名称。
1. Be…在 Spring 框架中定义 Bean 时不一定需要指定名称Spring 会智能生成默认名称。本文将介绍 Spring 的三种 BeanName 生成器包括在 XML 配置、Java 注解和组件扫描中使用的情况并解释它们如何自动创建和管理 Bean 名称。
1. BeanNameGenerator
Spring 中提供了一个名为 BeanNameGenerator 的接口这个接口就只有一个需要实现的方法就是 generateBeanName从名字就能看出来这就是专门用来生成 beanName 的方法。
public interface BeanNameGenerator {String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}这个方法有两个参数
definition这个是要生成的 Bean 定义。registry这个是将来 BeanDefinition 的注册器。
BeanNameGenerator 有三个不同的实现类对应不同的处理场景 AnnotationBeanNameGenerator这个专门用来处理包扫描的时候扫到的 Bean对于这些 Bean其 name 属性该如何处理由这个类来解决当然小伙伴们都知道通过 Component/Service/Repository/ Controller 这些注解定义的 Bean默认情况下beanName 就是类名首字母小写。FullyQualifiedAnnotationBeanNameGenerator这个继承自 AnnotationBeanNameGenerator并重写了 AnnotationBeanNameGenerator#buildDefaultBeanName 方法这个是使用类的全路径来作为 Bean 的默认名称。DefaultBeanNameGenerator这个是专门用来解决 XML 文件中定义的 Bean 如果没有设置 beanName那么就通过 DefaultBeanNameGenerator 来为其生成 beanName。
看了上面三个场景之后可能有小伙伴发现一个 BUG那么 注解定义的 Bean其 beanName 属性是在哪里处理的呢这个其实比较特殊是当场处理的没用到 BeanNameGenerator松哥后面单独说。
接下来我们详细看下上面这三个实现类。
2. AnnotationBeanNameGenerator
咱们直接来看最关键的 generateBeanName 方法吧
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);
}这个方法首先判断 definition 是否为 AnnotatedBeanDefinition 类型根据我们前面文章对 BeanDefinition 的介绍大家知道AnnotatedBeanDefinition 的实现类主要是针对三种情况Bean 注解定义的 Bean、Service/Controller/Component/Repository 等注解标记的 Bean 以及系统的启动配置类如果是这三种情况那么就去调用 determineBeanNameFromAnnotation 方法这个方法会尝试从注解中提取出来 beanName如果不是上面三种情况那么就调用 buildDefaultBeanName 方法去生成 beanName。
那我们先来看 determineBeanNameFromAnnotation 方法
Nullable
protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {AnnotationMetadata amd annotatedDef.getMetadata();Setstring types amd.getAnnotationTypes();String beanName null;for (String type : types) {AnnotationAttributes attributes AnnotationConfigUtils.attributesFor(amd, type);if (attributes ! null) {Setstring metaTypes this.metaAnnotationTypesCache.computeIfAbsent(type, key -gt; {Setstring result amd.getMetaAnnotationTypes(key);return (result.isEmpty() ? Collections.emptySet() : result);});if (isStereotypeWithNameValue(type, metaTypes, attributes)) {Object value attributes.get(value);if (value instanceof String strVal) {if (StringUtils.hasLength(strVal)) {if (beanName ! null amp;amp; !strVal.equals(beanName)) {throw new IllegalStateException(Stereotype annotations suggest inconsistent component names: beanName versus strVal );}beanName strVal;}}}}}return beanName;
}这个方法首先会去获取类上的注解信息拿到 amd 之后获取到所有的注解类型然后进行遍历。
遍历的时候首先获取到注解上的所有属性 attributes当 attributes 不为空的时候继续去读取当前注解的元注解并将读取到的结果存入到 metaAnnotationTypesCache 集合中。这个是干嘛呢大家知道Spring 中用来标记 Bean 的注解大部分衍生自 Component甚至我们也可以自定义注解那么如果自定义注解了这个地方就没法判断了因为每个人自定义出来的注解都不一样。所以万变不离其宗这里就去找各个注解的元注解。例如如果我们在类上添加的是 Configuration那么 Configuration 的元注解有两个分别是 Component 和 Indexed。
接下来的 isStereotypeWithNameValue 方法就是判断 type 是不是 Component 或者 Jakarta 中自带的 ManagedBean、Named亦或者 metaTypes 里是否包含 Component。如果确定是 Component 衍生出来的注解亦或者是 ManagedBean、Named 注解标记的 Bean那么就将其 value 属性读取出来作为 beanName如果包含多个有效注解且各自配置的 beanName 不一致就会抛出异常。
例如下面这种情况
Configuration(j)
Component(a)
public class JavaConfig {
}这两个 beanName 不一致运行时就会出错。
同时经过上面的分析小伙伴也看到了我们其实可以通过自定义注解为 Bean 设置名称例如我有如下注解
Retention(RetentionPolicy.RUNTIME)
Component
public interface MyBeanName {String value() default ;
}这个注解衍生自 Component那么它的用法如下
MyBeanName(f)
public class JavaConfig {}那么 f 就是当前类生成的 beanName。
以上是从注解中去提取 beanName但是注解中可能没有提供 beanName那么就得调用 buildDefaultBeanName 方法去自动生成了如下
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {return buildDefaultBeanName(definition);
}
protected String buildDefaultBeanName(BeanDefinition definition) {String beanClassName definition.getBeanClassName();Assert.state(beanClassName ! null, No bean class name set);String shortClassName ClassUtils.getShortName(beanClassName);return StringUtils.uncapitalizeAsProperty(shortClassName);
}这个就很好懂了先拿到 bean 的完整类名然后提取出来 shortName也就是去除包名之后的名字然后首字母小写之后返回。
这就是 Component 注解体系下的 beanName 生成流程。
3. FullyQualifiedAnnotationBeanNameGenerator
FullyQualifiedAnnotationBeanNameGenerator 类只是重写了 AnnotationBeanNameGenerator 的 buildDefaultBeanName 方法如下
Override
protected String buildDefaultBeanName(BeanDefinition definition) {String beanClassName definition.getBeanClassName();Assert.state(beanClassName ! null, No bean class name set);return beanClassName;
}重写后的方法就是获取类的完整路径返回。
FullyQualifiedAnnotationBeanNameGenerator 默认情况下并不会直接使用需要自己手动配置像下面这样
Configuration
ComponentScan(nameGenerator FullyQualifiedAnnotationBeanNameGenerator.class)
public class JavaConfig {}此时生成的 Bean 的默认名称就是类的全路径了。
4. DefaultBeanNameGenerator
这个是专门用来处理 XML 中默认的 beanName 的。这个在最近录制的 Spring 源码视频中已经详细介绍过了这里就不再啰嗦了。。
5. Bean 处理特殊情况
如果类是被 Bean 注解标记的那么处理情况就特殊一些直接现场处理方法在 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod 位置
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {// Consider name and any aliasesListstring names new ArrayListlt;gt;(Arrays.asList(bean.getStringArray(name)));String beanName (!names.isEmpty() ? names.remove(0) : methodName);// Register aliases even when overriddenfor (String alias : names) {this.registry.registerAlias(beanName, alias);}
}从这里可以看到如果一开始配置了 name 属性那么就把 names 集合中的第一个值拿出来作为 beanName集合中的其他值则当作别名来处理如果没有配置 name 属性值那么就使用方法名作为 beanName。