🤖🤖-摘要:
本文介绍了SpringBoot自动配置的原理, 包括自动配置流程,核心配置流程以及如何实施自动配置。首先导入’starter’以获得来自’autoconfigure’包的配置, 然后使用‘@EnableAutoConfiguration’自动载入所有自动配置类。根据条件选择需要的配置类后,上述类将使用由配置文件中的特定前缀属性值提取而成的组件,从而实现自动配置。

我的SpringBoot项目第二个模块

在日常开发中, 通常我们只需要引入某个场景启动器, 再加上一些相应的配置即可, 无需费心复杂的整合操作, 这也是 SpringBoot 的强大之处.

SpringBoot 是如何省去繁杂的整合过程的呢?

接下来按照流程一步一步分析.

SpringBoot自动配置流程

1.导入starter

web场景为例, 导入了web开发场景

  1. 场景启动器导入了相关场景的所有依赖, 如下:
    • starter-json,starter-tomcat,springMVC
  2. 每个场景启动器都引入了一个spring-boot-starter, 核心场景启动器
  3. 核心场景启动器引入了spring-boot-autoconfigure
  4. spring-boot-autoconfigure里面囊括了所有场景的所有配置
  5. 只要这个包下的所有类都能生效, 那么相当于SpringBoot官方写好的整合功能就生效了
  6. SpringBoot默认却扫描不到spring-boot-autoconfigure下写好的所有配置类.
    • 这些配置类给我们做了整合操作,默认只扫描主程序所在的包

2.主程序:@SpringBootApplication

为什么引入场景,无需配置就已经整合完成了?一切都要从@SpringBootApplication开始

  • @SpringBootApplication由三个注解组成@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
    • @SpringBootConfiguration 其实就是@Configuration, 声明主程序类是一个配置类
    • @ComponentScan SpringBoot默认只能扫描自己主程序所在的包及其下面的子包, 扫描不到spring-boot-autoconfigure包中官方写好的配置类
    • @EnableAutoConfiguration就是SpringBoot开启自动配置的核心
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//...
}
  • @Import(AutoConfigurationImportSelector.class) 可以批量给容器中导入组件
  • SpringBoot@3.1.0 会批量导入146个自动配置类(xxxAutoConfiguration)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// ...

/**
* 批量获取候选配置
* @param annotationMetadata 注解元信息(主程序全类名,本项目为:com.fredo.Main)
* @return 候选配置的数组
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取自动配置节点
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

/**
* 获取自动配置节点
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 返回注解属性, 类型:LinkedHashMap, 值(exclude->[],excludeName->[])
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取候选配置类 值(146个自动配置类的全类名)
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);
return new AutoConfigurationEntry(configurations, exclusions);
}

/**
* 获取候选配置类
* 位置:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, 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;
}
// ...
}
  • 按需生效
    • 虽然导入了146个自动配置类, 并不是都能生效
    • 每一个自动配置类, 都有条件注解@ConditionalOnxxx, 只有条件成立, 才能生效
    • 导入对应的依赖包, 即满足条件, 该自动配置类就会生效

3.xxxAutoConfiguration自动配置类

  1. 每个xxxAutoConfiguration自动配置类,都需要满足某个条件
@ConditionalOnClass(Xxx.class) // 条件就是需要引入依赖包含 Xxx.class
@Conditional(XxxCondition.class)
@EnableConfigurationProperties(XxxProperties.class)
public class XxxAutoConfiguration {
// ...
}
  1. 条件满足, 自动配置类就会给容器中使用@Bean放一堆组件

  2. 每个自动配置类都可能有这个注解@EnableConfigurationProperties(XxxProperties.class)

    • 用来把配置文件中配的指定前缀的属性值封装到XxxProperties属性类
  3. Tomcat为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
// ...
}

SpringBoot核心配置流程

  1. 导入starter, 就会导入autoconfigure
  2. autoconfigure包里有文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
  3. @EnableAutoConfiguration会自动的把上面文件里所有自动配置类都导入进来. XxxAutoConfiguration是有条件注解进行按需加载
  4. XxxAutoConfiguration给容器中导入一堆组件, 组件都是从xxxProperties中提取属性值
  5. XxxProperties又和配置文件进行绑定

达到的效果:导入starter –> 修改配置文件, 就能修改底层行为

小试牛刀

接下来检验下SpringBoot自动配置原理的理解, 尝试回答如下问题:

为什么项目启动后默认端口号是8080, 以及如何修改???
  1. 引入web场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

自动引入了spring-boot-starterspring-boot-starter-tomcat

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>

spring-boot-starter自动引入依赖spring-boot-autoconfigure

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>

spring-boot-autoconfigure包里有文件:

  • additional-spring-configuration-metadata.json, 里面包含了所有的默认值
{
"name": "server.port",
"defaultValue": 8080
}
  • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
  1. 合成注解@SpringBootApplication中的@EnableAutoConfiguration
    自动的把上面文件里所有自动配置类都导入进来. XxxAutoConfiguration是有条件注解进行按需加载

其中就包括ServletWebServerFactoryAutoConfiguration.java

@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
// ...
}

绑定了属性文件ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
private Integer port;
// ...
}

只需要在自己的配置文件中自定义server.port的值

SpringBoot启动时就会去resources路径下加载符合要求的文件, 从该文件中查找配置来覆盖默认配置,
即完成了配置自定义

spring-boot-starter-parent-3.1.0.pom文件:

<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
<include>**/application*.yaml</include>
<include>**/application*.properties</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<excludes>
<exclude>**/application*.yml</exclude>
<exclude>**/application*.yaml</exclude>
<exclude>**/application*.properties</exclude>
</excludes>
</resource>
</resources>
...
</build>