SpringBoot(一):自动配置、Starter 与核心注解
SpringBoot(一):自动配置、Starter 与核心注解
导语:Spring Boot 的灵魂只有一个词——自动配置。本篇把
@SpringBootApplication拆到注解级、把自动配置的加载链路(spring.factories→AutoConfiguration.imports)与条件装配(@ConditionalOnClass的 ASM 实现、@ConditionalOnMissingBean的语义)讲透,再落到 Starter 的本质与自定义套路,并给出自动配置的调试与排除手段,共 13 题。
一、核心概念与注解
1. 什么是 Spring Boot?它的设计目标与核心特性是什么?
答: Spring Boot 是用于快速构建独立、可运行、生产级 Spring 应用的框架,核心理念是「约定优于配置(Convention over Configuration)」。
设计目标:
- 摆脱繁琐 XML 与依赖版本管理,开箱即用;
- 内嵌 Web 容器(Tomcat/Jetty/Undertow),
java -jar即可运行; - 用 Starter 一站式集成技术栈;
- 提供生产级能力(Actuator、外部化配置、健康检查)。
四大核心特性:
| 特性 | 解决什么 |
|---|---|
| 自动配置(Auto-configuration) | 根据类路径自动装配 Bean,省掉大量 @Bean |
| 起步依赖(Starter) | 依赖聚合 + 版本统一,解决 Jar 冲突 |
| 内嵌容器 | 免外部容器部署,天然适配 Docker/K8s |
| Actuator | 开箱可观测:健康、指标、环境、映射 |
一句话:Spring Boot 没有引入新功能,它做的是"自动配置 + 依赖管理 + 内嵌容器"三件事,把"搭一个能跑的 Spring 应用"从几天压缩到几分钟。
2. @SpringBootApplication 由哪些注解组成?
答: 它是一个组合注解,等价于三个注解:
| 注解 | 作用 |
|---|---|
@SpringBootConfiguration | 本质是 @Configuration,标识主类为配置类(可定义 @Bean) |
@EnableAutoConfiguration | 自动配置总开关,内部通过 @Import(AutoConfigurationImportSelector.class) 实现 |
@ComponentScan | 扫描主类所在包及其子包下的 @Component/@Service/@Controller 等 |
@SpringBootApplication(
scanBasePackages = "com.demo", // 覆盖默认扫描范围
exclude = {DataSourceAutoConfiguration.class}, // 排除指定自动配置
excludeName = "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
)
public class Application { public static void main(String[] a) { SpringApplication.run(Application.class, a); } }最高频的坑:主启动类必须放在所有业务包的最外层(如
com.demo),否则@ComponentScan扫不到子包、@Controller全部 404。这是"约定优于配置"最直接的一条约定。
3. 请详细讲讲 Spring Boot 自动配置的原理。
答: 本质一句话——启动时按类路径的实际情况,有选择地把一组 @Configuration 里的 Bean 注册进容器。
加载链路:
@SpringBootApplication
→ @EnableAutoConfiguration
→ @Import(AutoConfigurationImportSelector.class)
→ AutoConfigurationImportSelector#getAutoConfigurationEntry()
· getCandidateConfigurations():读取所有 jar 的自动配置类清单
· removeDuplicates():去重
· getExclusions():剔除 exclude / excludeName
· filter(configurations, autoConfigurationMetadata):过滤掉不满足 @Conditional 的
→ 每个自动配置类被当作普通 @Configuration 解析,其 @Bean 方法按 @Conditional 决定是否注册两种清单读取方式(版本演进,面试必答):
| 版本 | 机制 |
|---|---|
| Boot 2.7 之前 | SpringFactoriesLoader 读取 META-INF/spring.factories 中 EnableAutoConfiguration 键下的全限定类名 |
| Boot 2.7 起 | 新增 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每行一个类名;2.7 是对 spring.factories 的过渡期兼容 |
| Boot 3.0 起 | 彻底移除 spring.factories 中的自动配置加载,只认 .imports 文件 |
一个自动配置类的典型长相:
@AutoConfiguration // Boot 2.7+ 的专用注解(本质是 @Configuration(proxyBeanMethods=false))
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class }) // 类路径有才生效
@ConditionalOnMissingBean(DataSource.class) // 用户自己定义过就不重复注册
@EnableConfigurationProperties(DataSourceProperties.class) // 绑定配置属性
public class DataSourceAutoConfiguration { ... }加分点:把自动配置理解为"一套带有条件判断的、可被用户覆盖的默认配置"——用户定义优先(
@ConditionalOnMissingBean)是它"不抢戏"的关键设计。
4. 自动配置类之间的加载顺序如何控制?
答:
| 手段 | 说明 |
|---|---|
@AutoConfigureBefore / @AutoConfigureAfter | 声明相对顺序(如 DataSource 相关的要在 JdbcTemplate 之前) |
@AutoConfigureOrder | 绝对顺序(Ordered 语义) |
DeferredImportSelector | AutoConfigurationImportSelector 实现的是它——在所有用户 @Configuration 解析完之后才执行,确保"用户配置优先、自动配置兜底" |
| 排序入口 | AutoConfigurationSorter 依据 @AutoConfigureOrder/@AutoConfigureBefore/After + 字母序做稳定排序,结果可被 AutoConfigurationSorter 元数据缓存 |
为什么"用户配置优先"能成立? 因为自动配置是延迟导入(Deferred)的:用户自己的 @Bean/@Configuration 先注册,自动配置类的 @ConditionalOnMissingBean 再判断时,就已经能看到用户定义并主动让位。
还有一个"隐形顺序"规则:同一自动配置类内部的
@Bean方法由@Bean(before/after)或方法依赖(参数注入关系)决定执行顺序——ConfigurationClassPostProcessor会做拓扑排序。
5. @Conditional 系列在自动配置中是怎么起作用的?
答: 条件注解决定"某个自动配置类/@Bean 要不要生效",语义上是"条件不满足连 BeanDefinition 都不注册"。
| 注解 | 判断依据 | 典型用途 |
|---|---|---|
@ConditionalOnClass | 类路径是否存在指定类 | 没引入 Redis 依赖就跳过 Redis 自动配置 |
@ConditionalOnMissingClass | 类路径不存在 | 反向判断 |
@ConditionalOnMissingBean | 容器中不存在该 Bean(按类型/名称) | 用户自定义优先,框架兜底 |
@ConditionalOnBean | 容器中已存在 | 依赖某个 Bean 才能装配 |
@ConditionalOnProperty | 配置项是否满足(havingValue/matchIfMissing) | spring.datasource.enabled=true 才启用 |
@ConditionalOnResource / @ConditionalOnWebApplication / @ConditionalOnSingleCandidate | 资源、应用类型、唯一候选 | Web/响应式分支、单数据源判断 |
两个"讲到底层"的关键点:
@ConditionalOnClass不会真正加载类:它通过OnClassCondition使用 ASM 读取注解元数据(MetadataReader) 判断类是否存在——否则"类不存在"的自动配置类在解析注解时就已ClassNotFoundException了。这是自动配置"能在缺依赖时安全跳过"的技术基础。@ConditionalOnMissingBean的顺序敏感:它只能看到"当前已注册的 BeanDefinition",所以必须依赖第 4 题的加载顺序保证——若某自动配置被排在用户配置之前,OnMissingBean就可能误判而重复注册。
调试利器:启动加
--debug(或设置debug=true),Boot 会打印ConditionEvaluationReport,逐条列出"哪些自动配置生效(positive matches)、哪些没生效(negative matches)及原因"——排查"自动配置没生效"的第一手段(另见actuator/conditions端点)。
6. 什么是 Starter?它是如何工作的?
答: Starter 是一组依赖描述符——本质是一个不含业务代码的"空 jar",只通过 pom.xml 聚合相关依赖,并配套(或依赖)一个自动配置模块。
工作机制:
引入 spring-boot-starter-web
→ 间接引入 spring-webmvc、tomcat-embed、jackson、validation、logging
→ 这些类进入类路径
→ WebMvcAutoConfiguration / ServletWebServerFactoryAutoConfiguration 的 @ConditionalOnClass 满足
→ 自动注册 DispatcherServlet、内嵌 Tomcat、Jackson 转换器等 Bean命名规范:
- 官方:
spring-boot-starter-*(如-web、-data-jpa、-security、-actuator) - 第三方:
*-spring-boot-starter(如mybatis-spring-boot-starter、dubbo-spring-boot-starter)
一句话:Starter 负责"把依赖带进类路径",自动配置负责"把 Bean 注册进容器"——两者是"依赖 + 条件装配"的组合拳。
7. 如何自定义一个 Spring Boot Starter?
答: 标准四步(面试常让"手写一个"):
// ① 属性绑定类
@ConfigurationProperties(prefix = "demo")
public class DemoProperties {
private String prefix = "default"; // getter/setter
}
// ② 自动配置类
@AutoConfiguration // 或 @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DemoService.class)
@EnableConfigurationProperties(DemoProperties.class)
public class DemoAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 允许用户覆盖
public DemoService demoService(DemoProperties props) { return new DemoService(props.getPrefix()); }
}③ 注册自动配置:resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(每行一个自动配置类全限定名;Boot 2.7 之前写 META-INF/spring.factories)
④ 提供 starter 模块(可选但推荐):一个只含 pom 依赖的 `demo-spring-boot-starter` 模块
→ 使用者只引 starter + 配置 demo.prefix=xxx,即可直接用 DemoService必须体现的三个设计意识:
@ConditionalOnMissingBean—— 允许用户覆盖,不与业务抢注;- 属性外部化 + 默认值 —— 开箱即用,不配置也能跑;
- 条件化 + 独立模块 —— 缺依赖不报错、可被排除。
8. 如何排除或禁用某个自动配置?
答:
| 方式 | 写法 |
|---|---|
| 注解排除 | @SpringBootApplication(exclude = XxxAutoConfiguration.class) / excludeName = "..." |
| 配置排除 | spring.autoconfigure.exclude=com.xxx.XxxAutoConfiguration |
| 覆盖 Bean | 自己定义同类型 Bean,利用 @ConditionalOnMissingBean 让自动配置自动让位(最优雅) |
| 关闭开关 | 部分自动配置有开关属性(如 spring.datasource.enabled=false、spring.redis.enabled) |
实践建议:优先用"覆盖 Bean"而不是"排除自动配置"——排除会让整个模块能力消失,而覆盖只替换其中一部分;只有在"自动配置引入的副作用必须彻底关闭"(如多数据源场景要排除默认
DataSourceAutoConfiguration)时才用 exclude。
二、Web 容器与版本演进
9. 什么是内嵌服务器?为什么使用它?如何替换?
答: 内嵌服务器指把 Servlet 容器(Tomcat/Jetty/Undertow)作为应用依赖打进 jar,应用以普通 Java 程序启动,无需外部 Web 容器。
优势:
- 一键部署:
java -jar app.jar,天然契合 Docker/K8s(一个容器一个进程); - 版本内聚:容器版本随依赖管理,避免"应用与容器不兼容";
- 微服务契合:每个服务自带容器,可独立扩缩容。
替换容器(Boot 中):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId> <!-- 或 spring-boot-starter-jetty -->
</dependency>| 容器 | 特点 | 适用 |
|---|---|---|
| Tomcat(默认) | 生态最成熟、资料最多、高并发下性能与内存表现均衡 | 通用首选 |
| Jetty | 轻量、启动快、可嵌入性好 | 大量短连接、长连接(WebSocket) |
| Undertow | 基于 XNIO,内存占用低、吞吐高 | 高并发、资源敏感(注意其生态与资料较少) |
注意:Spring Boot 3 只支持 Servlet 6.0+(Jakarta EE 9+) 的容器版本;切容器后要复核
server.*中与容器实现强相关的配置项(线程数、max-http-form-post-size等)。
10. Spring Boot 2.7 / 3.x 相比早期有哪些关键变化?
答: 版本演进是"是否跟得上框架"的加分项:
| 版本 | 关键变化 |
|---|---|
| 2.4 | 配置文件加载顺序重构(引入 spring.config.import、config data API) |
| 2.6 | 默认禁止循环依赖(allow-circular-references=false);默认禁用路径匹配后缀(use-suffix-pattern);spring.mvc.pathmatch.matching-strategy 默认 path-pattern-parser |
| 2.7 | 自动配置迁移到 .imports 文件(spring.factories 过渡兼容);@AutoConfiguration 注解;@ConfigurationProperties 支持构造器绑定与 @ConstructorBinding 显式化 |
| 3.0 | 基线 Java 17、Jakarta EE 9+(javax.* → jakarta.*);移除 spring.factories 自动配置加载;引入 AOT + GraalVM Native Image 支持;ProblemDetail(RFC 7807) |
| 3.1+ | 新的 RestClient、HTTP Interface(@HttpExchange)、spring-boot-docker-compose、可观测性(Micrometer Observation)增强 |
迁移(2.x → 3.0)最容易踩的坑:
javax.*→jakarta.*:Servlet、Validation、Persistence、annotation 全部换包名,依赖必须同步升级(这是最大的工作量);- Java 17 基线;
spring.factories自动配置失效 → 换成.imports;- 第三方库是否已支持 Jakarta(老版本驱动/中间件客户端可能没有);
- 配置属性改名(如
spring.redis.*→spring.data.redis.*)。
11. Spring Boot 提供了哪些常用的 Starter?
答: 高频清单:
| Starter | 用途 |
|---|---|
spring-boot-starter-web | Spring MVC + 内嵌 Tomcat(RESTful) |
spring-boot-starter-webflux | 响应式 Web(Netty) |
spring-boot-starter-validation | 参数校验(Hibernate Validator) |
spring-boot-starter-data-jpa / -jdbc | 数据访问(JPA / JdbcTemplate) |
spring-boot-starter-data-redis | Redis(Lettuce) |
spring-boot-starter-security | 认证授权 |
spring-boot-starter-actuator | 生产监控端点 |
spring-boot-starter-test | JUnit 5 + Mockito + AssertJ + MockMvc |
spring-boot-starter-aop | AOP(AspectJ 支持) |
spring-boot-starter-cache | 缓存抽象 |
mybatis-spring-boot-starter | 第三方:MyBatis 集成 |
技巧:不确定某个能力该引什么依赖时,看官方 "Starters" 文档索引;引入了
spring-boot-starter-web就不要再单独引spring-web/tomcat(版本会被 BOM 管理,重复引入易冲突)。
12. Spring Boot 的自动配置为什么要求"不能被 @ComponentScan 扫描到"?
答: 官方明确约定:自动配置类不应出现在你的 @ComponentScan 扫描路径下。
- 原因:自动配置类如果被组件扫描直接注册为普通
@Configuration,就会绕过AutoConfigurationImportSelector的排序与条件过滤(@AutoConfigureBefore/After失效),导致 Bean 装配顺序错乱、@ConditionalOnMissingBean判断失误。 - 标准做法:自动配置类放在独立的包(如
com.demo.autoconfigure)并在.imports文件登记;主启动类所在包不要覆盖到它。 - 同时保证:自动配置类必须能被
ApplicationContext通过@Import解析(而不是靠扫描),Boot 才能统一控制顺序与条件。
这也是
@AutoConfiguration注解语义的一部分——它声明"我是被自动配置机制导入的",而不是被扫描的。
13. 如何排查"自动配置没生效"?
答: 按从快到慢的顺序:
--debug启动:打印ConditionEvaluationReport,直接看到 Negative matches(含原因);actuator/conditions端点:在运行时查看条件评估结果(/actuator/conditions);actuator/beans端点:确认 Bean 到底有没有注册;actuator/env:确认配置项是否真的被加载(以及来源优先级);--trace:更详细的加载日志;- 常见根因:① 依赖没引入(
@ConditionalOnClass不满足);② 自己定义了同类型 Bean(@ConditionalOnMissingBean让位);③ 被spring.autoconfigure.exclude排除;④ 启动类包路径不对导致扫描/导入异常;⑤ 配置属性名写错(@ConditionalOnProperty不满足)。
一句话:自动配置问题几乎都能用
--debug+conditions端点定位——它会明确告诉你"哪个条件不满足",而不是靠猜。
本章小结:自动配置的完整链条 =
@EnableAutoConfiguration→AutoConfigurationImportSelector(DeferredImportSelector)→ 读取.imports清单 → 条件过滤 → 注册 BeanDefinition。理解"用户优先、条件兜底、顺序可控"这三个关键词,就理解了 Spring Boot 的设计哲学。
