Spring Boot 自定义 Starter 与自动装配原理深度剖析

Spring Boot 自定义 Starter 与自动装配原理深度剖析
神经蛙用过 Spring Boot 的人都知道”引入依赖就能用”,但一旦要自己封装一个公共组件给团队复用,就必须搞懂自动装配。本文从源码层面拆开 @SpringBootApplication,然后带你手写一个能放进生产环境的 Starter。
一、自动装配到底做了什么
1.1 从 @SpringBootApplication 拆起
每个 Spring Boot 项目的启动类上都挂着这么一个注解:
1 |
|
它其实是一个”三合一”的组合注解,扒开源码能看到:
1 |
|
三个注解各司其职:
| 注解 | 作用 | 不写会怎样 |
|---|---|---|
@SpringBootConfiguration |
标记这是一个配置类,允许在类中定义 @Bean |
启动类里的 @Bean 不生效 |
@EnableAutoConfiguration |
开启自动装配,加载 classpath 下的自动配置类 | 所有 starter 全部失效 |
@ComponentScan |
扫描 @Component @Service 等 |
自己写的业务 Bean 扫不进来 |
其中 ② 才是本文的主角。
1.2 @EnableAutoConfiguration 如何工作
点进 @EnableAutoConfiguration,会看到它通过 @Import 引入了一个选择器:
1 |
|
AutoConfigurationImportSelector 实现了 DeferredImportSelector,它最核心的方法是 selectImports(),简化后逻辑如下:
1 |
|
getAutoConfigurationEntry() 内部做了四件事,这是理解自动装配的关键:
1 | protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata metadata) { |
1.3 候选配置从哪里读:Spring Boot 2 与 3 的差异
这是面试高频、也最容易踩坑的地方。两个大版本的配置文件位置和格式都不一样。
Spring Boot 2.x —— 读取所有 jar 包里的:
1 | META-INF/spring.factories |
内容格式是 properties:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
Spring Boot 3.x —— 改为读取:
1 | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
内容格式是纯文本,一行一个全限定名:
1 | com.example.demo.DemoAutoConfiguration |
Spring Boot 3 已完全移除对 spring.factories 注册自动配置的支持。如果你按 2.x 的教程写 Starter 放到 3.x 项目里,现象是依赖引了、配置写了,但 Bean 一个都没注册,而且不报任何错,排查起来非常折磨人。
1.4 完整时序串起来
1 | 应用启动 |
二、条件注解:自动装配的”开关”
条件注解决定了”什么情况下这个自动配置才生效”。Spring Boot 在 org.springframework.boot.autoconfigure.condition 包下提供了十几 个。
2.1 常用条件注解速查
| 注解 | 生效条件 | 典型用途 |
|---|---|---|
@ConditionalOnClass |
classpath 中存在指定类 | 引入了某依赖才装配 |
@ConditionalOnMissingClass |
classpath 中不存在指定类 | 缺省兜底方案 |
@ConditionalOnBean |
容器中存在指定 Bean | 依赖其他 Bean 才装配 |
@ConditionalOnMissingBean |
容器中不存在指定 Bean | 允许用户自定义覆盖默认 |
@ConditionalOnProperty |
配置项满足条件 | 通过配置开关控制 |
@ConditionalOnWebApplication |
当前是 Web 应用 | Web 环境专属配置 |
@ConditionalOnNotWebApplication |
当前非 Web 应用 | 非 Web 环境配置 |
@ConditionalOnExpression |
SpEL 表达式为 true | 复杂组合条件 |
@ConditionalOnJava |
指定 Java 版本 | 版本兼容处理 |
2.2 @ConditionalOnMissingBean 为什么最重要
看一眼 Spring Boot 自带的 RedisAutoConfiguration 源码片段:
1 |
|
@ConditionalOnMissingBean(name = "redisTemplate") 的含义是:只有当用户自己没定义 redisTemplate 时,我才用默认的。
这就实现了自动装配最重要的一条设计原则 —— 约定优于配置,但允许用户覆盖。你只要在配置类里自己写个 @Bean 叫 redisTemplate,默认的自动失效,全程不需要任何 exclude 操作。
三、动手写一个 Starter
3.1 场景与项目结构
假设我们要封装一个”短信发送”组件,团队里多个微服务都要用。目标是:其他项目引入依赖 + 配置账号,就能直接注入 SmsService 使用。
命名规范很重要,官方约定:
- 官方 starter:
spring-boot-starter-xxx - 第三方 starter:
xxx-spring-boot-starter
我们建两个模块:
1 | sms-spring-boot-starter/ # 空壳,只做依赖聚合 |
为什么要拆成两个模块?因为 autoconfigure 模块承载全部逻辑,starter 模块只是个”依赖清单”。这样使用者可以只引 starter 拿到全量能力,也可以只引 autoconfigure 自己控制版本。Spring Boot 官方所有 starter 都是这个结构。
3.2 配置属性类
1 |
|
@ConfigurationProperties(prefix = "sms") 把配置文件里的 sms.* 一次性绑定到这个对象上,比逐个 @Value 清爽得多。
3.3 核心服务类
1 |
|
3.4 自动配置类
1 |
|
三个注解各有用意:
@EnableConfigurationProperties(SmsProperties.class)—— 把SmsProperties注册进容器,否则注入不进来@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true")—— 总开关,没配置sms.enabled=true的话整个自动配置都不加载@ConditionalOnMissingBean—— 允许用户自定义SmsService覆盖默认实现
3.5 注册自动配置
这一步最容易忘,也最坑。
Spring Boot 3.x,在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 写入:
1 | com.example.sms.SmsAutoConfiguration |
Spring Boot 2.x,在 src/main/resources/META-INF/spring.factories 写入:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
如果想同时兼容两个大版本,两个文件都写是安全的 —— 3.x 只认 imports 文件,2.x 只认 spring.factories,互不干扰。
3.6 autoconfigure 模块的 pom
1 | <dependencies> |
3.7 starter 模块的 pom
starter 模块不写任何 Java 代码,只有一个 pom:
1 | <dependencies> |
3.8 使用方怎么用
第一步,引依赖:
1 | <dependency> |
第二步,加配置:
1 | sms: |
第三步,直接注入:
1 |
|
全程没有一行 new,没有 @Import,没有 XML —— 这就是自动装配的价值。
四、微服务常用 Starter 与整合要点
4.1 常用 Starter 清单
| Starter | 作用 | 备注 |
|---|---|---|
spring-boot-starter-web |
Web MVC + 内嵌 Tomcat | 最常用 |
spring-boot-starter-validation |
参数校验(Hibernate Validator) | Boot 2.3 后需单独引入 |
spring-boot-starter-data-redis |
Redis 操作 | 默认 Lettuce 客户端 |
spring-boot-starter-aop |
切面编程 | 自定义注解必备 |
spring-boot-starter-actuator |
健康检查与监控端点 | 生产必备 |
spring-cloud-starter-gateway |
API 网关 | 基于 WebFlux,不能与 web 共存 |
spring-cloud-starter-openfeign |
声明式 HTTP 调用 | 需配合注册中心 |
spring-cloud-starter-alibaba-nacos-discovery |
服务注册与发现 | 阿里系 |
4.2 配置热更新
配置中心改了配置,应用不重启就生效,靠的是 @RefreshScope:
1 |
|
原理是 @RefreshScope 把 Bean 的作用域改成了 refresh,配置变更事件触发时,Spring Cloud 会把这个 Bean 销毁并重建。
@RefreshScope 有个坑:加了它之后,该 Bean 是懒加载代理的,每次调用都会走一次代理解析。高频调用的热路径上慎用。另外它只对 @Value 和 @ConfigurationProperties 有效,对构造函数里已经算好的静态字段无效。
五、踩坑记录
5.1 自动配置类不生效
排查顺序:
- 确认
sms.enabled=true这类开关条件是否满足 - 确认 imports / spring.factories 文件路径完全正确(尤其是 Boot 3 那一长串目录名)
- 确认文件被打包进了 jar —— 用
jar tf target/xxx.jar | grep imports检查 - 开启 debug 日志看自动装配报告:
debug: true,启动日志会打印哪些配置生效、哪些被淘汰
5.2 包扫描路径不一致
自动配置类不受 @ComponentScan 路径限制,因为它走的是 @Import 机制。但如果你在自动配置类里用了 @ComponentScan 去扫别的包,就很容易扫漏。推荐做法是显式声明 Bean,不要用扫描。
5.3 循环依赖
两个自动配置类互相 @ConditionalOnBean 依赖对方时,会形成死锁。解决方式是用 @AutoConfigureAfter / @AutoConfigureBefore 显式声明装配顺序:
1 |
|
5.4 配置提示不出现
IDE 里写 sms. 没有补全,检查两件事:
- 是否引入了
spring-boot-configuration-processor target/classes/META-INF/spring-configuration-metadata.json是否生成
5.5 Spring Boot 3 迁移要点
| 变化 | Boot 2 | Boot 3 |
|---|---|---|
| JDK 基线 | 8+ | 17+ |
| Java EE → Jakarta | javax.* |
jakarta.* |
| 自动配置注册 | spring.factories |
AutoConfiguration.imports |
| 配置属性绑定 | 宽松绑定 | 更严格,部分写法废弃 |
其中 javax.* → jakarta.* 是最痛的一条,所有 Servlet 相关的 import 都要改。














