软件编程
位置:首页>> 软件编程>> java编程>> Spring Boot示例分析讲解自动化装配机制核心注解

Spring Boot示例分析讲解自动化装配机制核心注解

作者:麦神-mirson  发布时间:2022-07-26 15:56:14 

标签:Spring,Boot,自动化,装配机制,部署

1. 自动化装配介绍

Spring Boot针对mvc做了大量封装,简化开发者的使用,内部是如何管理资源配置,Bean配置,环境变量配置以及启动配置等? 实质是SpringBoot做了大量的注解封装,比如@SpringBootApplication, 同时采用Spring 4框架的新特性@Conditional基于条件的Bean创建管理机制来实现;

实际的工作场景中是复杂多样的, 有些项目需要不同的组件, 比如REDIS、MONGODB作缓存; RABBITMQ、KAFKA作消息队列; 有些项目运行环境不同, 比如JDK7、JDK8不同版本,面对众多复杂的需求, 又要做到最大化支持, Spring Boot是如何管理实现的, 这就依赖Conditional功能,基于条件的自动化配置。

Spring Boot示例分析讲解自动化装配机制核心注解

2. Spring Boot 自动化配置UML图解

SpringBootApplication是我们所常用熟知的注解, 它是一个组合注解, 依赖多个注解,共同实现Spring Boot应用功能, 以下为所有依赖的UML图解,我们围绕这些注解深入研究,看下具体的实现。

Spring Boot示例分析讲解自动化装配机制核心注解

3. Spring Boot 自动化配置核心注解分析

SpringBootApplication注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
/**
* 需要排除的自动化配置, 根据类名进行排除,  比如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 需要排除的自动化配置, 根据名称进行排除
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 指定需要扫描的包路径,参数填写包名
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 指定需要扫描的包路径
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* Bean方法的 * 配置, 如果没有采用工厂方法, 可以标记为false, 采用cglib代理。
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}

3.1 @Inherited

java.lang.annotation.@Inherited 注解,从包名可以看出为JDK自带注解, 作用是让子类能够继承父类中引用Inherited的注解, 但需注意的是, 该注解作用范围只在类声明中有效; 如果是接口与接口的继承, 类与接口的继承, 是不会生效。

3.2 @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {
/**
* Bean方法的 * 配置
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}

这是配置型处理注解, 可以看到内部源码引用了@Configuration注解,

自身没有太多的实现, 那为什么还需要再包装?官方给出的解释是对Spring的@Configuration的扩展,

用于实现SpringBoot的自动化配置。proxyBeanMethods属性默认为true, 作用是对bean的方法是否开启代理方式调用, 默认为true, 如果没有采用工厂方法,可以设为false, 通过cglib作 * 。

3.3 @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   // 设置注解支持重载的标识
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 排除自动化配置的组件, 如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等
*/
Class<?>[] exclude() default {};
/**
* 排除自动化配置的组件, 根据名称设置
*/
String[] excludeName() default {};
}

用于管理开启Spring Boot的各种自动化配置注解, 如datasource, mongodb, redis等,也是spring-boot-autoconfigure工程的核心注解。

AutoConfigurationPackage

它的主要作用是扫描主程序同级及下级的包路径所有Bean与组件注册到Spring Ioc容器中。

Import

它可以把没有声明配置的类注册到Spring Ioc容器中管理引用。 导入的AutoConfigurationImportSelector类实现BeanClassLoaderAware、ResourceLoaderAware、EnvironmentAware等接口, 管理类装载器, 资源装载器及环境配置等, 是一个负责处理自动化配置导入的选择管理器。在下面【@AutoConfigurationImportSelector剖析】进行详解。

3.4 @ComponentScan

这是我们在Spring下面常用的一个注解,它可以扫描Spring定义的注解, 如@Componment, @Service等, 常用的属性有basePackages扫描路径,includeFilters包含路径过滤器, excludeFilters排除路径过滤器,lazyInit是否懒加载等,能够非常灵活的扫描管理需要注册组件。

3.5 @ConfigurationPropertiesScan

作用是扫描指定包及子包路径下面的ConfigurationProperties注解,管理工程配置属性信息。主要属性为basePackages扫描路径, 支持多个路径,数组形式;basePackageClasses属性也可以具体到包下面的类,

支持多个配置。

3.6 @AutoConfigurationImportSelector

AutoConfigurationImportSelector 实现 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口, 为自动化配置的核心处理类, 主要负责自动化配置规则的一系列处理逻辑:

/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.3.0
* @see EnableAutoConfiguration
*/
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
           ...
       }
}

讲解几个技术点:

getCandidateConfigurations方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

该方法是获取所有Spring Boot声明定义的自动化配置类。

