第3课:SpringBoot4.x 自动配置原理:@EnableAutoConfiguration底层源码剖析

文章目录

    • [1. 课前导读](#1. 课前导读)
    • [2. 知识目标](#2. 知识目标)
    • [3. 理论讲解](#3. 理论讲解)
      • [3.1 自动配置的本质:两个关键设计](#3.1 自动配置的本质:两个关键设计)
      • [3.2 Spring Boot 4.x模块化:自动配置的"结构性变化"](#3.2 Spring Boot 4.x模块化:自动配置的“结构性变化”)
      • [3.3 候选类的加载:从spring.factories到AutoConfiguration.imports](#3.3 候选类的加载:从spring.factories到AutoConfiguration.imports)
      • [3.4 条件装配的两道关口](#3.4 条件装配的两道关口)
    • [4. 环境搭建](#4. 环境搭建)
      • [4.1 项目创建](#4.1 项目创建)
      • [4.2 父POM(autoconfig-demo/pom.xml)](#4.2 父POM(autoconfig-demo/pom.xml))
      • [4.3 自定义Starter模块的POM](#4.3 自定义Starter模块的POM)
      • [4.4 测试应用的POM](#4.4 测试应用的POM)
    • [5. 代码实战](#5. 代码实战)
      • [5.1 自定义服务类:SmsService.java](#5.1 自定义服务类:SmsService.java)
      • [5.2 配置属性类:SmsProperties.java](#5.2 配置属性类:SmsProperties.java)
      • [5.3 自动配置类:SmsAutoConfiguration.java](#5.3 自动配置类:SmsAutoConfiguration.java)
      • [5.4 注册自动配置类](#5.4 注册自动配置类)
      • [5.5 测试应用的启动类](#5.5 测试应用的启动类)
      • [5.6 测试应用的配置文件](#5.6 测试应用的配置文件)
      • [5.7 运行与验证](#5.7 运行与验证)
    • [6. 底层源码剖析](#6. 底层源码剖析)
      • [6.1 @EnableAutoConfiguration 的注解结构](#6.1 @EnableAutoConfiguration 的注解结构)
      • [6.2 为什么必须是 DeferredImportSelector](#6.2 为什么必须是 DeferredImportSelector)
      • [6.3 核心管线:getAutoConfigurationEntry()](#6.3 核心管线:getAutoConfigurationEntry())
      • [6.4 候选加载:ImportCandidates 的实现](#6.4 候选加载:ImportCandidates 的实现)
      • [6.5 AutoConfigurationReplacements:平滑迁移的"垫片"](#6.5 AutoConfigurationReplacements:平滑迁移的“垫片”)
      • [6.6 第一道过滤:不加载字节码的快速剔除](#6.6 第一道过滤:不加载字节码的快速剔除)
      • [6.7 条件评估:SpringBootCondition 模板方法](#6.7 条件评估:SpringBootCondition 模板方法)
      • [6.8 排序:AutoConfigurationSorter](#6.8 排序:AutoConfigurationSorter)
      • [6.9 排错利器:ConditionEvaluationReport](#6.9 排错利器:ConditionEvaluationReport)
    • [7. 常见坑点与生产问题排查](#7. 常见坑点与生产问题排查)
    • [8. 本章总结](#8. 本章总结)
    • [9. 课后作业](#9. 课后作业)
  • [🔗 SpringBoot 4.x 从入门到企业级实战系列课程](#🔗 SpringBoot 4.x 从入门到企业级实战系列课程)

本文是《SpringBoot 4.x 从入门到精通》系列第3课,全文约13000字,建议阅读时间50分钟。

环境要求:Spring Boot 4.x(基于4.1.0 GA源码),Spring Framework 7.0.x,Java 17+(推荐Java 21/25)


1. 课前导读

上一课我们解决了"依赖从哪来"的问题------通过Starter引入功能、通过BOM管理版本。但还有一个更深层的问题没有回答:为什么在pom.xml里加一个spring-boot-starter-webmvc,Spring Boot就自动配置好了DispatcherServlet、内嵌Tomcat、JSON序列化器?

这就是Spring Boot最核心的"魔法"------自动配置(Auto-Configuration) 。它的入口是启动类上的@SpringBootApplication注解,而核心机制则藏在@EnableAutoConfiguration和它导入的AutoConfigurationImportSelector中。

Spring Boot 4.x在自动配置领域进行了一次"结构性变化":从单一的spring-boot-autoconfigure模块拆分为core(8个核心模块)+ module(128个功能模块) 的两层结构,自动配置类被分散到各个功能模块中,每个模块在自己的JAR里声明自己的AutoConfiguration.imports文件。整个仓库共有99个AutoConfiguration.imports文件,共同构成全部候选配置类。

这意味着:自动配置的声明方式变了、包名变了、调试方式也变了。 如果你拿着Spring Boot 3.x的源码分析来对照4.x的代码,很可能会发现入口都找不到。这节课将带你从@EnableAutoConfiguration注解出发,把自动配置从"注解入口 → 候选加载 → 两道过滤 → 拓扑排序 → Bean注册"的完整链路走一遍,并手把手教你自定义一个自动配置类。

前置知识 :第2课的Maven依赖管理和Starter概念;了解Spring的@Configuration和@Bean注解。

学习难点 :理解DeferredImportSelector的延迟执行机制、模块化拆分后候选类的发现方式、条件注解的评估时机。

【配图描述:Spring Boot 4.x 自动配置全景图------从@SpringBootApplication到Bean注册的完整链路】


2. 知识目标

学完本节课,你必须掌握以下核心知识点:

  1. @EnableAutoConfiguration的注解结构 :它组合了哪些元注解,@Import导入了什么,为什么选择DeferredImportSelector而非普通的ImportSelector。
  2. 候选类的加载机制 :从spring.factories到AutoConfiguration.imports的历史演进,ImportCandidates如何扫描classpath上所有模块的imports文件。
  3. 4.x模块化对自动配置的影响 :core/module两层结构、包名迁移、AutoConfigurationReplacements平滑迁移机制。
  4. 条件装配的底层原理 :AutoConfigurationImportFilter的第一道过滤、Condition接口与SpringBootCondition模板方法、@ConditionalOnClass/@ConditionalOnMissingBean的判定逻辑。
  5. 自定义自动配置实战:编写一个完整的Starter,包括自动配置类、imports文件、配置属性绑定和条件注解。

3. 理论讲解

3.1 自动配置的本质:两个关键设计

在深入源码之前,先理解自动配置的两个核心设计思想。

思想一:约定优于配置。 自动配置类本质上是一批预先编写好的@Configuration类,它们基于"大多数应用都会这样配置"的约定,提供了默认的Bean定义。比如DispatcherServletAutoConfiguration约定了一个Spring MVC应用需要一个DispatcherServlet,于是自动注册了一个。

思想二:条件化装配。 自动配置类不是无条件生效的,而是通过@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等条件注解控制。只有当类路径中存在特定类、容器中不存在特定Bean、配置文件中开启了特定属性时,自动配置才会生效。这就是为什么引入不同的Starter会激活不同的自动配置。

思想三:用户配置优先。 所有自动配置类都通过DeferredImportSelector延迟处理,保证用户定义的@Bean和@Component先注册。当自动配置类中的@ConditionalOnMissingBean执行判断时,已经能看到用户定义的所有Bean,从而实现"用户定义的覆盖默认配置"。

3.2 Spring Boot 4.x模块化:自动配置的"结构性变化"

Spring Boot 3.5的spring-boot-autoconfigure JAR已经膨胀到了2 MiB,包含了所有技术的自动配置类。即使你的应用只用了Web和JDBC,JPA、Security、Redis、Kafka等模块的自动配置类仍然会被加载到classpath中。

Spring Boot 4.x从根本上改变了这一点:将spring-boot-autoconfigure拆分为core(8个核心模块)+ module(128个功能模块) 的两层结构。

复制代码
core/spring-boot-autoconfigure/.../AutoConfiguration.imports   ← 仅12行(核心基建)
module/spring-boot-jackson/.../AutoConfiguration.imports        ← 1行:JacksonAutoConfiguration
module/spring-boot-webmvc/.../AutoConfiguration.imports         ← 6行:DispatcherServlet等

包名也迁移到了"按特性分组"的新体系:org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration、org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration。旧的org.springframework.boot.autoconfigure.*包下只保留了admin、aop、cache、context等核心子包,webmvc、jackson等大功能全部搬走。

关键洞察 :加载逻辑没有变化。AutoConfigurationImportSelector是通用的------它不看模块、不分组,只是通过ImportCandidates把classpath上所有JAR的imports文件全部合并。模块化对Selector完全透明:拆分只发生在声明层面,运行机制零改动。

【配图描述:Spring Boot 3.x单体autoconfigure vs 4.x模块化autoconfigure的架构对比图】

3.3 候选类的加载:从spring.factories到AutoConfiguration.imports

Spring Boot 2.x使用META-INF/spring.factories文件声明自动配置类,这是一种通用的SPI机制,所有扩展点(包括初始化器、监听器)都写在同一个文件里。

从Spring Boot 2.7开始,自动配置类被迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中。到了Spring Boot 4.x,spring.factories方式已被彻底移除 ,必须使用新的AutoConfiguration.imports文件。

两者的区别:

维度 spring.factories AutoConfiguration.imports
文件路径 META-INF/spring.factories META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
格式 键值对(Properties格式) 每行一个全限定类名
用途 所有Spring Boot扩展点 仅用于自动配置类
4.x状态 已移除 唯一方式

AutoConfiguration.imports文件的内容示例:

复制代码
com.example.autoconfigure.SmsAutoConfiguration
com.example.autoconfigure.EmailAutoConfiguration

每行一个自动配置类的全限定名,不需要键名。

3.4 条件装配的两道关口

Spring Boot的自动配置采用了"两道过滤"的设计来提升性能。

第一道:类存在性过滤(不加载字节码)。 候选自动配置类可能有上百个,如果全部交给Spring的配置类解析器(它会用ASM读取每个类的字节码、解析注解),开销可观。Spring Boot的设计是:在真正读取类字节码之前,先用最廉价的手段把明显不匹配的候选剔除。这就是AutoConfigurationImportFilter接口的用途。典型的过滤器是OnClassCondition,它通过检查类路径中是否存在特定类来决定是否保留候选。

第二道:条件评估(加载字节码后)。 通过第一道过滤的候选类会被加载并交给Spring的条件评估机制。SpringBootCondition是一个模板方法类,定义了条件评估的骨架:先调用getMatchOutcome()获取评估结果,然后根据结果记录日志。

常用的条件注解:

条件注解 判定逻辑
@ConditionalOnClass 类路径中存在指定类
@ConditionalOnMissingClass 类路径中不存在指定类
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnProperty 配置文件中存在指定属性
@ConditionalOnWebApplication 当前是Web应用

4. 环境搭建

4.1 项目创建

本节课需要创建一个包含自定义Starter的实验项目。我们创建一个多模块项目,结构如下:

复制代码
autoconfig-demo/
├── pom.xml                          (父POM)
├── demo-spring-boot-starter/        (自定义Starter模块)
│   ├── pom.xml
│   └── src/main/
│       ├── java/com/example/starter/
│       │   ├── SmsService.java
│       │   ├── SmsProperties.java
│       │   └── SmsAutoConfiguration.java
│       └── resources/META-INF/spring/
│           └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── demo-app/                        (测试应用)
    ├── pom.xml
    └── src/main/
        ├── java/com/example/app/
        │   └── DemoApplication.java
        └── resources/application.yml

4.2 父POM(autoconfig-demo/pom.xml)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>autoconfig-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>demo-spring-boot-starter</module>
        <module>demo-app</module>
    </modules>

    <properties>
        <java.version>21</java.version>
        <spring-boot.version>4.0.8</spring-boot.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

4.3 自定义Starter模块的POM

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>autoconfig-demo</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-spring-boot-starter</artifactId>

    <dependencies>
        <!-- 自动配置模块,提供 @AutoConfiguration 等注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!-- 配置属性绑定支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
        </dependency>
        <!-- 配置处理器,生成配置元数据 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

4.4 测试应用的POM

xml 复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.example</groupId>
        <artifactId>autoconfig-demo</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>demo-app</artifactId>

    <dependencies>
        <!-- 引入自定义 Starter -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>demo-spring-boot-starter</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- Web Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

5. 代码实战

5.1 自定义服务类:SmsService.java

java 复制代码
package com.example.starter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 短信发送服务接口。
 * 自动配置类会注册一个默认实现,用户可以通过
 * 定义自己的 @Bean 来覆盖。
 */
public interface SmsService {
    String send(String phone, String message);
}

/**
 * 默认的短信发送实现(控制台输出,用于演示)。
 */
class DefaultSmsService implements SmsService {

    private static final Logger log = LoggerFactory.getLogger(DefaultSmsService.class);

    private final SmsProperties properties;

    DefaultSmsService(SmsProperties properties) {
        this.properties = properties;
    }

    @Override
    public String send(String phone, String message) {
        log.info("[默认短信服务] 提供商={}, 发送至={}, 内容={}",
                properties.getProvider(), phone, message);
        return "默认短信服务已发送: " + message + " -> " + phone;
    }
}

5.2 配置属性类:SmsProperties.java

java 复制代码
package com.example.starter;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 短信服务的配置属性。
 * 
 * 在 application.yml 中配置:
 * demo:
 *   sms:
 *     enabled: true
 *     provider: aliyun
 *     sign-name: 我的应用
 * 
 * 配置元数据由 spring-boot-configuration-processor 自动生成,
 * 支持 IDE 自动补全。
 */
@ConfigurationProperties(prefix = "demo.sms")
public class SmsProperties {

    /** 是否启用短信服务 */
    private boolean enabled = true;

    /** 短信提供商,默认 "console" */
    private String provider = "console";

    /** 短信签名 */
    private String signName = "默认签名";

    /** 连接超时时间(毫秒) */
    private int timeout = 5000;

    // Getter / Setter
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }

    public String getProvider() { return provider; }
    public void setProvider(String provider) { this.provider = provider; }

    public String getSignName() { return signName; }
    public void setSignName(String signName) { this.signName = signName; }

    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }
}

5.3 自动配置类:SmsAutoConfiguration.java

java 复制代码
package com.example.starter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
 * 短信服务自动配置类。
 * 
 * 关键注解说明:
 * - @AutoConfiguration:4.x推荐写法,等价于 @Configuration(proxyBeanMethods = false)。
 *   proxyBeanMethods = false 关闭了 CGLIB 代理,对自动配置类是必要的,
 *   因为自动配置类的 @Bean 方法不应该被相互调用。
 * 
 * - @ConditionalOnProperty:只有 demo.sms.enabled=true(默认)时才生效
 * - @ConditionalOnMissingBean:如果用户已经定义了 SmsService,则跳过默认实现
 * - @EnableConfigurationProperties:启用配置属性绑定
 */
@AutoConfiguration
@ConditionalOnProperty(
        prefix = "demo.sms",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true   // 未配置时默认启用
)
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(SmsAutoConfiguration.class);

    public SmsAutoConfiguration() {
        log.info(">>> SmsAutoConfiguration 被加载 <<<");
    }

    /**
     * 注册默认的 SmsService 实现。
     * 
     * @ConditionalOnMissingBean 的判定时机非常关键:
     * 自动配置类通过 DeferredImportSelector 延迟处理,
     * 保证用户的 @Bean 先注册。所以当这个条件执行时,
     * 已经能看到用户定义的 Bean 了。
     */
    @Bean
    @ConditionalOnMissingBean(SmsService.class)
    public SmsService smsService(SmsProperties properties) {
        log.info(">>> 创建默认 SmsService,provider={} <<<", properties.getProvider());
        return new DefaultSmsService(properties);
    }
}

5.4 注册自动配置类

在demo-spring-boot-starter模块的src/main/resources/META-INF/spring/目录下创建文件:

文件名:org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容:

复制代码
com.example.starter.SmsAutoConfiguration

重要提醒:如果你的自动配置类没有在这个文件里注册,即使类上有所有正确的注解,也不会生效。这是最常见的"自动配置类为什么没生效"原因之一。

5.5 测试应用的启动类

java 复制代码
package com.example.app;

import com.example.starter.SmsService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    /**
     * 启动后自动测试短信服务。
     */
    @Bean
    CommandLineRunner testSms(SmsService smsService) {
        return args -> {
            String result = smsService.send("13800138000", "Hello Spring Boot 4.x!");
            System.out.println("测试结果: " + result);
        };
    }
}

5.6 测试应用的配置文件

yaml 复制代码
# application.yml
demo:
  sms:
    enabled: true
    provider: aliyun
    sign-name: 我的应用

5.7 运行与验证

编译并启动

bash 复制代码
cd autoconfig-demo
mvn clean install
cd demo-app
mvn spring-boot:run

预期输出

启动日志中会看到:

复制代码
>>> SmsAutoConfiguration 被加载 <<<
>>> 创建默认 SmsService,provider=aliyun <<<
测试结果: 默认短信服务已发送: Hello Spring Boot 4.x! -> 13800138000

验证条件装配:用户Bean覆盖默认Bean

在DemoApplication中添加一个自定义的SmsService:

java 复制代码
@Bean
SmsService customSmsService() {
    return (phone, message) -> {
        System.out.println("[自定义短信] " + message + " -> " + phone);
        return "自定义短信服务: " + message;
    };
}

重新启动后,日志中不再出现"创建默认 SmsService",因为@ConditionalOnMissingBean检测到了用户定义的Bean,跳过了默认配置。

验证条件装配:禁用属性

在application.yml中设置demo.sms.enabled=false,重新启动。SmsAutoConfiguration不会被加载,启动日志中不会出现"SmsAutoConfiguration 被加载"。如果CommandLineRunner依赖SmsService,会报NoSuchBeanDefinitionException。


6. 底层源码剖析

6.1 @EnableAutoConfiguration 的注解结构

@EnableAutoConfiguration的定义非常简洁,但包含了三个关键的元注解:

java 复制代码
// 源码路径:org.springframework.boot.autoconfigure.EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}
  • @AutoConfigurationPackage:将被注解类所在包注册为自动配置包,供JPA实体扫描等使用。
  • @Import(AutoConfigurationImportSelector.class):导入自动配置选择器,这是自动配置的核心入口。
  • exclude() / excludeName():允许手动排除特定的自动配置类,对应配置文件中的spring.autoconfigure.exclude。

6.2 为什么必须是 DeferredImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelector接口,而不是普通的ImportSelector:

java 复制代码
// 源码路径:org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware,
                   ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

DeferredImportSelector与普通ImportSelector的关键区别在于执行时机 。普通ImportSelector在配置类解析过程中立即执行,而DeferredImportSelector会延迟到所有常规配置类解析完毕后才执行。

这个延迟机制是"用户配置永远优先"的实现基础。当自动配置类中的@ConditionalOnMissingBean执行判断时,用户定义的@Bean和@Component已经全部注册到容器中,条件评估才能正确判断"是否存在用户自定义的Bean"。

6.3 核心管线:getAutoConfigurationEntry()

AutoConfigurationImportSelector的核心方法是selectImports(),它调用了getAutoConfigurationEntry()来完成整个加载流程:

java 复制代码
// 简化后的核心流程
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {

    // 1. 检查是否启用自动配置
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }

    // 2. 获取 @EnableAutoConfiguration 的 exclude/excludeName 属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);

    // 3. 从 classpath 上所有模块的 imports 文件加载候选配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

    // 4. 去重
    configurations = removeDuplicates(configurations);

    // 5. 应用用户排除项(包括 .replacements 文件中的旧类名映射)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);

    // 6. 第一道过滤:类存在性检查(不加载字节码)
    configurations = getConfigurationClassFilter().filter(configurations);

    // 7. 触发 AutoConfigurationImportEvent 事件
    fireAutoConfigurationImportEvents(configurations, exclusions);

    return new AutoConfigurationEntry(configurations, exclusions);
}

6.4 候选加载:ImportCandidates 的实现

候选类的加载由ImportCandidates完成。在4.x中,它会扫描classpath上所有JAR包 的META-INF/spring/目录下的AutoConfiguration.imports文件:

java 复制代码
// 源码路径:org.springframework.boot.context.annotation.ImportCandidates
public final class ImportCandidates implements Iterable<String> {

    private static final String LOCATION = "META-INF/spring/%s.imports";

    public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
        // 1. 构造文件路径:
        //    META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
        String location = String.format(LOCATION, annotation.getName());

        // 2. 使用 ClassLoader.getResources() 加载所有 JAR 中的同名文件
        Enumeration<URL> urls = classLoader.getResources(location);

        // 3. 逐行读取每个文件中的类名
        List<String> candidates = new ArrayList<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            candidates.addAll(loadFromUrl(url));
        }
        return new ImportCandidates(candidates);
    }
}

关键点:ClassLoader.getResources()会返回classpath上所有 同名资源文件的URL。在4.x的模块化架构下,spring-boot-jackson、spring-boot-webmvc等每个模块的JAR中都有自己的AutoConfiguration.imports文件,这些文件会被全部加载并合并。

6.5 AutoConfigurationReplacements:平滑迁移的"垫片"

Spring Boot 4.x引入了一个独立类AutoConfigurationReplacements,用于处理已被废弃或移动的自动配置类:

java 复制代码
// 源码路径:org.springframework.boot.autoconfigure.AutoConfigurationReplacements
final class AutoConfigurationReplacements {

    private static final String LOCATION = "META-INF/spring/%s.replacements";

    // 将旧类名映射到新类名
    // 例如:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
    //   -> org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration
}

它的作用是:如果老配置中的@AutoConfigureBefore、@AutoConfigureAfter或排除项仍然引用旧类名(模块化迁移前的类名),通过.replacements文件将旧类名映射到新类名,保证老配置不因类名搬迁而失效。getExclusions()方法末尾会执行replaceAll(excluded),排序阶段(AutoConfigurationSorter)也会套用同样的替换。

6.6 第一道过滤:不加载字节码的快速剔除

AutoConfigurationImportFilter接口定义了第一道过滤机制:

java 复制代码
// 源码路径:org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(String[] autoConfigurationClasses,
                    AutoConfigurationMetadata autoConfigurationMetadata);
}

这个过滤器通过检查类路径中是否存在特定类来快速剔除候选。典型的实现是OnClassCondition,它使用ClassNameFilter进行类存在性检查------通过读取类的元数据信息(而非加载字节码)来判断类是否存在,比ClassUtils.forName()更高效。

过滤器的元数据来自编译期生成的spring-autoconfigure-metadata.properties文件。Spring Boot的构建过程中会为每个自动配置类生成元数据,记录它依赖的类、Bean和属性,这样运行期就不需要加载字节码就能做初步判断。

6.7 条件评估:SpringBootCondition 模板方法

通过第一道过滤的候选类会被加载并交给Spring的条件评估机制。SpringBootCondition是所有Spring Boot条件注解的基类,它实现了模板方法模式:

java 复制代码
// 源码路径:org.springframework.boot.autoconfigure.condition.SpringBootCondition
public abstract class SpringBootCondition implements Condition {

    @Override
    public final boolean matches(ConditionContext context,
                                 AnnotatedTypeMetadata metadata) {
        // 1. 获取类名或方法名(用于日志)
        String classOrMethodName = getClassOrMethodName(metadata);

        // 2. 调用子类的 getMatchOutcome() 进行实际评估
        ConditionOutcome outcome = getMatchOutcome(context, metadata);

        // 3. 记录评估日志(--debug 模式下可见)
        logOutcome(classOrMethodName, outcome);

        // 4. 记录到 ConditionEvaluationReport
        recordEvaluation(context, classOrMethodName, outcome);

        return outcome.isMatch();
    }

    // 子类实现这个方法来定义具体的匹配逻辑
    public abstract ConditionOutcome getMatchOutcome(
            ConditionContext context, AnnotatedTypeMetadata metadata);
}

条件评估的两个阶段 :Spring Boot的条件评估发生在两个阶段------配置类解析阶段(决定哪些自动配置类需要加载)和Bean定义注册阶段(决定哪些@Bean方法需要执行)。@ConditionalOnClass在第一阶段评估,@ConditionalOnMissingBean通常在第二阶段评估(因为需要检查容器中已注册的Bean定义)。

6.8 排序:AutoConfigurationSorter

候选类加载和过滤完成后,还需要进行排序。AutoConfigurationSorter负责根据@AutoConfigureBefore、@AutoConfigureAfter和@AutoConfigureOrder注解确定自动配置类的执行顺序。排序采用拓扑排序算法:先根据@AutoConfigureBefore和@AutoConfigureAfter构建依赖图,然后按字母序处理剩余类。

6.9 排错利器:ConditionEvaluationReport

当自动配置不生效时,ConditionEvaluationReport是最重要的排查工具。在application.yml中开启debug: true或在启动参数中添加--debug,启动日志中会输出详细的自动配置报告:

复制代码
============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:
-----------------
   SmsAutoConfiguration matched:
      - @ConditionalOnProperty (demo.sms.enabled=true) matched

Negative matches:
-----------------
   MailAutoConfiguration did not match:
      - @ConditionalOnClass did not find required class 'jakarta.mail.internet.MimeMessage'

这份报告会列出所有自动配置类的评估结果,明确告诉你是哪个条件不满足导致了自动配置未生效。当你排查"为什么某个Bean没有注入"时,这是第一个应该查看的地方。


7. 常见坑点与生产问题排查

坑点1:自动配置类未在imports文件中注册

报错现象:自定义Starter引入后,自动配置类完全不生效,日志中无任何相关输出。

原因 :自动配置类没有在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中声明。4.x完全依赖这个文件来发现自动配置类,如果没注册,即使类上有所有正确的注解也不会生效。

解决方案 :检查Starter模块的resources/META-INF/spring/目录下是否有正确的imports文件,且文件路径完全匹配。

坑点2:使用旧版spring.factories方式

报错现象:从2.x/3.x升级到4.x后,自定义Starter的自动配置失效。

原因 :Spring Boot 4.x已彻底移除spring.factories方式加载自动配置类。

解决方案 :将META-INF/spring.factories中的自动配置声明迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。注意:spring.factories仍然用于ApplicationContextInitializer、ApplicationListener等其他扩展点,只是不再用于自动配置类。

坑点3:@ConditionalOnMissingBean 不生效

报错现象:用户定义了自定义Bean,但自动配置类仍然注册了默认Bean,导致Bean冲突。

原因 :@ConditionalOnMissingBean的评估时机问题。如果自动配置类没有通过DeferredImportSelector延迟处理,或者用户在自动配置类之后才定义Bean,条件评估时看不到用户的Bean。

解决方案 :确保自动配置类使用@AutoConfiguration(而非@Configuration),且通过imports文件加载(而非@ComponentScan)。这样DeferredImportSelector会保证延迟执行,用户Bean优先注册。

坑点4:模块化后包名变化导致导入失败

报错现象 :import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;编译失败。

原因 :Spring Boot 4.x将WebMvcAutoConfiguration从org.springframework.boot.autoconfigure.web.servlet迁移到了org.springframework.boot.webmvc.autoconfigure。

解决方案:更新import路径。参考第2课中的Starter坐标对照表,将旧包名替换为新包名。如果不确定新包名,可以通过IDE的"Find Class"功能搜索类名。

坑点5:自动配置类使用了@Configuration导致CGLIB代理

报错现象 :自动配置类中@Bean方法相互调用时出现意外行为,或性能下降。

原因 :@Configuration默认proxyBeanMethods = true,会生成CGLIB代理。自动配置类的@Bean方法不应该被相互调用(这违反了模块化原则),关闭代理能减少不必要的性能开销。

解决方案 :使用@AutoConfiguration注解(4.x推荐),它等价于@Configuration(proxyBeanMethods = false)。

坑点6:AutoConfiguration.imports中类名拼写错误

报错现象 :启动时报ClassNotFoundException,提示imports文件中的类不存在。

原因:imports文件中每行必须是全限定类名,且类必须存在于classpath中。拼写错误、多余空格、空行都会导致加载失败。

解决方案:仔细检查imports文件的格式:每行一个全限定类名,无多余空格,文件末尾无空行。


8. 本章总结

本节课围绕Spring Boot 4.x的自动配置原理,从注解入口到条件装配,完整剖析了"魔法"背后的机制。

入口机制 :@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)导入选择器。AutoConfigurationImportSelector实现DeferredImportSelector,延迟到所有用户配置类解析完毕后才执行,保证"用户配置永远优先"。

模块化重构 :Spring Boot 4.x将单一的spring-boot-autoconfigure拆分为core(8个核心模块)+ module(128个功能模块),每个模块在自己的JAR中声明AutoConfiguration.imports文件。加载逻辑不变------ImportCandidates扫描classpath上所有JAR的imports文件并合并。AutoConfigurationReplacements作为平滑迁移垫片,将旧类名映射到新类名。

两道过滤 :第一道AutoConfigurationImportFilter通过类存在性检查(不加载字节码)快速剔除不匹配的候选;第二道SpringBootCondition模板方法执行具体的条件评估。

调试利器 :ConditionEvaluationReport通过--debug或debug: true输出详细的自动配置评估报告,是排查自动配置失效问题的首选工具。

自定义Starter三要素 :自动配置类(@AutoConfiguration)+ imports文件(META-INF/spring/...AutoConfiguration.imports)+ 配置属性类(@ConfigurationProperties),三者缺一不可。


9. 课后作业

基础题1

启动本节课的demo-app,在application.yml中添加debug: true,观察启动日志中的CONDITIONS EVALUATION REPORT。找到SmsAutoConfiguration的评估结果,记录它匹配了哪些条件。然后设置demo.sms.enabled=false,重新启动,记录SmsAutoConfiguration的评估结果变化。

基础题2

在SmsAutoConfiguration中添加一个新的条件注解@ConditionalOnClass(name = "com.example.starter.SmsService"),分析这个条件是否总是为true(因为SmsService就在同一个模块中)。然后尝试添加@ConditionalOnClass(name = "com.aliyun.sms.SmsClient"),观察在类路径中不存在该类时,自动配置是否生效。解释为什么@ConditionalOnClass的name属性比value属性更安全。

综合实战题

创建一个完整的自定义Starter,实现一个"操作日志"组件,要求:

  1. Starter模块结构:

    • AuditProperties:配置属性类,前缀demo.audit,包含enabled(boolean,默认true)、logLevel(String,默认"INFO")、maxRetentionDays(int,默认30)
    • AuditService接口:定义record(String action, String detail)方法
    • DefaultAuditService:默认实现,使用SLF4J输出日志
    • AuditAutoConfiguration:自动配置类,使用@ConditionalOnProperty、@ConditionalOnMissingBean、@EnableConfigurationProperties
  2. imports文件 :在META-INF/spring/下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports,注册AuditAutoConfiguration

  3. 测试应用:

    • 引入自定义Starter
    • 注入AuditService并在Controller中调用
    • 提供GET /api/audit/test接口,调用auditService.record("TEST", "手动测试")
  4. 验证条件装配:

    • 在测试应用中定义一个自定义的AuditService Bean,验证默认Bean是否被覆盖
    • 在application.yml中设置demo.audit.enabled=false,验证AuditService不再被注入(启动时报错)
  5. 使用--debug模式 :启动时添加--debug参数,找到AuditAutoConfiguration的CONDITIONS EVALUATION REPORT,记录评估结果

扩展挑战 :为AuditAutoConfiguration添加@AutoConfigureBefore和@AutoConfigureAfter注解,模拟多个自动配置类之间的顺序依赖,通过--debug报告验证排序是否生效。

🔗 SpringBoot 4.x 从入门到企业级实战系列课程

去订阅

相关推荐
Rain的Java大神之路1 小时前
MySQL 被高并发打爆?从定位到分库分表逐层拆解(含秒杀库存实战)
java·后端·面试
cc_瀚海知行1 小时前
工具调用 6|助手什么时候该停下来?
java·服务器·前端·人工智能·python·ai
YDS8291 小时前
Small Spring IOC篇:应用上下文实现和Bean的扩展
java·后端·spring
Pniubi1 小时前
ConcurrentHashMap拆分线程池难点再讲
java·jvm
golang学习记1 小时前
IDEA 的 Java 智能,现在跑到 Cursor 和 VS Code 里去了
java·ide·vscode·intellij-idea·claude code
devilnumber1 小时前
springboot的保护机制怎么实现的,为何能做到服务不宕机
java·spring boot·后端
未秃头的程序猿1 小时前
一次重复扣款的复盘:分布式锁加错了维度,比不加锁更危险
java·redis·后端
估值探索者1 小时前
【Python量化技术指标实战 02】MACD金叉也分强弱?手工算一次 DIF/DEA 就懂了
java·开发语言·python·股票数据·股票数据api接口
m0_639906942 小时前
MyBatis 核心体系:核心接口、动态 SQL 原理、插件底层机制
java·sql·mybatis