赞
踩
我们知道 Spring Boot 开启自动配置使用的是 @EnableAutoConfiguration 注解,一般使用组合了它的 @SpringBootApplication 主注解即可。那么 Spring Boot 是如何加载包含了各种需要自动配置的类的配置文件的呢?本文我们基于 Spring Boot 3.0.6 一起看看 Spring Boot 自动配置文件加载的原理。
@EnableAutoConfiguration 注解的源码如下:
/**
* 启用Spring应用程序上下文的自动配置,尝试猜测和配置您可能需要的bean。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 允许子类继承
@AutoConfigurationPackage // 注解会将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器
@Import(AutoConfigurationImportSelector.class) // 导入组件,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中
public @interface EnableAutoConfiguration {
/**
* 环境属性,可以用来覆盖自动配置是否启用。
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 用于排除某些自动配置类,被排出的自动配置类不会被加载到容器中。
*/
Class<?>[] exclude() default {};
/**
* 根据类名排除自动配置类,被排除的自动配置类不会被加载到容器中。
*/
String[] excludeName() default {};
}
上面最关键的两个注解是:
自动配置 @Import 注解导入的是 AutoConfigurationlmportSelector.class 类,这也是这个注解的关键所在,它实现了 ImportSelector 接口。Spring Boot 中的 ImportSelector 接口是一个用于动态导入配置类的接口,它可以根据条件动态地选择需要导入的配置类。
在 Spring Boot 中,我们可以使用 @Import 注解来导入配置类,但是如果需要根据条件来动态选择导入哪些配置类,就需要使用 ImportSelector 接口。
ImportSelector 接口源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
该接口包含了两个方法:
selectImports:
该方法是 ImportSelector 接口的的核心方法,用于从传入的 AnnotationMetadata 中选择要导入的配置类。在该方法中,可以根据 AnnotationMetadata importingClassMetadata 中的注解信息来决定需要导入哪些配置类。返回一个 String 数组,其中包含要导入的配置类的全限定类名。
getExclusionFilter:
该方法用于提供一个谓词函数,用于过滤掉一些不需要导入的配置类。该函数接受一个字符串参数(即要导入的配置类的全限定类名),并返回一个布尔值,指示该配置类是否需要被排除。如果不需要进行任何过滤,可以返回 null
或一个恒返回 false
的谓词函数。
然后我们回到 AutoConfigurationlmportSelector 类,它的作用是根据类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的自动配置类来动态导入配置类。
AutoConfigurationlmportSelector 类实现的 ImportSelector 接口的两个方法源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 检查是否启用自动配置
if (!this.isEnabled(annotationMetadata)) {
// 如果未启用,则返回空数组
return NO_IMPORTS;
} else {
// 获取自动配置条目
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
// 返回自动配置条目中的配置类的字符串数组
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
public Predicate<String> getExclusionFilter() {
// 返回一个谓词函数,用于过滤应该排除的自动配置类
return this::shouldExclude;
}
进入 getAutoConfigurationEntry 方法,该方法用于用于获取自动配置条目,方法源码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 检查是否启用自动配置
if (!this.isEnabled(annotationMetadata)) {
// 如果未启用,则返回空的自动配置条目
return EMPTY_ENTRY;
} else {
// 获取所有候选的自动配置类
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的自动配置类
configurations = this.removeDuplicates(configurations);
// 获取需要排除的自动配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查需要排除的自动配置类是否存在于候选的自动配置类中
this.checkExcludedClasses(configurations, exclusions);
// 从候选的自动配置类中去除需要排除的自动配置类
configurations.removeAll(exclusions);
// 应用配置类过滤器,过滤无效的自动配置类
configurations = this.getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 构造自动配置条目并返回
return new AutoConfigurationEntry(configurations, exclusions);
}
}
我们将关注点聚集在 getCandidateConfigurations 方法,该方法用于获取候选的自动配置类。方法源码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中加载所有自动配置类
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
// 检查自动配置类列表是否为空
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
// 返回自动配置类列表
return configurations;
}
其中,ImportCandidates.load 方法是从指定的位置加载所有实现了指定接口的类,并返回一个 ImportCandidate 实例,用于访问加载的类。在这里,该方法用于加载所有实现了 AutoConfiguration 接口的类。this.getBeanClassLoader() 用于获取当前线程的上下文类加载器,用于加载类。如果自动配置类列表为空,则会抛出一个 IllegalStateException 异常,提示用户检查自动配置文件是否正确。
上面提到的 ImportCandidates 类是一个辅助类,用于加载所有实现了指定接口的候选类。它有一个 load 方法,用于加载所有实现了指定接口的类,并返回一个 ImportCandidates 实例。
方法源码如下:
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
// 检查注解类是否为空
Assert.notNull(annotation, "'annotation' must not be null");
// 获取要使用的类加载器
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// 获取要加载的自动配置类列表所在的位置
String location = String.format("META-INF/spring/%s.imports", annotation.getName());
// 在类路径中查找指定位置的资源
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
// 保存所有候选类的列表
List<String> importCandidates = new ArrayList();
// 遍历所有找到的资源
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 读取资源中的所有候选类,并添加到列表中
importCandidates.addAll(readCandidateConfigurations(url));
}
// 返回一个包含所有候选类的 ImportCandidates 实例
return new ImportCandidates(importCandidates);
}
从源码中我们可以看到,Spring Boot 3.0.x 的自动配置逻辑是加载 META-INF/spring/%s.imports 配置文件中的自动配置,这个 %s
是占位符,指 @AutoConfiguration 注解的类的全路径名称,如下图所示:
该配置文件内容如下:
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
...... Spring Boot 3.0.6 共注册了142个自动配置类
可以看到,编写格式确实简单了,一些应用级别的自动配置类已经全移到这个新配置文件中了。而老的自动配置文件 (spring.factories) 还保留了一些系统级别的组件,如各种初始化器、监听器等,这些初始化器和监听器都会在 Spring Boot 启动时完成自动配置。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。