Spring源码-SpringBoot-自动配置介绍

总结摘要
SpringBoot自动配置介绍

前言

SpringBoot 采用“约定大于配置”的思想。

SpringBoot 支持自动配置。

约定大于配置

“约定大于配置”(Convention Over Configuration)是一种软件设计范式,其核心思想是:框架为开发者提供一套默认的、符合大多数人习惯的约定(标准、规范),开发者只需要在不遵守这些约定时,才需要显式地编写配置来指定自己的行为。

在 Spring 框架的语境下,尤其是在 Spring Boot 出现后,这一理念得到了极大的体现。

其优势主要体现在以下几个方面:

  1. 提升开发效率
    • 减少决策: 开发者无需在项目初期就纠结于项目结构、配置文件位置、Bean 的命名等琐碎细节。
    • 减少样板代码: 框架自动处理了大量常规的配置,开发者只需关注与业务逻辑相关的代码。
  2. 降低入门和上手门槛
    • 新成员加入项目时,如果项目遵循标准的 Spring Boot 约定,他们能更快地理解项目结构并开始工作,因为大部分配置方式都是“众所周知”的。
    • 学习和记忆的成本降低,开发者只需要记住“例外情况”该如何配置即可。
  3. 保持项目的一致性和标准化
    • 所有遵循相同约定的项目,其代码结构和配置方式都非常相似。这使得项目更容易维护、交接和自动化(例如,通过标准的 Maven/Gradle 插件进行构建和部署)。
  4. 减少错误
    • 手动编写的配置越少,因配置错误(如拼写错误、遗漏配置项)而导致的问题也就越少。框架提供的默认约定通常是经过充分测试和验证的。

SpringBoot 自动配置

自动配置(Auto-configuration)是 Spring Boot 对“约定大于配置”理念的核心实现机制。

它的工作原理是:Spring Boot 在启动时会扫描项目的 Classpath,根据你引入的依赖 Jar 包,来“猜测”你想要如何配置 Spring 应用,然后自动为你创建和配置所需的 Bean,并注入到 Spring 容器中。

关键组件:

  • @SpringBootApplication 注解:这是一个组合注解,它包含了一个至关重要的 @EnableAutoConfiguration 注解。
  • spring-boot-autoconfigure Jar 包:这个包里包含了大量针对常见技术(如 JDBC、JPA、Redis、MongoDB 等)的自动配置类。
  • META-INF/spring.factories 文件:在这个文件中,声明了所有在类路径下被发现时需要被处理的自动配置类。
  • 条件化配置(Conditional):这是自动配置的大脑。Spring Boot 使用大量的 @Conditional 注解(如 @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty)来判断某个自动配置是否应该生效。例如:“如果类路径下有 DataSource 类,并且用户没有自己配置 DataSource Bean,那么我就自动配置一个内存数据库的 DataSource。”
方面传统 Spring(配置大于约定)Spring Boot(约定大于配置)
配置量大量 XML/Java Config极少,甚至为零
项目搭建复杂,需手动整合各个组件快速,通过 Starter 依赖一键集成
部署需要外部 Web 服务器内嵌服务器,可执行 Jar/War
学习曲线较陡峭,需了解很多配置细节平缓,开箱即用
灵活性极高,可完全自定义同样高,通过覆盖自动配置来实现

使用指导

通常使用注解声明自动配置。

  1. 注解 @SpringBootApplication
  2. 注解 @EnableAutoConfiguration

注解 @SpringBootApplication底层使用了注解 @EnableAutoConfiguration,事实上注解 @EnableAutoConfiguration是启动自动配置的唯一注解。

注解 @EnableAutoConfiguration的文档说明如下:

Auto-configuration classes are usually applied based on your classpath and what beans you have defined.

自动配置类通常基于您的类路径以及您定义的 bean 来应用。

Auto-configuration classes are regular Spring @Configuration beans. They are located using ImportCandidates and the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).

