文章目录
-
- [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. 常见坑点与生产问题排查)
-
- 坑点1:自动配置类未在imports文件中注册
- 坑点2:使用旧版spring.factories方式
- [坑点3:@ConditionalOnMissingBean 不生效](#坑点3:@ConditionalOnMissingBean 不生效)
- 坑点4:模块化后包名变化导致导入失败
- 坑点5:自动配置类使用了@Configuration导致CGLIB代理
- 坑点6:AutoConfiguration.imports中类名拼写错误
- [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. 知识目标
学完本节课,你必须掌握以下核心知识点:
- @EnableAutoConfiguration的注解结构 :它组合了哪些元注解,
@Import导入了什么,为什么选择DeferredImportSelector而非普通的ImportSelector。 - 候选类的加载机制 :从
spring.factories到AutoConfiguration.imports的历史演进,ImportCandidates如何扫描classpath上所有模块的imports文件。 - 4.x模块化对自动配置的影响 :core/module两层结构、包名迁移、
AutoConfigurationReplacements平滑迁移机制。 - 条件装配的底层原理 :
AutoConfigurationImportFilter的第一道过滤、Condition接口与SpringBootCondition模板方法、@ConditionalOnClass/@ConditionalOnMissingBean的判定逻辑。 - 自定义自动配置实战:编写一个完整的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,实现一个"操作日志"组件,要求:
-
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
-
imports文件 :在
META-INF/spring/下创建org.springframework.boot.autoconfigure.AutoConfiguration.imports,注册AuditAutoConfiguration -
测试应用:
- 引入自定义Starter
- 注入
AuditService并在Controller中调用 - 提供
GET /api/audit/test接口,调用auditService.record("TEST", "手动测试")
-
验证条件装配:
- 在测试应用中定义一个自定义的
AuditServiceBean,验证默认Bean是否被覆盖 - 在
application.yml中设置demo.audit.enabled=false,验证AuditService不再被注入(启动时报错)
- 在测试应用中定义一个自定义的
-
使用
--debug模式 :启动时添加--debug参数,找到AuditAutoConfiguration的CONDITIONS EVALUATION REPORT,记录评估结果
扩展挑战 :为AuditAutoConfiguration添加@AutoConfigureBefore和@AutoConfigureAfter注解,模拟多个自动配置类之间的顺序依赖,通过--debug报告验证排序是否生效。