这篇文章主要分析了SpringBoot自动化装配机制核心注解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
1. 自动化装配介绍
Spring Boot针对mvc做了大量封装,简化开发者的使用,内部是如何管理资源配置,Bean配置,环境变量配置以及启动配置等? 实质是SpringBoot做了大量的注解封装,比如@SpringBootApplication, 同时采用Spring 4框架的新特性@Conditional基于条件的Bean创建管理机制来实现;
实际的工作场景中是复杂多样的, 有些项目需要不同的组件, 比如REDIS、MONGODB作缓存; RABBITMQ、KAFKA作消息队列; 有些项目运行环境不同, 比如JDK7、JDK8不同版本,面对众多复杂的需求, 又要做到最大化支持, Spring Boot是如何管理实现的, 这就依赖Conditional功能,基于条件的自动化配置。
2. Spring Boot 自动化配置UML图解
SpringBootApplication是我们所常用熟知的注解, 它是一个组合注解, 依赖多个注解,共同实现Spring Boot应用功能, 以下为所有依赖的UML图解,我们围绕这些注解深入研究,看下具体的实现。
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-autoconfigure工程下的META-INF/spring.factories文件中:
看到这里, 我们应该可以明白,为什么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条, 具体内容如下:
里面是主要的自动化配置类的元数据信息,autoConfigurationEntries属性就是具体的自动化配置条目。这些主要自动化类配置是Spring boot帮助我们实现mvc的核心功能,如请求分发,文件上传,参数验证,编码转换等功能。还有一部分是定制条件自动化配置类,
autoConfigurationMetadata元数据内容较多, 包含各种组件, 根据环境配置和版本不同, 这里可以看到共有705个:
由于Spring Boot支持众多插件,功能丰富, 数量较多; 这里存在些疑问, 这里面的元数据和上面的entries条目都是AutoConfiguration自动化配置类, 那有什么区别? 其实这里面的, 都是基于条件的自动化配置。
我们就拿KafkaAutoConfiguration来看:
可以看到注解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针对自动化配置, 是分为两部分, 一部分是核心注解,来支撑服务的正常运行; 另一部分是非核心的各种自动化组件注解,做了大量封装,便于我们集成使用。
到此这篇关于Spring Boot示例分析讲解自动化装配机制核心注解的文章就介绍到这了,更多相关Spring Boot自动化装配机制内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!
本文标题为:Spring Boot示例分析讲解自动化装配机制核心注解
基础教程推荐
- Java并发编程进阶之线程控制篇 2023-03-07
- java基础知识之FileInputStream流的使用 2023-08-11
- Java实现线程插队的示例代码 2022-09-03
- Java文件管理操作的知识点整理 2023-05-19
- JDK数组阻塞队列源码深入分析总结 2023-04-18
- java实现多人聊天系统 2023-05-19
- Java数据结构之对象比较详解 2023-03-07
- Java实现查找文件和替换文件内容 2023-04-06
- ConditionalOnProperty配置swagger不生效问题及解决 2023-01-02
- springboot自定义starter方法及注解实例 2023-03-31