CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
Starter 与自动配置
这一课你将能做到
用依赖树命令看清一个 starter 背后到底引入了哪些库
打开条件评估报告,读懂哪些自动配置生效了、哪些被跳过了、为什么
亲手写一个带条件装配的自动配置类,并用 exclude 关掉官方自动配置
为什么要学
上一课你加了一个 spring-boot-starter-web,应用就具备了完整的 Web 能力。但面试官问「自动配置的原理是什么」,很多人答不上来;实际工作中「为什么我的 Redis 没连上」「为什么这个 Bean 没配进去」,十有八九也是自动配置没按你预期生效。
人话:Starter 是「依赖打包快递」,自动配置是「开箱即用的说明书执行器」 。快递里有什么、说明书按什么规则执行,这一课全部拆开看。看懂之后,你会发现 Boot 的「魔法」其实全是「条件判断」。
先建立图像
你引入 starter-web
↓(Maven 依赖传递)
拉进 spring-web、jackson、内嵌 tomcat 等一堆 jar
↓(类路径上出现了这些 jar 的类)
自动配置阶段:Boot 逐条检查配置清单
↓
「类路径有 DispatcherServlet 相关类?→ 帮你注册 MVC 的 Bean」
「类路径没有 Redis 相关类?→ Redis 自动配置跳过,不装」
关键句:自动配置 = 拿着「条件注解」对着类路径逐条打勾 。类路径上有什么,就装配什么;你没有的东西,它一个字节都不多配。
名词 人话 Starter 一个空壳 jar,里面没有代码,只有 pom 依赖清单 自动配置类 一堆 @Bean 方法的配置类,靠条件注解决定装不装 条件注解 @ConditionalOnClass(有这个类才装)、@ConditionalOnMissingBean(你没自己配我才装)等配置清单 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,列出所有候选自动配置类
跟着做
步骤 1:用依赖树看清 starter 里有什么
在上一课的 taskflow 项目根目录执行:
mvnw dependency:tree -Dincludes=org.springframework.boot
输出节选(层级表示依赖传递):
com.example:taskflow:jar:0.0.1-SNAPSHOT
+- org.springframework.boot:spring-boot-starter-web:jar:3.3.5
| +- org.springframework.boot:spring-boot-starter:jar:3.3.5
| | +- org.springframework.boot:spring-boot-starter-logging:jar:3.3.5
| | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.3.5
| +- org.springframework.boot:spring-boot-starter-json:jar:3.3.5
| | +- com.fasterxml.jackson.core:jackson-databind:jar:2.17.2
| +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.3.5
看到了吗:一个 starter-web 传递引入了 autoconfigure(自动配置引擎)、starter-json(Jackson)、starter-tomcat(内嵌服务器)、starter-logging(Logback 日志)。
Starter 自己没有一行 Java 代码,它只是 pom 清单。
步骤 2:常用 Starter 一览 Starter 给你什么 什么时候用 spring-boot-starter-web MVC + JSON + 内嵌 Tomcat 写 REST API spring-boot-starter-validation Hibernate Validator 参数校验 接口入参校验(本轨第 8 课) spring-boot-starter-jdbc DataSource + JdbcTemplate 轻量数据库访问(第 9 课) spring-boot-starter-data-jpa Hibernate + Spring Data JPA ORM 开发(第 10 课) spring-boot-starter-aop AspectJ 注解 + 动态代理 日志、事务等横切逻辑(第 6 课) spring-boot-starter-security 安全过滤器链 认证授权(第 12 课) spring-boot-starter-actuator 健康检查与监控端点 运维监控(第 15 课) spring-boot-starter-test JUnit 5 + Mockito + MockMvc 测试(第 14 课)
命名规律:官方 starter 都叫 spring-boot-starter-xxx;第三方的一般叫 xxx-spring-boot-starter(如 mybatis-spring-boot-starter)。
步骤 3:打开条件评估报告,亲眼看看自动配置 在 application.yml 加一行(或启动时加参数 --debug):
重启应用,控制台会出现一大段 CONDITIONS EVALUATION REPORT 。节选:
Positive matches:
-----------------
DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class
'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)
Negative matches:
-----------------
RedisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class
'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)
逐行翻译:MVC 自动配置「命中」了,因为类路径上找到了 DispatcherServlet 类;Redis 自动配置「没命中」,因为你没引 Redis 的依赖。这份报告就是排查「某个功能没自动生效」的第一现场 ——本站后端排查数据源、缓存类问题时也这么看。
看完把 debug: true 删掉(它会刷屏且拖慢启动)。
步骤 4:亲手写一个「条件装配」自动配置类 新建 greeting/GreetingProperties.java:
package com.example.taskflow.greeting;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "greeting")
public record GreetingProperties(String prefix, String suffix) {
public GreetingProperties {
if (prefix == null) prefix = "Hello";
if (suffix == null) suffix = "!";
}
}
新建 greeting/GreetingService.java:
package com.example.taskflow.greeting;
public class GreetingService {
private final GreetingProperties properties;
public GreetingService(GreetingProperties properties) {
this.properties = properties;
}
public String greet(String name) {
return properties.prefix() + "," + name + properties.suffix();
}
}
新建 greeting/GreetingAutoConfiguration.java:
package com.example.taskflow.greeting;
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(GreetingProperties.class)
@ConditionalOnProperty(prefix = "greeting", name = "enabled", havingValue = "true",
matchIfMissing = false)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户自己配了 GreetingService,就用用户的;没配才用我这个默认的
public GreetingService greetingService(GreetingProperties properties) {
return new GreetingService(properties);
}
}
关键一步——注册到自动配置清单。新建文件 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,内容一行:
com.example.taskflow.greeting.GreetingAutoConfiguration
package com.example.taskflow.controller;
import com.example.taskflow.greeting.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/api/greet")
public String greet() {
return greetingService.greet("CodeForge");
}
}
greeting:
enabled: true
prefix: "你好"
suffix: ",欢迎学习 Spring Boot"
重启访问 http://localhost:8080/api/greet,返回:你好,CodeForge,欢迎学习 Spring Boot。再把 enabled 改成 false 重启——应用直接启动失败,报 GreetingService 找不到 Bean。这就是条件装配的力量:一行配置控制一整套 Bean 的生死。
步骤 5:exclude——关掉你不想要的自动配置 最常见的例子:项目引了数据库依赖但临时想先不配数据源,启动会报「Failed to configure a DataSource」。关掉它的自动配置:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class TaskflowApplication {
public static void main(String[] args) {
SpringApplication.run(TaskflowApplication.class, args);
}
}
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
讲清楚要点
Starter 解决「依赖地狱」 :一个坐标拉齐一组库,版本由 Boot parent 仲裁。你永远不需要手动对齐 jackson、tomcat、logback 的版本。
自动配置的执行时机在用户配置之后 :所以 @ConditionalOnMissingBean 语义成立——你手写的 Bean 优先级更高,Boot 的默认配置只兜底。这是「约定可覆盖」的实现基础。
条件注解是一族,不是一种 :@ConditionalOnClass(类存在)、@ConditionalOnMissingBean(Bean 不存在)、@ConditionalOnProperty(配置项满足)、@ConditionalOnWebApplication(是 Web 应用)。读懂它们,自动配置源码就不神秘了。
Boot 3 的清单文件变了 :2.x 用 META-INF/spring.factories,3.x 改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。看老教程时注意区分。
排查口诀 :「功能没生效」先看三样——依赖在不在(依赖树)、条件满足没(评估报告)、是不是被你自己的 Bean 顶掉了(搜索同名 Bean)。
你可能会懵的地方
自动配置类这么多,启动不会很慢吗?
有一百多个候选,但绝大多数在条件检查阶段就被快速跳过(只是判断类存不存在,开销极小),真正创建 Bean 的只有命中的几十个。用 --debug 报告能看到正/负匹配的完整清单。
为什么我引入了依赖,对应的自动配置还是没生效?
三个检查点:①依赖真的进类路径了吗(dependency:tree 确认);②条件注解要求的类/配置满足了吗(--debug 报告 Negative matches);③是不是你的 exclude 或别人的 Bean 挡掉了。
@AutoConfiguration 和 @Configuration 有什么区别?
@AutoConfiguration 是 @Configuration(proxyBeanMethods = false) 的特化,并表明该类要注册进自动配置清单,在所有用户配置处理完之后才执行。
同一个类既在 @ComponentScan 扫描范围内又写在清单里会怎样?
组件扫描先执行,自动配置阶段的 @ConditionalOnMissingBean 会发现 Bean 已存在而跳过,最终只有一个实例,不会冲突——但不要这么写,语义混乱。
面试让我说「自动配置原理」,按什么顺序讲?
入口 @EnableAutoConfiguration → 通过 Import 机制读取 AutoConfiguration.imports 清单拿到全部候选类 → 逐个用 @Conditional* 过滤(对照类路径、配置、已有 Bean)→ 命中的配置类中的 @Bean 注入容器 → 用户自定义 Bean 因先于自动配置而优先。一套话术覆盖八成考点。
错法对照 错法 问题 正解 手动引入 spring-webmvc、tomcat 等十几个依赖拼 Web 环境 版本不齐,运行期诡异冲突 引 starter-web,一个坐标搞定 清单文件放错路径(如 META-INF 下随便建) 自动配置类根本不被读取 必须是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 在自动配置 Bean 上不加 @ConditionalOnMissingBean 用户自定义 Bean 时报重复定义冲突 默认兜底 Bean 一律加条件注解 用 exclude 关闭自动配置但还留着依赖 功能静默失效,排查半天 关配置就连依赖一起移除,保持一致 把业务 @Service 类也注册进自动配置清单 扫描和自动配置双通道,语义混乱 自动配置清单只放「基础设施」配置类
卡点急救
Failed to configure a DataSource :引了 JDBC/JPA 依赖但没配数据源。要么补 spring.datasource 配置,要么 exclude = DataSourceAutoConfiguration.class(开发期临时方案)。
不知道某个 Bean 是谁配的 :IDEA 里按两次 Shift 搜类名 → 打开对应的 *AutoConfiguration 源码,看它的 @Conditional* 注解即可还原条件。
GreetingService 启动报 NoSuchBeanDefinitionException :按步骤 4 检查三件事——greeting.enabled=true 写了没、清单文件路径和内容对不对、类上 @ConditionalOnProperty 的 matchIfMissing 是 false(没写配置就不装)。
面试常问
Spring Boot 自动配置的实现原理?
见「你可能会懵的地方」最后一条的话术,核心链路:@EnableAutoConfiguration → AutoConfigurationImportSelector → 读取 imports 清单 → @Conditional 条件过滤 → 按 Bean 覆盖规则注入。
如何自定义一个 Starter?
建 autoconfigure 模块(写配置属性类 + 自动配置类 + 条件注解)+ 建 starter 模块(空 pom 聚合依赖);在 autoconfigure 模块 resources 下注册 imports 清单;发布后业务方引 starter + 写配置即可用。
@ConditionalOnMissingBean 为什么能保证用户配置优先?
自动配置类的处理时机晚于用户 @ComponentScan/@Configuration;条件检查时用户 Bean 已注册,MissingBean 条件不满足,Boot 默认 Bean 不创建。
Starter 和普通依赖有什么区别?
普通依赖只提供 jar;starter = 依赖聚合(坐标组合)+ 自动配置激活(连带 autoconfigure 模块的条件装配),是「能力包」而不是「库」。
自测三问
starter-web 引入后,传递带进了哪些核心库?各自负责什么?
@ConditionalOnClass 和 @ConditionalOnMissingBean 分别在什么时机、检查什么?
Boot 3.x 的自动配置清单文件完整路径是什么?和 2.x 有什么变化?
小结一句话 Starter 用一个坐标打包一组依赖,自动配置拿着条件注解对着类路径按需装配——「魔法」的真相是「依赖驱动 + 条件判断」。
延伸