Spring Boot 自动加载机制详解:从原理到手写一个 Starter
Spring Boot 自动加载机制详解:从原理到手写一个 Starter
前言
Spring Boot 最吸引人的地方之一,就是“开箱即用”。
引入 spring-boot-starter-web,Tomcat、Spring MVC、JSON 转换器都准备好了;引入 spring-boot-starter-data-redis,Redis 相关组件就能按配置自动装配;引入数据库驱动和连接池,DataSource 也会自动出现。
这背后不是魔法,而是一套非常清晰的机制:自动配置(Auto-configuration)。
本文分两部分:
- 先讲 Spring Boot 自动加载机制到底如何工作。
- 再手写一个 jar,让业务项目只要引入依赖,就能自动加载我们的 Bean。
目标是读完之后,你不仅知道 @SpringBootApplication 为什么能启动一堆东西,还能自己写一个类似 xxx-spring-boot-starter 的组件。
一、先看一个现象
一个最小 Spring Boot 应用通常只有几行代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
没有写:
@EnableWebMvc
@ComponentScan(...)
@Bean
public DispatcherServlet dispatcherServlet() { ... }
但 Web 应用仍然能启动。
原因是 @SpringBootApplication 不是一个普通注解,它内部组合了几个关键能力:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}
其中和“自动加载”关系最大的就是:
@EnableAutoConfiguration
它负责打开 Spring Boot 的自动配置入口。
二、什么是自动配置
Spring 官方文档对自动配置的解释可以概括为一句话:
Spring Boot 会根据你添加到 classpath 中的 jar 依赖,尝试自动配置你的 Spring 应用。
比如:
- classpath 里有 Spring MVC,Spring Boot 尝试配置 Web MVC。
- classpath 里有 Jackson,Spring Boot 尝试配置 JSON 序列化。
- classpath 里有 HSQLDB,并且你没有手动配置
DataSource,Spring Boot 可以自动配置内存数据库。 - classpath 里有 Redis 客户端,并且配置了 Redis 地址,Spring Boot 可以自动配置 Redis 相关 Bean。
重点是两个词:
classpath + 条件判断
不是只要有自动配置类就一定生效。Spring Boot 会先判断条件,条件满足才注册 Bean。
三、自动配置的核心链路
可以把 Spring Boot 自动加载理解成这条链:
@SpringBootApplication
↓
@EnableAutoConfiguration
↓
加载自动配置候选类
↓
逐个判断 @Conditional 条件
↓
条件满足则注册 @Bean
↓
用户自定义 Bean 优先,自动配置退让
这里有三个核心点。
1. 自动配置类从哪里来
Spring Boot 会扫描依赖 jar 中的自动配置声明文件。
在 Spring Boot 3.x/4.x 推荐方式中,自动配置类放在这个文件里:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容是一行一个自动配置类全限定名:
com.example.demo.autoconfigure.HelloAutoConfiguration
com.example.demo.autoconfigure.HelloWebAutoConfiguration
只要你的 jar 在业务项目 classpath 中,Spring Boot 就能发现这个文件,然后把里面列出的类作为自动配置候选类。
补充:Spring Boot 2.x 常见写法是通过
META-INF/spring.factories的EnableAutoConfigurationkey 注册自动配置类。Spring Boot 3 以后更推荐使用AutoConfiguration.imports。
2. 自动配置类怎么决定是否生效
自动配置类通常不是无条件生效,而是配合条件注解使用。
常见条件注解:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass |
classpath 中存在某个类时生效 |
@ConditionalOnMissingBean |
容器中不存在某个 Bean 时生效 |
@ConditionalOnBean |
容器中存在某个 Bean 时生效 |
@ConditionalOnProperty |
配置项满足条件时生效 |
@ConditionalOnWebApplication |
当前是 Web 应用时生效 |
@ConditionalOnResource |
指定资源存在时生效 |
例如:
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
}
含义是:只有 classpath 中存在 ObjectMapper,这段配置才有意义。
再看一个更常见的例子:
@Bean
@ConditionalOnMissingBean
public HelloService helloService() {
return new HelloService();
}
含义是:如果用户自己已经定义了 HelloService,自动配置就不再创建默认 Bean。
这就是 Spring Boot 自动配置的一个重要原则:
Spring Boot 提供默认值,但用户显式配置优先。
3. 自动配置不是组件扫描
很多人第一次写 starter 时会犯一个错误:以为把类放进 jar,业务项目就会自动扫描到。
实际上,业务项目的 @ComponentScan 默认只扫描启动类所在包及其子包。你的 starter jar 通常不在这个包下面,所以不会被扫描。
正确做法不是让业务项目扫描你的 jar,而是让你的 jar 提供自动配置声明:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Spring Boot 通过这个文件找到你的自动配置类。
四、自动配置和 starter 的关系
很多依赖名字里都有 starter,比如:
spring-boot-starter-web
spring-boot-starter-data-redis
my-company-spring-boot-starter
starter 本质上不是特殊技术,而是一种依赖组织方式。
通常一个 starter 会做两件事:
- 引入功能所需的依赖。
- 引入自动配置模块,让 Spring Boot 能自动创建相关 Bean。
可以简单理解:
starter = 依赖集合 + 自动配置入口
在企业内部,我们经常会做自己的 starter,例如:
- 统一日志 starter
- 统一鉴权 starter
- 统一异常处理 starter
- 统一 Redis 工具 starter
- 统一接口签名 starter
- 统一消息发送 starter
业务项目只需要引入依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
然后配置几项属性,就能直接使用相关能力。
五、手写一个最小 starter
我们写一个非常简单的 starter:
业务项目引入 jar 后,可以自动注入一个 HelloService。
使用方式:
@RestController
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
@GetMapping("/hello")
public String hello() {
return helloService.sayHello("Spring Boot");
}
}
配置:
hello:
enabled: true
prefix: "你好"
访问 /hello 返回:
你好, Spring Boot
六、项目结构
创建一个模块:
hello-spring-boot-starter
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── hello
│ ├── HelloProperties.java
│ ├── HelloService.java
│ └── autoconfigure
│ └── HelloAutoConfiguration.java
└── resources
└── META-INF
└── spring
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
注意自动配置类的位置:
com.example.hello.autoconfigure.HelloAutoConfiguration
它最好放在一个明确的 autoconfigure 包里,不要指望业务项目通过组件扫描发现它。
七、编写 pom.xml
示例基于 Spring Boot 3.x:
<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>hello-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.3.0</spring-boot.version>
</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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
这里重点是:
spring-boot-autoconfigure
它提供了 @AutoConfiguration、@ConditionalOnMissingBean、@ConditionalOnProperty 等自动配置相关能力。
spring-boot-configuration-processor 用来生成配置元数据,让使用方在 IDE 中写 hello.prefix 时有提示。
八、编写配置属性类
package com.example.hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
/**
* 是否启用 HelloService 自动配置。
*/
private boolean enabled = true;
/**
* 问候语前缀。
*/
private String prefix = "Hello";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}
这个类负责把配置文件中的:
hello:
enabled: true
prefix: "你好"
绑定成 Java 对象。
九、编写业务能力类
package com.example.hello;
public class HelloService {
private final HelloProperties properties;
public HelloService(HelloProperties properties) {
this.properties = properties;
}
public String sayHello(String name) {
return properties.getPrefix() + ", " + name;
}
}
这里不要加 @Service。
原因是我们不希望它依赖组件扫描,而是由自动配置类决定什么时候创建。
十、编写自动配置类
package com.example.hello.autoconfigure;
import com.example.hello.HelloProperties;
import com.example.hello.HelloService;
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
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnProperty(
prefix = "hello",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public class HelloAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties);
}
}
逐行解释:
@AutoConfiguration
声明这是一个 Spring Boot 自动配置类。
@EnableConfigurationProperties(HelloProperties.class)
启用配置属性绑定,让 hello.prefix 这类配置能绑定到 HelloProperties。
@ConditionalOnProperty(...)
控制配置开关。默认启用,用户可以通过下面配置关闭:
hello:
enabled: false
@ConditionalOnMissingBean
如果业务项目自己定义了 HelloService,自动配置就退让,不再创建默认对象。
这正是 Spring Boot starter 应该遵循的设计:提供默认实现,但允许用户覆盖。
十一、注册自动配置类
创建文件:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:
com.example.hello.autoconfigure.HelloAutoConfiguration
注意:
- 一行一个自动配置类。
- 可以用
#写注释。 - 自动配置类应该通过这个文件加载,不应该依赖业务项目组件扫描。
- 如果有多个自动配置类,需要考虑顺序,可以用
@AutoConfigureBefore、@AutoConfigureAfter或@AutoConfiguration(before = ..., after = ...)。
到这里,一个最小 starter 就完成了。
十二、在业务项目中使用
业务项目引入依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
配置:
hello:
prefix: "你好"
使用:
package com.example.demo;
import com.example.hello.HelloService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
@GetMapping("/hello")
public String hello() {
return helloService.sayHello("Spring Boot");
}
}
启动业务项目后,HelloService 会自动注入。
业务项目没有写:
@Bean
public HelloService helloService() { ... }
也没有扫描 starter 包。
这就是自动配置的效果。
十三、用户如何覆盖默认 Bean
如果业务项目不想使用 starter 默认实现,可以自己声明一个 Bean:
@Configuration
public class CustomHelloConfiguration {
@Bean
public HelloService helloService() {
HelloProperties properties = new HelloProperties();
properties.setPrefix("自定义");
return new HelloService(properties);
}
}
由于自动配置类中有:
@ConditionalOnMissingBean
Spring Boot 会检测到容器里已经有 HelloService,于是不会再创建 starter 中的默认 Bean。
这就是“自动配置退让机制”。
十四、如何验证自动配置是否生效
1. 启动时开启 debug
启动应用时加:
java -jar app.jar --debug
或者配置:
debug=true
Spring Boot 会输出条件评估报告,告诉你哪些自动配置生效了,哪些没有生效,以及原因是什么。
2. 使用 Actuator conditions
如果项目引入了 Actuator,可以查看 conditions 端点:
/actuator/conditions
它能帮助排查自动配置为什么没有生效。
3. 写自动配置测试
Spring Boot 官方推荐使用 ApplicationContextRunner 测试自动配置。
示例:
package com.example.hello.autoconfigure;
import com.example.hello.HelloService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
class HelloAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HelloAutoConfiguration.class));
@Test
void shouldCreateHelloServiceByDefault() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(HelloService.class);
assertThat(context.getBean(HelloService.class).sayHello("Boot"))
.isEqualTo("Hello, Boot");
});
}
@Test
void shouldUseConfiguredPrefix() {
contextRunner
.withPropertyValues("hello.prefix=你好")
.run(context -> {
assertThat(context.getBean(HelloService.class).sayHello("Boot"))
.isEqualTo("你好, Boot");
});
}
@Test
void shouldNotCreateHelloServiceWhenDisabled() {
contextRunner
.withPropertyValues("hello.enabled=false")
.run(context -> {
assertThat(context).doesNotHaveBean(HelloService.class);
});
}
}
测试 starter 时,不建议直接启动一个完整 Spring Boot 应用。ApplicationContextRunner 更轻量,也更适合组合不同条件。
十五、常见问题
1. 为什么我的自动配置类没有生效
优先检查四件事:
- jar 是否真的在业务项目 classpath 中。
AutoConfiguration.imports路径是否正确。- 文件中写的自动配置类全限定名是否正确。
- 条件注解是否没有满足,比如配置项关闭、缺少依赖、用户已经定义了同类型 Bean。
2. 为什么不要在自动配置类上使用 @ComponentScan
自动配置应该是精确、可控的。
如果你在自动配置类上加 @ComponentScan,可能会把一堆不该注册的类扫描进业务项目,导致 Bean 冲突、启动变慢、行为不可预测。
Spring 官方文档也明确建议:自动配置类不要通过组件扫描去找额外组件,需要引入其他配置时应使用明确的 @Import。
3. @Configuration 和 @AutoConfiguration 有什么区别
@AutoConfiguration 本质上也是配置类,但它表达的是“这是给 Spring Boot 自动配置机制使用的配置类”。
普通业务配置类通常由组件扫描发现。
自动配置类应该由 AutoConfiguration.imports 声明后被 Spring Boot 加载。
4. starter 一定要叫 spring-boot-starter 吗
不一定。
命名只是约定。官方 starter 通常叫:
spring-boot-starter-xxx
第三方或公司内部 starter 通常建议叫:
xxx-spring-boot-starter
例如:
mybatis-spring-boot-starter
company-log-spring-boot-starter
这样可以避免和官方 starter 混淆。
5. 一个 starter 里能有多个自动配置类吗
可以。
例如:
com.example.hello.autoconfigure.HelloAutoConfiguration
com.example.hello.autoconfigure.HelloWebAutoConfiguration
com.example.hello.autoconfigure.HelloMetricsAutoConfiguration
分别处理基础能力、Web 能力、监控能力。
如果它们之间有顺序要求,再使用 @AutoConfigureAfter 或 @AutoConfigureBefore。
十六、设计一个好 starter 的原则
1. 默认可用,但允许覆盖
starter 应该提供合理默认值,但不能绑死用户。
典型做法:
@ConditionalOnMissingBean
让业务项目可以通过自定义 Bean 覆盖默认实现。
2. 功能要有开关
建议提供类似配置:
hello:
enabled: true
并配合:
@ConditionalOnProperty(prefix = "hello", name = "enabled", matchIfMissing = true)
用户遇到问题时可以快速关闭。
3. 条件要尽量精确
不要无条件创建一堆 Bean。
如果依赖某个类:
@ConditionalOnClass(SomeClient.class)
如果只在 Web 环境生效:
@ConditionalOnWebApplication
如果依赖配置项:
@ConditionalOnProperty(...)
条件越精确,starter 对业务项目的侵入越小。
4. 不要抢业务项目的控制权
starter 是提供默认能力,不是接管项目。
避免:
- 随意扫描业务包
- 随意覆盖用户 Bean
- 在启动时做耗时网络请求
- 在自动配置阶段执行复杂业务逻辑
- 默认开启高风险行为
5. 提供配置元数据和文档
如果 starter 暴露配置项,建议引入:
spring-boot-configuration-processor
并为配置字段写注释。这样使用方在 IDE 中能看到提示。
同时要写清楚:
- 需要引入什么依赖
- 支持哪些配置项
- 默认行为是什么
- 如何关闭
- 如何覆盖默认 Bean
十七、完整加载流程回顾
业务项目引入 starter 后,大致流程如下:
1. 业务项目启动
2. @SpringBootApplication 启用自动配置
3. Spring Boot 读取 classpath 中的 AutoConfiguration.imports
4. 找到 HelloAutoConfiguration
5. 判断 @ConditionalOnProperty 等条件
6. 绑定 HelloProperties
7. 如果容器中没有 HelloService,则创建默认 HelloService
8. 业务代码通过构造方法注入 HelloService
这就是“写一个 jar,让 Spring Boot 自动加载起来”的核心。
总结
Spring Boot 自动配置的本质不是神秘的黑盒,而是一套清晰的约定:
@SpringBootApplication开启自动配置。- Spring Boot 根据 classpath 和自动配置声明文件寻找候选配置类。
- 自动配置类通过条件注解决定是否生效。
- 默认 Bean 通过
@ConditionalOnMissingBean给用户让路。 - 自定义 starter 通过
AutoConfiguration.imports暴露自动配置类。 - 好的 starter 应该默认可用、条件精确、允许覆盖、可以关闭、易于测试。
如果只记一句话:
starter 负责把 jar 放进 classpath,AutoConfiguration.imports 负责让 Spring Boot 找到配置类,Conditional 注解负责决定是否真正注册 Bean。
掌握这条链路后,再看各种官方 starter 或第三方 starter,就不再是“为什么一引入依赖就能用”的疑问,而是可以清楚地知道:它一定是在某个自动配置类里,根据某些条件,注册了一批默认 Bean。
参考资料
- Spring Boot Reference: Auto-configuration
https://docs.spring.io/spring-boot/reference/using/auto-configuration.html - Spring Boot Reference: Creating Your Own Auto-configuration
https://docs.spring.io/spring-boot/reference/features/developing-auto-configuration.html

浙公网安备 33010602011771号