Spring Boot 商业场景训练营
Spring Boot 不能只会写 Controller,也不能只背“自动配置、Starter、约定优于配置”。商业项目里真正要会的是:把一个可复用能力做成 Starter,知道配置如何绑定,知道默认 Bean 为什么生效,知道用户 Bean 为什么能覆盖默认实现,知道自动配置没生效怎么查,知道健康检查、指标、失败诊断、启动扩展点分别放在哪里。
训练目标:用“医疗采集客户端 Starter”把 Spring Boot 自动配置、Starter、配置体系、扩展点、Actuator 和排查流程跑通。
训练总流程
flowchart TD
A["识别公共能力"] --> B["拆分 SDK / autoconfigure / starter"]
B --> C["定义配置属性"]
C --> D["编写自动配置类"]
D --> E["配置条件注解"]
E --> F["注册自动配置清单"]
F --> G["接入业务项目验证"]
G --> H["补健康检查和指标"]
H --> I["用 conditions 排查"]训练一:为什么要做 Starter
场景
医疗数据采集平台里,多个服务都要调用医院侧采集网关。每个服务都需要网关地址、超时时间、token、重试次数、健康检查和指标。如果每个项目都复制一份配置和客户端代码,后期改协议会非常痛苦。
正确拆分
| 模块 | 职责 | 是否包含 Spring Boot |
|---|---|---|
collect-client-sdk | 纯 Java 客户端、请求对象、响应对象 | 不依赖 Boot |
collect-client-spring-boot-autoconfigure | 自动配置、属性类、条件注解、健康检查 | 依赖 Boot |
collect-client-spring-boot-starter | 聚合依赖入口 | 通常只放依赖 |
flowchart TD
A["业务项目引入 starter"] --> B["starter 带入 autoconfigure"]
B --> C["autoconfigure 带入 SDK"]
C --> D["Boot 读取自动配置类"]
D --> E["条件满足则创建 CollectClient"]不这样会怎样
| 错误做法 | 后果 |
|---|---|
| 每个项目复制客户端代码 | 协议升级时到处改 |
| Starter 里写业务规则 | 公共组件和业务耦合 |
| 不给用户覆盖默认 Bean | 业务项目无法自定义连接池、鉴权、重试 |
| 配置写死 | 多环境部署困难,密钥风险高 |
训练二:配置属性类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
// 本训练以 Boot 2.7 / JDK 8 为可运行基线。
// Boot 3 / Spring 6 才改为 jakarta.validation.constraints.NotBlank。
import javax.validation.constraints.NotBlank;
import java.time.Duration;
@Validated
@ConfigurationProperties(prefix = "collect.client")
public class CollectClientProperties {
private boolean enabled = true;
@NotBlank
private String baseUrl;
private Duration connectTimeout = Duration.ofSeconds(3);
private Duration readTimeout = Duration.ofSeconds(10);
private int maxRetry = 2;
private String token;
// getter/setter 省略
}业务项目配置:
collect:
client:
enabled: true
base-url: https://gateway.hospital.example
connect-timeout: 3s
read-timeout: 10s
max-retry: 2
token: ${COLLECT_GATEWAY_TOKEN}原理
Boot 启动时会把配置源加载到 Environment,再由 Binder 根据 collect.client 前缀绑定到 CollectClientProperties。base-url 能绑定到 baseUrl,是因为 Spring Boot 支持宽松绑定。
flowchart TD
A["application.yml / 环境变量"] --> B["Environment"]
B --> C["Binder"]
C --> D["@ConfigurationProperties"]
D --> E["CollectClientProperties Bean"]训练三:自动配置类
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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
@ConditionalOnClass(CollectClient.class)
@EnableConfigurationProperties(CollectClientProperties.class)
@ConditionalOnProperty(prefix = "collect.client", name = "enabled", havingValue = "true", matchIfMissing = true)
public class CollectClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient(CollectClientProperties properties) {
return new CollectClient(
properties.getBaseUrl(),
properties.getConnectTimeout(),
properties.getReadTimeout(),
properties.getMaxRetry(),
properties.getToken()
);
}
}条件注解为什么重要
| 条件 | 作用 | 不加会怎样 |
|---|---|---|
@ConditionalOnClass | SDK 存在才装配 | 缺类时报错 |
@ConditionalOnProperty | 提供开关 | 业务无法关闭 |
@ConditionalOnMissingBean | 用户自定义优先 | 默认 Bean 覆盖业务 Bean |
@EnableConfigurationProperties | 注册配置属性 | 配置无法绑定成 Bean |
训练四:Boot 2 和 Boot 3 自动配置清单
Boot 2 常见写法
META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.collect.CollectClientAutoConfigurationBoot 3 推荐写法
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.collect.CollectClientAutoConfiguration面试回答
Boot 2.6及更早常见通过 spring.factories 的 EnableAutoConfiguration key 声明自动配置类;Boot 2.7 已支持并推荐 AutoConfiguration.imports 且仍兼容旧方式;Boot 3 使用 imports,每行一个自动配置类。这些机制本质上都是给 AutoConfigurationImportSelector 提供候选自动配置类,再由条件注解筛选,最终注册 BeanDefinition。训练五:用户自定义 Bean 覆盖默认实现
业务项目想自定义客户端:
@Configuration
public class CollectClientCustomConfig {
@Bean
public CollectClient collectClient() {
return new CollectClient("https://mock.example", Duration.ofSeconds(1), Duration.ofSeconds(2), 0, "mock");
}
}因为自动配置中有 @ConditionalOnMissingBean,容器里已经有用户 Bean 时,默认 CollectClient 不会创建。
flowchart TD
A["解析自动配置"] --> B{"容器里已有 CollectClient"}
B -- "有" --> C["跳过默认 Bean"]
B -- "没有" --> D["创建默认 CollectClient"]训练六:健康检查和指标
HealthIndicator
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
public class CollectClientHealthIndicator implements HealthIndicator {
private final CollectClient collectClient;
public CollectClientHealthIndicator(CollectClient collectClient) {
this.collectClient = collectClient;
}
@Override
public Health health() {
boolean ok = collectClient.ping();
if (ok) {
return Health.up().withDetail("collectGateway", "reachable").build();
}
return Health.down().withDetail("collectGateway", "unreachable").build();
}
}自动配置:
@Bean
@ConditionalOnMissingBean
public CollectClientHealthIndicator collectClientHealthIndicator(CollectClient client) {
return new CollectClientHealthIndicator(client);
}指标
可以用 Micrometer 统计请求次数、失败次数、耗时:
Timer.Sample sample = Timer.start(meterRegistry);
try {
collectClient.collect(request);
meterRegistry.counter("collect.client.request", "result", "success").increment();
} catch (Exception e) {
meterRegistry.counter("collect.client.request", "result", "fail").increment();
throw e;
} finally {
sample.stop(meterRegistry.timer("collect.client.latency"));
}训练七:FailureAnalyzer 让错误更好懂
配置缺失时,普通异常可能很长。可以用 FailureAnalyzer 输出更清晰的失败原因和处理建议。
public class CollectClientFailureAnalyzer
extends AbstractFailureAnalyzer<CollectClientConfigException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
CollectClientConfigException cause) {
return new FailureAnalysis(
"采集客户端配置错误:" + cause.getMessage(),
"请检查 collect.client.base-url、token、enabled 配置,或关闭 collect.client.enabled。",
cause
);
}
}注册:
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.collect.CollectClientFailureAnalyzer训练八:自动配置没生效怎么查
打开条件报告
debug: true或使用 Actuator:
curl http://localhost:8080/actuator/conditions
curl http://localhost:8080/actuator/configprops
curl http://localhost:8080/actuator/env排查流程
flowchart TD
A["Starter 没生效"] --> B["确认依赖是否引入"]
B --> C["确认自动配置清单是否存在"]
C --> D["看 conditions 报告"]
D --> E{"条件是否匹配"}
E -- "否" --> F["查 classpath / 配置项 / exclude"]
E -- "是" --> G{"Bean 是否创建失败"}
G -- "是" --> H["看异常和 FailureAnalyzer"]
G -- "否" --> I["查是否已有用户 Bean 覆盖"]常见原因
| 现象 | 原因 |
|---|---|
| 自动配置类不在报告里 | 清单文件路径错、依赖没引入 |
@ConditionalOnClass 不匹配 | SDK 类不在 classpath |
@ConditionalOnProperty 不匹配 | 配置项写错或开关关闭 |
| 默认 Bean 没创建 | 用户已经自定义 Bean |
| 配置属性为空 | prefix 写错、配置源被覆盖、Profile 不对 |
训练九:扩展点怎么选
| 需求 | 推荐扩展点 |
|---|---|
| 配置解密要在绑定前生效 | EnvironmentPostProcessor |
| 动态注册一批 BeanDefinition | BeanDefinitionRegistryPostProcessor |
| 修改已有 BeanDefinition | BeanFactoryPostProcessor |
| 给 Bean 初始化前后增强 | BeanPostProcessor |
| 启动完成后轻量预热 | ApplicationRunner |
| 请求进入 MVC 前做处理 | Filter / HandlerInterceptor |
| Controller 自动注入当前用户 | HandlerMethodArgumentResolver |
| 暴露健康状态 | HealthIndicator |
| 暴露业务指标 | MeterBinder / Micrometer |
| 启动失败给友好提示 | FailureAnalyzer |
关键原则:扩展点要放在正确生命周期阶段。配置解密不能放 Runner,因为 Runner 执行时配置绑定和自动配置早就结束了。
最终验收清单
做完这页后,你应该能回答:
- Starter、autoconfigure、SDK 为什么要拆开?
@ConfigurationProperties为什么比一堆@Value更适合公共配置?- 自动配置类是怎么被发现的?
- Boot 2 和 Boot 3 自动配置清单有什么区别?
@ConditionalOnMissingBean为什么重要?- Starter 没生效怎么排查?
/actuator/conditions、/actuator/configprops、/actuator/env分别看什么?- HealthIndicator 和业务指标怎么加?
- FailureAnalyzer 解决什么问题?
- 配置解密为什么应该放
EnvironmentPostProcessor? - Runner 为什么不适合做不可控长任务?
关联知识点
| 知识点 | 入口 |
|---|---|
| Spring Boot 主线 | 从零到生产级掌握 |
| 自动配置全过程 | 自动配置与扩展点全过程 |
| Starter 全过程 | Starter 全过程 |
| 配置体系 | 配置体系全过程 |
| 启动流程 | 启动流程全过程 |
| 扩展点 | Spring Boot 扩展点 |
| 面试 | Spring Boot 面试题 |