看下具体有哪些信息:

Spring Boot示例分析讲解自动化装配机制核心注解

这些实际是配置在Spring-boot-autoconfigure工程下的META-INF/spring.factories文件中:

Spring Boot示例分析讲解自动化装配机制核心注解

看到这里, 我们应该可以明白,为什么AOP,RABBIT,DATASOURCE, REIDS等组件SPRING BOOT都能帮我们快速配置实现,其实它内部遵循SPI机制, 已经把自动化配置做好了封装。

AutoConfigurationGroup类

它是AutoConfigurationImportSelector的内部类,实现了DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware接口,是一个重要的核心类。主要作用是负责自动化配置条目信息的记录, 排序,元数据处理等。它通过getImportGroup方法获取返回,该方法实现DeferredImportSelector的接口。

private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
   // 记录注解的元数据
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
// 记录自动化配置条目,放入集合
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
       // 设置bean的类加载器
private ClassLoader beanClassLoader;
       // 设置bean工厂信息
private BeanFactory beanFactory;
       // 设置资源加载器信息
private ResourceLoader resourceLoader;
       // 设置自动化配置的元数据记录
private AutoConfigurationMetadata autoConfigurationMetadata;
...
}

属性主要定义了一些自动化配置类目信息、BEAN工厂、类和资源加载器信息。entries条目有22条, 具体内容如下:

Spring Boot示例分析讲解自动化装配机制核心注解

里面是主要的自动化配置类的元数据信息,autoConfigurationEntries属性就是具体的自动化配置条目。这些主要自动化类配置是Spring boot帮助我们实现mvc的核心功能,如请求分发,文件上传,参数验证,编码转换等功能。还有一部分是定制条件自动化配置类,

autoConfigurationMetadata元数据内容较多, 包含各种组件, 根据环境配置和版本不同, 这里可以看到共有705个:

Spring Boot示例分析讲解自动化装配机制核心注解

由于Spring Boot支持众多插件,功能丰富, 数量较多; 这里存在些疑问, 这里面的元数据和上面的entries条目都是AutoConfiguration自动化配置类, 那有什么区别? 其实这里面的, 都是基于条件的自动化配置。

我们就拿KafkaAutoConfiguration来看:

Spring Boot示例分析讲解自动化装配机制核心注解

可以看到注解ConditionalOnClass,意思是KafkaAutoConfiguration生效的前提是基于KafkaTemplate类的初始化成功,这就是定制条件,也就是基于条件的自动化配置类,虽然有七百多个,但其实是根据工程实际用到的组件,才会触发加载对应的配置。 有关Conditional基于条件的自动化配置实现原理, 在下面我们再作深入研究。

继续看AutoConfigurationImportSelector内部类的selectImports方法:

@Override
public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
       return Collections.emptyList();
   }
   // 将所有自动化条目根据配置的Exclusion条件作过滤, 并转换为SET集合
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
       .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
   // SET集合, 记录所有需要处理的自动化配置
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
       .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
       .collect(Collectors.toCollection(LinkedHashSet::new));
   // 两个SET, 做交集过滤, 排除不需要的配置
   processedConfigurations.removeAll(allExclusions);
   // 最后进行排序处理
   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
       .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
       .collect(Collectors.toList());
}

该方法是针对autoConfigurationEntries自动化配置条目做过滤,根据指定的排除规则处理;再根据设置的启动的优先级做排序整理。从代码中可以看到,先获取所有的allExclusions排除配置信息,再获取所有需要处理的processedConfigurations配置信息,然后做过滤处理,最后再调用sortAutoConfigurations方法,根据order顺序做排序整理。

AutoConfigurationImportSelector内部类的process方法:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                () -> String.format("Only %s implementations are supported, got %s",
                                    AutoConfigurationImportSelector.class.getSimpleName(),
                                    deferredImportSelector.getClass().getName()));
   // 获取自动化配置条目
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
       .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
   // 记录获取的条目
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
       // 放入成员变量entries中
       this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

该方法是扫描获取autoConfigurationEntries自动化配置条目信息。

annotationMetadata参数:

为注解元数据,有也就是被@SpringBootApplication修饰的类信息,在这里就是我们的启动入口类信息。

deferredImportSelector参数:

通过@EnableAutoConfiguration注解定义的 @Import 的类,也就是AutoConfigurationImportSelector对象。根据配置,会加载指定的beanFactory、classLoader、resourceLoader和environment对象。

AutoConfigurationImportSelector内部类的getAutoConfigurationEntry方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
       // 1、判断是否开对应注解
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
       // 2、获取注解定义的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
       // 3、获取符合规则的Spring Boot 内置的自动化配置类, 并做去重处理
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
       // 4、做排除规则匹配, 过滤处理
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
       // 5、触发自动导入处理完成事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

