0%

SpringBoot—自动配置原理与自定义配置

自动配置遵循“自动配置,按需启用”的工作方式。这部分通过源码分析,记录了自动配置的实现原理。
同时也通过源码阐述如何修改默认配置。

初始加载自动配置类

自动配置类的初始加载是由组合注解@SpringBootApplication实现的。前面的日志提到过,这个组合注解时由如下三个注解组成的:

1
2
3
4
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

下面逐个解释每个注解

@SpringBootConfiguration

追踪后发现本质是一个@Configuration。说明此注解表示主类也是一个配置类,是核心配置类。

@ComponentScan

指定包扫描路径

@EnableAutoConfiguration(重点)

追踪可发现,这是由两者注解合成的

1
2
3
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

下面分开说明

@AutoConfigurationPackage

自动配置包
追踪源码发现重点在于:

1
@Import({Registrar.class})

再次追踪Registrar发现下述代码与之有关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}

/* 重要部分是下面这个方法 */
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
//【断点】上一行代码
}


public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}

对上述代码打断点(断点位置见注释)后分析其作用:

  1. 【断点】处发现,metadata提供了当前类的全包名(和其他信息),然后通过.getPackageNames()的方法获取包名,并转成字符串数组。
  2. 这个字符串数组被传给register方法,由register方法将该包下的组件批量注册

@Import(AutoConfigurationImportSelector.class)

追踪AutoConfigurationImportSelector。截取其中有关代码

1
2
3
4
5
6
7
8
9
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
//重点在上一行
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

这段代码的核心是getAutoConfigurationEntry(annotationMetadata)。由他获取所有需要导入的组件。追踪这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected AutoConfigurationImportSelector.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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}

发现重点在于getCandidateConfigurations(annotationMetadata, attributes)
对此处断点Debug运行,发现这里获得了127个组件
跟踪getCandidateConfigurations得到

1
2
3
4
5
6
7
8
9
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

/* 重点在下行 */
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

发现这里使用loadFactoryNames这个工厂加载器加载了某些名字,再追踪

1
2
3
4
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

追踪返回值有关的方法loadSpringFactories,得到如下一个有用的内容:

1
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

去外部库里面找 “ETA-INF/spring.factories” 目录。我们发现 ring-boot-autoconfigure-2.3.4.RELEASE.jar 库下面有这个文件。里面也看到了那 127个组件。
说明这个方法是将写死的127个场景进行加载。

总结

上述两个注解,共同完成了组件的加载。分别作用如下

  • @AutoConfigurationPackage

    • 利用register,将Main程序所在包下所有的包导入
  • @Import(AutoConfigurationImportSelector.class)

    • 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
    • 调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
    • 利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
    • 从META-INF/spring.factories位置来加载一个文件。(默认扫描所有包的目录,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories)

按需启用自动配置项

上一节中提到的“127个场景”的所有自动配置启动的时候默认全部加载(利用:xxxxAutoConfiguration)。
但是这些场景的配置并不会都启用并注册组件。这是通过jorg.springframework.boot.autoconfigure目录下所展示的各个包配置文件中的条件装配规则(@Conditional)确定的。最终这些场景会按需配置。

修改默认配置

我们有两种方法修改默认配置,一种是在配置类自己写该组件的@Bean,场景加载时,通过条件装配发现用户自写相关组件就会采用用户的组件。这是由@ConditionalMissingBean标签实现。
另一种方法是用户去看这个组件是获取的配置文件什么值就去修改“application.properties”

自动加载流程

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 —-> application.properties