Spring Boot以零配置、开箱即用的特性而闻名。开发者只需在主类上添加@SpringBootApplication注解,然后在外化配置文件(比如application.yml)中创建独立的配置,就能成功启动应用,避免了传统SSM应用中的繁琐的XML配置。这样的便利性其实是建立在Spring Boot强大的自动配置机制上实现的,本文就来分析一下Spring Boot中自动配置的原理。
@SpringBootApplication注解
一般我们都会在项目的主类上添加该注解,而这就激活了Spring Boot的自动配置,这个注解为什么这么神奇?
@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) })
public @interface SpringBootApplication {
}
重点是该注解中引入了一个关键注解:@EnableAutoConfiguration。顾名思义,这个注解就是用来开启自动配置的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
在这个注解上,又通过了@Import注解引入了一个选择器类:AutoConfigurationImportSelector。 该类实现了DeferredImportSelector接口,所以该类不仅是一个选择器,还是延迟选择器。在Spring中的ConfigurationClassParser一文中,介绍了底层Spring Framework是如何处理延迟选择器的。
选择器组类型
因为底层Spring Framework中是调用的组对象的process方法和selectImports方法,所以先来看看该选择器中使用的是哪种组类型。
@Override
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
AutoConfigurationGroup类
先来看process方法。
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
@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(annotationMetadata);
// 将自动配置组添加到列表中
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 从自动配置组对象中获取自动配置类列表并遍历,将其放到map中
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 获取排除类
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
// 获取所有自动配置类
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 移除被排除了的自动配置类,其实在上面getAutoConfigurationEntry方法中就已经移除了,这里似乎没有意义
processedConfigurations.removeAll(allExclusions);
// 对自动配置类进行排序
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
selectImports方法中主要就是在获取process方法中准备好的自动配置类列表,然后返回就行了。重点是在process方法调用的getAutoConfigurationEntry方法中。
getAutoConfigurationEntry方法
// annotationMetadata指的是哪个类上的注解import了该类
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断是不是关闭了自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取类上的注解属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 加载自动配置类的全限定名
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复项
configurations = removeDuplicates(configurations);
// 获取排除项
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查所要排除的项是否存在
checkExcludedClasses(configurations, exclusions);
// 移除排除项
configurations.removeAll(exclusions);
// 调用配置类过滤器并过滤配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 将结果封装到entry对象中
return new AutoConfigurationEntry(configurations, exclusions);
}
这个方法中主要做了下面几件事情:
- 加载自动配置类的全限定名并移除重复项;
- 获取排除项并校验,如果校验成功则移除自动配置类;
- 使用过滤器来过滤自动配置类;
加载自动配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/*
* 加载classpath下面的META-INF/spring.factories文件
* getSpringFactoriesLoaderFactoryClass()返回EnableAutoConfiguration.class
* 就目前的版本(2.7.x)而言,spring.factories文件中不存在EnableAutoConfiguration对应的配置,
* 而是换成了下面的AutoConfiguration.imports的方式。
*
* spring.factories文件这种方式已被废弃,未来版本中会被移除。
* 而是改为了以imports结尾的文件,在classpath:/META-INF/spring目录下,相比之前多了一层spring目录。
*/
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
/*
* 加载classpath下面的META-INF/spring/%s.imports文件���注意%s会被换成具体的类名,
* 如下面就表示是加载META-INF/spring/AutoConfiguration.imports
* 并把加载结果添加在configurations集合中
*/
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
/*
* 不能为空,否则断言失败报错
*/
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor 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;
}
protected final <T> List<T> removeDuplicates(List<T> list) {
// 通过set的唯一性来去重
return new ArrayList<>(new LinkedHashSet<>(list));
}
重点是在getCandidateConfigurations方法中,在2.7.x的版本中,Spring Boot就已经没有使用传统的spring.factories文件来配置自动配置类了,而是通过”META-INF/spring/AutoConfiguration.imports“文件。所以这里只分析.imports文件是怎么读取的。
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// 用annotation的名称替换LOCATION字符串中的%s
String location = String.format(LOCATION, annotation.getName());
// 找到这些位置下的文件
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> autoConfigurations = new ArrayList<>();
// 遍历.imports文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 读取文件中的每一行,并合并到列表中
autoConfigurations.addAll(readAutoConfigurations(url));
}
// 将读取到的文件内容封装到对象中
return new ImportCandidates(autoConfigurations);
}
private static List<String> readAutoConfigurations(URL url) {
// 读取文件
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
List<String> autoConfigurations = new ArrayList<>();
String line;
// 按行读取
while ((line = reader.readLine()) != null) {
// 移除注释
line = stripComment(line);
// 移除前后的空格
line = line.trim();
// 忽略无效行
if (line.isEmpty()) {
continue;
}
// 添加到配置列表中
autoConfigurations.add(line);
}
// 返回配置列表
return autoConfigurations;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load autoconfigurations from location [" + url + "]", ex);
}
}
这里对.imports文件按行读取,将每行看作是一条配置项。
排除项
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
// 获取@EnableAutoConfiguration注解的exclude的属性值
excluded.addAll(asList(attributes, "exclude"));
// 获取@EnabelAutoConfigurtaion注解的excludeName的属性值
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
// 从environment中获取自动配置排除配置项
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
protected List<String> getExcludeAutoConfigurationsProperty() {
Environment environment = getEnvironment();
if (environment == null) {
return Collections.emptyList();
}
if (environment instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(environment);
// 从environment中获取 "spring.autoconfigure.exclude“ 配置项
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
.orElse(Collections.emptyList());
}
String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
// 遍历排除项
for (String exclusion : exclusions) {
// 判断排除项存不存在
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
// 如果排除项不存在,则添加到无效排除项中
invalidExcludes.add(exclusion);
}
}
// 如果存在无效排除项
if (!invalidExcludes.isEmpty()) {
// 处理无效排除项
handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
// 针对无效排除项,抛出异常
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}
注意,不管是在@EnableAutoConfiguration注解中还是配置到environment的排除项,都需要确保被排除的类是存在的,否则Spring Boot会抛出异常。
过滤器
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter); // 如果过滤器实现了一些Aware接口,那么设置对应的属性
}
// 将过滤器封装进入包装对象中
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
/*
* 2.7.x版本中有下面三个过滤器:
* OnBeanCondition
* OnClassCondition
* OnWebApplicationCondition
*/
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
// 遍历过滤器
for (AutoConfigurationImportFilter filter : this.filters) {
// 过滤器会利用元数据判断配置类是否匹配
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) { // 所有配置类都匹配,则直接返回
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
// 处理过滤结果,只保留那些匹配上的配置类
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
注释中已经说明了有三个默认的过滤器,被添加到ConfigurationClassFilter对象中后,然后会在其filter方法被遍历执行。这里就不一一分析具体的过滤器了。
总结
本文分析了Spring Boot中的自动配置原理,可以发现在Spring Boot中的实现还是挺简单的,就是读取和解析各个jar包中类路径下的META-INF/spring/AutoConfiguration.imports文件,然后进行一些排除和过滤操作。真正的配置类的加载操作还是Spring Framework中的ConfigurationParer类来完成的,请参考Spring中的ConfigurationClassParser。