该方法主要作用是获取Spring Boot 内置的自动化条目, 例AopAutoConfiguration等,该方法会调用上面讲解的getCandidateConfigurations方法。 主要步骤逻辑如下:

  • 判断是否开启元注解扫描, 对应属性为spring.boot.enableautoconfiguration,默认情况下, 是开启自动配置。

  • 获取定义的注解属性, 跟踪内部源码, 里面会返回exclude和excludeName等属性。

  • 获取符合规则的Spring Boot 内置的自动化配置, 并做去重处理,也就是我们上面讲解的getCandidateConfigurations方法, 从中我们就可以理解其中的关联关系。

  • 做排除规则检查与过滤处理, 根据上面第2个步骤获取的exclude等属性以及配置属性spring.autoconfigure.exclude做过滤处理。

  • 触发自动导入完成事件, 该方法内部逻辑正常处理完成才会触发,会调用AutoConfigurationImportListener * 做通知处理。

3.7 @AutoConfigurationPackages

AutoConfigurationPackages是EnableAutoConfiguration上的另一个核心注解类, 官方解释为:

Indicates that the package containing the annotated class should be registered

意思是包含该注解的类,所在包下面的class, 都会注册到Spring Ioc容器中。对应源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}

Import注解, 导入AutoConfigurationPackages抽象类下面的内部静态类Registrar,研究Registrar实现原理:

Registrar实现 ImportBeanDefinitionRegistrar、DeterminableImports 接口,它负责存储从@AutoConfigurationPackage注解扫描到的信息。 源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
       // 注册BEAN的定义信息
   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   register(registry, new PackageImport(metadata).getPackageName());
   }
       // 决定是否导入注解中的配置内容
   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
   return Collections.singleton(new PackageImport(metadata));
   }
   }

这里面主要涉及到PackageImport类, 它是AutoConfigurationPackages的内部私有静态类,主要是记录导入的 报名信息, 源码如下:

/**
* Wrapper for a package import.
*/
private static final class PackageImport {
private final String packageName;
// 构造方法, 记录注解内容
PackageImport(AnnotationMetadata metadata) {
this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}
// 获取指定包名称
public String getPackageName() {
return this.packageName;
}
   // 重载父类比较逻辑, 根据包名判断
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.packageName.equals(((PackageImport) obj).packageName);
}
// 重载hash标识, 以包名的HASH值为准
@Override
public int hashCode() {
return this.packageName.hashCode();
}
   // 重载toString, 打印内容
@Override
public String toString() {
return "Package Import " + this.packageName;
}
}

内部断点跟踪的话, 可以看到它记录的是我们启动类所在的包名。这也就是为什么不需要指定扫描包路径, 也会加载启动类所在包下面的JavaConfig配置信息。

回到上面Registrar的registerBeanDefinitions方法, 内部调用的是register方法:

它是处理记录AutoConfigurationPackages扫描包信息,源码如下:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
       // 判断是否包含BEAN定义信息, 如果包含, 更新packageNames信息
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
       // 如果registry中不包含BEAN定义, 重新构造GenericBeanDefinition对象, 记录相关信息
else {            
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}

先判断AutoConfigurationPackages注解, 记录对应的扫描包信息;如果不存在,则自行创建基于BasePackages的BEAN定义信息, 并进行注册。再看下addBasePackages方法:

private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
       // 获取已经存在的Bean定义信息
String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
       // 创建合并集合, 过滤重复的Bean定义
Set<String> merged = new LinkedHashSet<>();
       // 根据Set特性, 自动合并去重
merged.addAll(Arrays.asList(existing));
merged.addAll(Arrays.asList(packageNames));        
return StringUtils.toStringArray(merged);
}

获取已经存在的定义信息,再和packageNames合并, 过滤重复的扫描包。

自动化配置到此就不再对其他代码进行深入跟踪分析,Spring Boot整个框架代码还是较多, 大家可以按这种思路, 逐个层级去剖析,深入挖掘更多技术点。

4. 总结

我们研究了Spring Boot的自动化配置原理,逐层研究剖析,从@SpringBootApplication启动注解开始,到下面的@SpringBootConfiguration, @ConfigurationPropertiesScan, @ComponentScan以及核心@EnableAutoConfiguration。我们对@EnableAutoConfiguration下面的@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个重要注解作了深入研究,从中可以看到Spring Boot针对自动化配置, 是分为两部分, 一部分是核心注解,来支撑服务的正常运行; 另一部分是非核心的各种自动化组件注解,做了大量封装,便于我们集成使用。

来源:https://blog.csdn.net/hxx688/article/details/125171319

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com