SpringBoot 极大简化了 Spring 项目的配置,告别繁杂 XML 配置,核心能力就是自动配置(AutoConfiguration)。很多后端开发天天使用各类 starter,例如spring-boot-starter-web、spring-boot-starter-jdbc,但大多只停留在 “引入依赖就能直接用” 的层面。不清楚自动配置底层如何生效、条件注解如何工作,也不会封装团队通用组件、手写自定义 Starter。本文从 SpringBoot 自动配置底层源码流程、条件注解、元数据文件,手把手从零开发一套生产规范的自定义 Starter。
前言
在 SpringBoot 诞生之前,搭建 Spring Web 项目,需要大量手动配置:
- 配置
DispatcherServlet、视图解析器 - 配置数据源 DataSource、事务管理器
- 配置扫描路径、配置 Bean、导入 xml 文件 项目依赖版本还需要自己管理,版本冲突问题非常头疼。
SpringBoot 两大核心特性:
- 自动配置:根据 classpath 下存在的类、配置文件属性,自动装配 Bean 到 IOC 容器。
- Starter 启动器:一站式依赖聚合,引入 starter,自动导入配套依赖,无需手动逐个管理版本。
Starter 负责管理依赖;自动配置负责自动创建 Bean。二者配合实现 “开箱即用”。 底层基础:IOC 容器、条件注解、@Import 导入机制。
一、@SpringBootApplication 注解拆解
SpringBoot 项目启动类上的@SpringBootApplication是一个复合注解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Configuration @EnableAutoConfiguration @ComponentScan public @interface SpringBootApplication {}拆分为三大核心注解:
@ComponentScan组件扫描,默认扫描当前启动类所在包及其所有子包,识别
@Component/@Service/@Repository/@Controller,解析成 BeanDefinition 注册到容器。坑点:启动类放在根包,否则会出现 Bean 扫描不到。
- @Configuration标记当前类为配置类,类中
@Bean方法用于注册 Bean。SpringBoot2 推荐使用 Full 模式配置类。 @EnableAutoConfiguration(自动配置开关,核心)
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}@AutoConfigurationPackage:自动把启动类所在包纳入扫描范围。@Import(AutoConfigurationImportSelector.class):导入自动配置选择器,这就是加载所有自动配置类的入口。
二、自动配置完整源码执行流程
- 项目启动,执行
SpringApplication.run() - 解析启动类注解,识别
@EnableAutoConfiguration,导入AutoConfigurationImportSelector - 调用
selectImports()方法,读取自动配置候选类列表- SpringBoot2:读取资源文件
META-INF/spring.factories - SpringBoot3:使用新规范
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- SpringBoot2:读取资源文件
- 拿到全部候选自动配置类之后,使用条件注解进行过滤,不满足条件的配置类直接跳过,不会加载。
- 满足条件的自动配置类被当做配置类解析,读取里面
@Bean方法,将 Bean 注册进 IOC 容器。 - 后续:开发者自定义 Bean 优先级高于自动配置 Bean(依靠
@ConditionalOnMissingBean)
举例:DataSourceAutoConfiguration 当 classpath 中存在
DataSource.class,并且容器中不存在 DataSource Bean 时,自动配置类生效,自动创建数据源。 如果开发者自己在项目写了 DataSource Bean,@ConditionalOnMissingBean不满足,自动配置就不会创建数据源,用户自定义 Bean 优先。
三、核心条件注解(@Conditional 系列)
SpringBoot 自动配置大量使用@Conditional派生注解,用来判断配置类 / Bean 是否创建。
| 注解 | 作用 |
|---|---|
| @ConditionalOnClass | classpath 路径存在指定类,才生效 |
| @ConditionalOnMissingClass | classpath 不存在指定类,才生效 |
| @ConditionalOnBean | 容器中存在指定 Bean 才生效 |
| @ConditionalOnMissingBean | 容器不存在指定 Bean 才生效(最常用) |
| @ConditionalOnProperty | 配置文件 application.yml 存在指定属性,并且匹配值,才生效 |
| @ConditionalOnWebApplication | 当前是 Web 环境才生效 |
| @ConditionalOnNotWebApplication | 非 Web 环境才生效 |
设计思想:按需装配,不是一次性加载全部配置类,减少不必要 Bean 创建。
四、Starter 的官方设计规范
Spring 官方推荐 Starter 拆分为两个独立 Maven/Gradle 模块:
- xxx-autoconfigure(自动配置模块)存放自动配置代码、配置属性类、条件注解,核心逻辑代码。
- xxx-starter(启动器模块)本身没有任何 Java 代码,只做依赖聚合。pom.xml 引入 xxx-autoconfigure 以及所需第三方依赖。
好处:
- 解耦配置代码和依赖声明;
- 可以单独引用 autoconfigure 模块,自定义依赖;
- 统一版本管理,使用者只需要引入 starter,不用关心底层依赖。
区分官方 starter 命名:spring-boot-starter-xxx 自定义业务 starter 命名规范:xxx-spring-boot-starter(避免和官方命名混淆)
五、手把手开发自定义 Starter(
需求:开发一个demo-spring-boot-starter,封装一个消息打印工具 DemoService。 支持 yml 配置自定义前缀参数,当容器没有 DemoService Bean 时自动装配;用户可以自定义 Bean 覆盖默认实现。
模块 1:demo-spring-boot-autoconfigure(自动配置模块)
步骤 1:引入 pom 依赖
<dependencies> <!-- SpringBoot自动配置基础包 --> <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>步骤 2:配置属性类 DemoProperties
读取 yml 配置文件中demo前缀的配置,使用@ConfigurationProperties绑定配置
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "demo") public class DemoProperties { /** * 自定义消息 */ private String msg = "默认消息"; /** * 超时时间 */ private Integer timeout = 3000; // getter & setter public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Integer getTimeout() { return timeout; } public void setTimeout(Integer timeout) { this.timeout = timeout; } }步骤 3:业务工具类 DemoService
public class DemoService { private final DemoProperties properties; public DemoService(DemoProperties properties) { this.properties = properties; } public void printMsg() { System.out.println("自定义Starter输出:" + properties.getMsg() + ",超时时间:" + properties.getTimeout()); } }步骤 4:自动配置类 DemoAutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration // 开启配置属性绑定,注入DemoProperties @EnableConfigurationProperties(DemoProperties.class) // classpath存在DemoService类才加载该配置类 @ConditionalOnClass(DemoService.class) public class DemoAutoConfiguration { @Bean // 容器不存在DemoService Bean时,才自动创建 @ConditionalOnMissingBean public DemoService demoService(DemoProperties demoProperties) { return new DemoService(demoProperties); } }步骤 5:注册自动配置类
SpringBoot2:resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.autoconfigure.DemoAutoConfigurationSpringBoot3:resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.demo.autoconfigure.DemoAutoConfiguration模块 2:demo-spring-boot-starter(启动器模块,无 Java 代码)
pom.xml 只依赖 autoconfigure 模块
<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>demo-spring-boot-autoconfigure</artifactId> <version>1.0.0</version> </dependency> </dependencies>测试使用自定义 Starter
- 在测试项目 pom 引入 starter 依赖
<dependency> <groupId>com.example</groupId> <artifactId>demo-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>- application.yml 配置
demo: msg: Hello 自定义Starter timeout: 5000- 直接注入 DemoService 使用
@RestController public class TestController { @Autowired private DemoService demoService; @GetMapping("/test") public String test(){ demoService.printMsg(); return "success"; } }如果用户自己在项目定义 DemoService 类型 Bean,
@ConditionalOnMissingBean会让自动配置的 Bean 不创建,用户 Bean 优先。
六、自定义 Starter 的工程应用场景
- 封装团队通用组件:统一日志切面、权限校验、限流组件、通用异常处理器
- 封装第三方 SDK:统一配置、连接管理,避免每个项目重复写连接代码
- 统一中间件配置:Redis、MQ 的通用配置、工具类,全项目复用
- 统一业务通用能力:数据脱敏、ID 生成器、分布式锁工具
七、开发自定义 Starter 常见踩坑总结
- 自动配置类没有注册到 spring.factories/imports 文件:引入 starter 但是 Bean 无法自动创建,最常见问题。
- @ConditionalOnMissingBean 使用位置错误:写在配置类上,而不是写在 @Bean 方法上,达不到用户 Bean 覆盖的效果。
- 配置属性类没有加
@EnableConfigurationProperties,yml 配置无法注入。 - starter 模块写业务代码,违反官方规范;代码应当全部放在 autoconfigure 模块。
- 版本管理混乱:starter 和 autoconfigure 版本不一致,依赖冲突。
- 自动配置类不要放在启动类能扫描到的包下:防止 @ComponentScan 重复扫描,导致自动配置逻辑异常。
八、自动配置开启 / 关闭
- 全局排除某些自动配置类(启动类注解)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})适用于:不需要数据源,需要排除数据库自动配置。
九、问答汇总
Q1:SpringBoot 自动配置原理是什么?
A:核心是@EnableAutoConfiguration导入AutoConfigurationImportSelector,读取 spring.factories/imports 文件加载候选配置类,配合条件注解按需装配 Bean。
Q2:@ConditionalOnMissingBean 作用?
A:容器中不存在对应 Bean 才创建,目的是用户自定义 Bean 优先级高于框架自动配置 Bean,方便覆盖默认行为。
Q3:SpringBoot2 和 SpringBoot3 在自动配置文件上有什么区别?
A:SpringBoot2 使用spring.factories;SpringBoot3 废弃 spring.factories 自动配置方式,改用AutoConfiguration.imports文件。
Q4:Starter 和 autoconfigure 模块分别负责什么?
A:autoconfigure 存放自动配置代码、Bean 装配逻辑;starter 只做依赖聚合,方便使用者一键引入依赖。
Q5:如果我不想使用自动配置创建的 Bean,如何覆盖?
A:自己在项目中定义同类型 Bean,配合@ConditionalOnMissingBean,自动配置不会创建,优先使用自定义 Bean。
Q6:自动配置类会被 @ComponentScan 扫描吗?
A:不会,自动配置类是通过AutoConfigurationImportSelector导入,不是组件扫描,这也是为什么自动配置类放在任意包都生效。
结语
自动配置是 SpringBoot 最核心的能力,本质是 IOC 容器 + @Import + 条件注解组合实现。掌握自动配置原理,你就不再只是 SpringBoot 使用者,有能力封装团队通用组件,开发自定义 Starter,把重复的配置逻辑下沉到组件,提升项目复用性、统一技术规范。 理解本篇内容,结合前面 IOC、AOP 两篇博客,Spring 底层体系已经打通, Spring 全家桶相关问题都可以从容应答。