自动配置类是普通的 Spring @Configuration bean。它们通过 ImportCandidates 和 SpringFactoriesLoader 机制(以此类为键)来定位。通常,自动配置的 bean 是 @Conditional bean(大多数情况下使用 @ConditionalOnClass 和 @ConditionalOnMissingBean 注解)。

类定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// org.springframework.boot.autoconfigure.SpringBootApplication
@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 {}

// org.springframework.boot.autoconfigure.EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // 忽略自动配置的类
	Class<?>[] exclude() default {};
    // 忽略自动配置的类的名称
	String[] excludeName() default {};    
}

源码分析

原理概述

注解 @EnableAutoConfiguration通过注解 @Import引入了AutoConfigurationImportSelector.class,该类将加载自动配置类。

待补充完整概述

// 一种ImportSelector的变体,表示在所有@Configuration bean 处理完成之后运行。

// org.springframework.context.annotation.DeferredImportSelector

流程概述

背景知识

在 SpringBoot 中,类 ConfigurationClassPostProcessor用于引导处理 @Configuration 类。自动配置类均为 Spring @Configurationbean,因此由类ConfigurationClassPostProcessor 处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// SpringBoot:
--> `ConfigurationClassPostProcessor` 解析启动类的注解 `@SpringBootApplication`
    --> ... 
    --> 解析注解 `@EnableAutoConfiguration`
        --> 解析注解 `@Import`
            --> 解析 `AutoConfigurationImportSelector.class`
                -> 因该 `selector instanceof DeferredImportSelector`,交由 `DeferredImportSelectorHandler` 处理
                    -> 将其存储在缓存 `org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#deferredImportSelectors` 
    --> 进行其它配置解析...
    --> 解析缓存 `org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#deferredImportSelectors` 中的配置默认仅 1 个配置`AutoConfigurationImportSelector.class`。
        --> 实际处理逻辑为 `org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports`。
            --> 调用`org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports`  `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup`中的方法获取自动配置类的全限定名称字符串
            --> for-each entrys遍历上一步骤获取的自动配置类进行解析
                --> 调用`org.springframework.util.ClassUtils#forName` 实例化自动配置类
                --> 调用`org.springframework.context.annotation.ConfigurationClassParser#processImports`方法解析这些配置类
                    --> 优先作为`ImportSelector``ImportBeanDefinitionRegistrar`进行解析
                    --> 如果不满足条件则将其作为普通配置类进行完整解析调用`org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass`。

类定义

上述流程概述涉及的类定义如下

1
2
3
// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
1
2
3
4
5
6
7
// 一种`ImportSelector`的变体,表示在所有`@Configuration` bean 处理完成之后运行。
// org.springframework.context.annotation.DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector {}

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup
private static class AutoConfigurationGroup
        implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler
private class DeferredImportSelectorHandler {}

// org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping
private static class DeferredImportSelectorGrouping {}

// org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler
private class DeferredImportSelectorGroupingHandler {}

// org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping
private static class DeferredImportSelectorGrouping {}

流程详情

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private class DeferredImportSelectorHandler {

    @Nullable
    private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

    public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
        DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
        // 因 `deferredImportSelectors` 是默认初始化为空 ArrayList 的,第一次调用该`handle`方法(且未调用过`process()`方法)时,必定不为空,即先缓存下了,延迟批量处理。
        // 当调用 `process()`方法后,`deferredImportSelectors` 会被值为空,之后调用`handle`方法时,将不再缓存,而是立即处理。
        if (this.deferredImportSelectors == null) {
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            handler.register(holder);
            handler.processGroupImports();
        }
        else {
            // 缓存。
            this.deferredImportSelectors.add(holder);
        }
    }

    public void process() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        // 置为 null,之后将不再缓存 selector。
        this.deferredImportSelectors = null;
        try {
            if (deferredImports != null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                deferredImports.forEach(handler::register);
                handler.processGroupImports();
            }
        }
        finally {
            this.deferredImportSelectors = new ArrayList<>();
        }
    }
}

END