SpringBoot自动配置之AutoConfigurati

本文基于SpringBoot 2.5.7版本进行讲解

在上一篇文章,SpringBoot自动配置之@SpringBootApplication注解 - SpringBoot自动配置(一)说到,SpringBoot自动配置的核心就在@EnableAutoConfiguration注解上,这个注解通过@Import(AutoConfigurationImportSelector)来完成自动配置。

所以说,SpringBoot自动配置的奥秘就隐藏在AutoConfigurationImportSelector类中。

阅读须知

在分析AutoConfigurationImportSelector如何实现SpringBoot自动配置之前,读者需要了解:

  1. @Import注解是什么?有什么用?
  2. ImportSelector接口是什么?有什么用?

AutoConfigurationImportSelector类

了解一个类,我们首先就要先看这个类的定义:

1
2
java复制代码public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

我们可以看到它实现了DeferredImportSelector接口,那么接下来再来看看DeferredImportSelector接口的定义:

1
java复制代码public interface DeferredImportSelector extends ImportSelector {

可以看到,DeferredImportSelector接口又继承了ImportSelector接口。这样,我们就明白了,AutoConfigurationImportSelector类必定实现了selectImports()方法,这个方法应该就是SpringBoot能够实现自动配置的核心。

selectImports()方法

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码private static final String[] NO_IMPORTS = {};

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断SpringBoot是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取需要被引入的自动配置信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

selectImports()方法的源码没有多少。

isEnabled()方法判断SpringBoot是否开启了自动配置。若开启就通过getAutoConfigurationEntry()来获取需要配置的Bean全限定名数组,否则就直接返回空数组。

isEnabled():判断SpringBoot是否开启自动配置

1
2
3
4
5
6
7
java复制代码protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
// 若调用该方法的类是AutoConfigurationImportSelector,那么就获取EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的值,默认为true
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}

EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY是什么?我们看下它的定义:

1
java复制代码String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

看到这里,我们可以猜到这就是在配置文件application.yml或者application.properties中的配置。因此,我们可以在配置文件中来决定SpringBoot是否开启自动配置。

当我们没有配置的时候,默认就是开启自动配置的。

getAutoConfigurationEntry()方法:获取需要自动配置的bean信息

话不多说,先上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从spring.factories文件中获取配置类的全限定名数组
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 获取注解中exclude或excludeName排除的类集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
checkExcludedClasses(configurations, exclusions);
// 去除被排除的类
configurations.removeAll(exclusions);
// 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 抛出事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

在这里,我们只需要知道这个getAutoConfigurationEntry()方法是用来获取需要自动配置的bean信息,以及里面每个方法做了什么,有个大概的印象就可以了。

下面会对每个方法作更近一步的讲解。

getAttributes():获取@EnableAutoConfiguration注解属性

1
2
3
4
5
6
7
java复制代码protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
+ " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}

image.png

看了上面断点的图,我们大概就明白了,这个方法就是获取@EnableAutoConfiguration注解的属性。

getCandidateConfigurations():从spring.factories文件获取需要配置的bean

1
2
3
4
5
6
7
java复制代码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;
}

getCandidateConfigurations()方法通过SpringFactoriesLoaderloadFactoryNames()方法从所有的spring.factories文件中获取需要配置的bean全限定名列表。

这里,对于SpringFactoriesLoader类的loadFactoryNames()方法就不展开讲了。有兴趣的可以自己阅读下源码。

到了这里,可能有很多人都疑惑了。spring.factories文件是什么?它又在哪里?

spring.factories文件本质上与properties文件相似,其中包含一组或多组键值对。其中,key的取值是接口的全限定名,value的取值是接口实现类的全限定名。一个接口可以设置多个实现类,不同实现类之间使用,隔开。例如:

1
2
3
ini复制代码org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

SpringFactoriesLoader会扫描所有jar包类路径下的META-INF/spring.factories文件,并获取指定接口的配置。

这里提一句,getCandidateConfigurations()方法获取的是EnableAutoConfiguration接口的配置。

removeDuplicates():去重

1
2
3
java复制代码protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}

我们知道SpringFactoriesLoaderloadFactoryNames()方法会从所有jar包类路径下的META-INF/spring.factories读取配置。就是说会从不同的spring.factories文件中读取配置,那么就有可能会出现配置了相同的类,这里就是对这些数据进行去重。

getExclusions():获取注解的exclude和excludeName属性配置的需要排除的类全限定名集合

1
2
3
4
5
6
7
java复制代码protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}

getExclusions方法就是将上面getAttributes()方法获取到@EnableAutoConfiguration注解的exclude和excludeName属性的值加入到excluded集合中。

checkExcludedClasses():检查排除类

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码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);
}
}

检查被排除的类是否可以实例化以及自动配置类列表是否包含该类。如果存在无效排除类,那么就抛出异常。

getConfigurationClassFilter():获取配置类过滤器

1
2
3
4
5
6
7
8
9
10
11
java复制代码private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
// 获取AutoConfigurationImportFilter过滤器列表
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}

getConfigurationClassFilter()方法通过getAutoConfigurationImportFilters()方法获取到spring.factories文件中AutoConfigurationImportFilter接口的配置,然后将其封装到ConfigurationClassFilter对象中。

总结

上面,我们介绍了AutoConfigurationImportSelector类是ImportSelector的实现类,实现了selectImports()方法。selectImports()方法又调用getAutoConfigurationEntry()方法从spring.factories文件中读取配置类的全限定名列表,并进行过滤,最终得到需要自动配置的类全限定名列表。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%