Spring Boot 扩展点
Spring Boot 扩展点解决的是:不改框架源码,也不把逻辑硬塞进业务代码,就能在合适的生命周期阶段插入自己的逻辑。
扩展点不是越多越好。真正要学会的是:
先判断你要干预的是“启动前配置”“Bean 定义”“Bean 初始化”“应用启动完成”“Web 请求链路”“健康检查”“失败诊断”中的哪一段,再选择对应扩展点。
学习目标
学完本页,你应该能回答:
- Spring Boot 启动过程有哪些阶段可以扩展。
EnvironmentPostProcessor、ApplicationContextInitializer、ApplicationRunner有什么区别。- 什么场景用
BeanPostProcessor,什么场景用ApplicationListener。 - Web 项目里拦截器、过滤器、参数解析器、返回值处理器分别在哪一层生效。
- Actuator 健康检查和指标怎么扩展。
- 自定义 Starter 通常要用哪些扩展点。
- 面试时如何讲“我不只会用注解,还懂框架扩展链路”。
本页按扩展点逐个讲用法。如果你想先建立“自动配置什么时候发生、扩展点插在哪一步、为什么选错会出问题”的全链路认知,先看 Spring Boot 自动配置与扩展点全过程。
扩展点全景图
flowchart TD
A["Spring Boot 扩展点"] --> B["启动早期"]
A --> C["容器阶段"]
A --> D["启动完成"]
A --> E["Web 请求链路"]
A --> F["运维监控"]
A --> G["失败诊断"]
B --> B1["EnvironmentPostProcessor"]
B --> B2["ApplicationContextInitializer"]
C --> C1["BeanFactoryPostProcessor"]
C --> C2["BeanPostProcessor"]
D --> D1["ApplicationRunner"]
D --> D2["CommandLineRunner"]
E --> E1["Filter / Interceptor"]
E --> E2["WebMvcConfigurer"]
F --> F1["HealthIndicator / MeterBinder"]
G --> G1["FailureAnalyzer"]按启动阶段理解扩展点
flowchart TD
A["启动 SpringApplication"] --> B["准备 Environment"]
B --> C["创建 ApplicationContext"]
C --> D["加载 BeanDefinition"]
D --> E["实例化和初始化 Bean"]
E --> F["启动 WebServer"]
F --> G["发布启动完成事件"]
G --> H["执行 Runner"]| 阶段 | 常用扩展点 | 适合做什么 |
|---|---|---|
| 准备 Environment | EnvironmentPostProcessor | 加载外部配置、解密配置、插入属性源 |
| 创建容器后刷新前 | ApplicationContextInitializer | 修改容器属性、注册早期组件 |
| BeanDefinition 阶段 | BeanFactoryPostProcessor | 修改 BeanDefinition |
| Bean 初始化阶段 | BeanPostProcessor | Bean 初始化前后增强 |
| 事件阶段 | ApplicationListener | 监听启动、刷新、失败等事件 |
| 启动完成后 | ApplicationRunner、CommandLineRunner | 预热缓存、校验依赖、启动任务 |
| Web 请求链路 | Filter、HandlerInterceptor | 日志、鉴权、traceId、限流 |
| MVC 定制 | WebMvcConfigurer | 参数解析、跨域、格式化、拦截器 |
| 监控 | HealthIndicator、MeterBinder | 健康检查、自定义指标 |
| 失败诊断 | FailureAnalyzer | 把启动异常翻译成人能看懂的提示 |
EnvironmentPostProcessor
EnvironmentPostProcessor 在 Bean 创建之前执行,适合修改配置环境。比如从自定义位置加载配置、对敏感配置解密、加入默认属性。
流程:
flowchart TD
A["SpringApplication 启动"] --> B["创建 Environment"]
B --> C["执行 EnvironmentPostProcessor"]
C --> D["加入或修改 PropertySource"]
D --> E["后续自动配置读取配置"]Demo:加入默认采集配置。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
public class CollectDefaultEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("collect.thread.core-size", "8");
map.put("collect.thread.queue-capacity", "1000");
environment.getPropertySources().addLast(
new MapPropertySource("collectDefaultProperties", map)
);
}
}声明文件,Boot 2 常见:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.collect.CollectDefaultEnvironmentPostProcessor适合场景:
| 场景 | 原因 |
|---|---|
| 配置解密 | 自动配置读取前就要解密 |
| 加载外部配置 | 让后续 @ConfigurationProperties 能绑定 |
| 设置默认值 | 不影响用户显式配置 |
常见坑:
| 坑 | 后果 |
|---|---|
| 在这里访问业务 Bean | 此时 Bean 还没创建 |
| 做慢网络调用 | 启动变慢甚至失败 |
| 属性源优先级放错 | 用户配置可能被默认值覆盖 |
ApplicationContextInitializer
ApplicationContextInitializer 在 ApplicationContext 创建后、refresh() 之前执行。此时容器对象有了,但 Bean 还没有正式创建。
flowchart TD
A["创建 ApplicationContext"] --> B["执行 Initializer"]
B --> C["修改容器或注册早期对象"]
C --> D["refresh 创建 Bean"]Demo:设置一个启动标记。
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class CollectContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory()
.registerSingleton("collectStartupMarker", "collect-context-ready");
}
}注册方式:
SpringApplication application = new SpringApplication(DemoApplication.class);
application.addInitializers(new CollectContextInitializer());
application.run(args);和 EnvironmentPostProcessor 区别:
| 对比 | EnvironmentPostProcessor | ApplicationContextInitializer |
|---|---|---|
| 执行时机 | Environment 准备阶段 | Context 创建后、refresh 前 |
| 主要对象 | 配置环境 | Spring 容器 |
| 典型用途 | 配置解密、属性源 | 修改容器、注册早期对象 |
ApplicationListener
Spring Boot 启动过程中会发布很多事件,例如环境准备完成、容器准备完成、启动完成、启动失败。
flowchart TD
A["启动过程"] --> B["发布事件"]
B --> C["ApplicationListener 接收"]
C --> D["执行监听逻辑"]Demo:监听应用启动完成。
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class CollectReadyListener
implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("采集服务启动完成,可以接收任务");
}
}常见事件:
| 事件 | 含义 | 适合做什么 |
|---|---|---|
ApplicationStartingEvent | 启动刚开始 | 很少直接使用 |
ApplicationEnvironmentPreparedEvent | Environment 准备好 | 读取配置、日志准备 |
ApplicationPreparedEvent | Context 准备好 | 观察 BeanDefinition |
ApplicationReadyEvent | 应用可服务 | 缓存预热、注册状态 |
ApplicationFailedEvent | 启动失败 | 告警、诊断日志 |
注意:监听启动完成后做预热可以,但不要长时间阻塞主线程。如果预热很慢,要异步执行并做好状态标记。
ApplicationRunner 和 CommandLineRunner
这两个扩展点都在容器启动完成后执行,适合做启动后任务。
flowchart TD
A["容器 refresh 完成"] --> B["WebServer 启动"]
B --> C["执行 Runner"]
C --> D["应用进入可用状态"]区别:
| 扩展点 | 参数形式 | 适合 |
|---|---|---|
ApplicationRunner | ApplicationArguments | 需要解析命令行 option |
CommandLineRunner | String[] args | 简单拿原始参数 |
Demo:启动后预热字典缓存。
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DictionaryWarmupRunner implements ApplicationRunner {
private final DictionaryService dictionaryService;
public DictionaryWarmupRunner(DictionaryService dictionaryService) {
this.dictionaryService = dictionaryService;
}
@Override
public void run(ApplicationArguments args) {
dictionaryService.warmup();
}
}注意:
- Runner 里异常可能导致启动失败。
- 不要在 Runner 里执行不可控的长任务。
- 多个 Runner 可以用
@Order控制顺序。 - 如果只是定时任务,不要强行放 Runner。
BeanFactoryPostProcessor
BeanFactoryPostProcessor 在 Bean 实例化之前执行,能修改 BeanDefinition。
这是 Spring 核心扩展点,Spring Boot 中也经常间接受益于它。
flowchart TD
A["BeanDefinition 已注册"] --> B["BeanFactoryPostProcessor"]
B --> C["修改 BeanDefinition"]
C --> D["后续按新定义创建 Bean"]Demo:检查所有 Service Bean 命名。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class ServiceBeanNameCheckPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
String[] names = beanFactory.getBeanDefinitionNames();
for (String name : names) {
if (name.endsWith("Service")) {
System.out.println("service bean = " + name);
}
}
}
}不要在这里提前 getBean(),因为这会导致 Bean 过早实例化,破坏正常生命周期。
BeanDefinitionRegistryPostProcessor
BeanDefinitionRegistryPostProcessor 比 BeanFactoryPostProcessor 更早一点。它能在 BeanDefinition 注册阶段继续往容器里注册新的 BeanDefinition。
一句话区分:
| 扩展点 | 主要能力 | 典型用途 |
|---|---|---|
BeanDefinitionRegistryPostProcessor | 新增或删除 BeanDefinition | 扫描 Mapper、注册代理 Bean、批量注册客户端 |
BeanFactoryPostProcessor | 修改已有 BeanDefinition | 调整属性、校验定义、修改 scope |
为什么它重要?
MyBatis Mapper、Feign Client、Dubbo Reference 这类接口本身不能直接 new 出业务实现,框架需要扫描接口,然后为接口注册一个代理对象的 BeanDefinition。这个动作必须发生在普通 Bean 创建之前,否则业务 Service 注入接口时就找不到 Bean。
flowchart TD
A["扫描接口或配置"] --> B["构造 BeanDefinition"]
B --> C["注册到 BeanDefinitionRegistry"]
C --> D["业务 Bean 依赖该定义"]
D --> E["refresh 阶段创建代理对象"]Demo:注册一个采集客户端 BeanDefinition。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CollectClientRegistryPostProcessor
implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(CollectClient.class)
.addConstructorArgValue("https://hospital.example.com")
.getBeanDefinition();
registry.registerBeanDefinition("collectClient", definition);
}
}常见坑:
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 在这里读取业务 Bean | 此时 Bean 还没创建 | 只处理定义,不处理对象 |
| 注册 Bean 名重复 | 覆盖或启动失败 | 注册前先判断是否存在 |
| 把业务逻辑放进注册器 | 启动过程变复杂 | 只做框架基础设施注册 |
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar 也是注册 BeanDefinition 的扩展点,但它通常和 @Import、自定义 @EnableXxx 注解一起使用。
这类扩展的典型风格是:
@EnableCollectClient
@SpringBootApplication
public class DemoApplication {
}然后 @EnableCollectClient 负责导入注册器。
import org.springframework.context.annotation.Import;
@Import(CollectClientRegistrar.class)
public @interface EnableCollectClient {
}注册器代码:
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class CollectClientRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition("collectClient")) {
registry.registerBeanDefinition(
"collectClient",
BeanDefinitionBuilder
.genericBeanDefinition(CollectClient.class)
.getBeanDefinition()
);
}
}
}它和自动配置的区别:
| 对比 | ImportBeanDefinitionRegistrar | Spring Boot 自动配置 |
|---|---|---|
| 触发方式 | 用户显式加 @EnableXxx 或被配置类导入 | Boot 启动时读取自动配置清单 |
| 主要用途 | 精确注册某类基础设施 Bean | 按条件提供默认 Bean |
| 常见例子 | Mapper 扫描、Feign Client、@EnableScheduling 类思路 | Web、Redis、DataSource、Actuator |
| 面试重点 | 编程式注册 BeanDefinition | 候选配置类 + 条件注解 |
如果是业务项目里普通配置,不要为了“高级”滥用这个扩展点。只有当你需要根据注解元数据、接口扫描结果、动态规则注册 BeanDefinition 时,它才合适。
BeanPostProcessor
BeanPostProcessor 在 Bean 初始化前后执行,适合给 Bean 做增强、包装、校验。
flowchart TD
A["实例化 Bean"] --> B["属性填充"]
B --> C["初始化前 BeanPostProcessor"]
C --> D["初始化方法"]
D --> E["初始化后 BeanPostProcessor"]
E --> F["Bean 可用"]Demo:给带自定义注解的 Bean 打印初始化日志。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CollectBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean.getClass().isAnnotationPresent(CollectComponent.class)) {
System.out.println("before init collect bean: " + beanName);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}常见用途:
| 用途 | 说明 |
|---|---|
| AOP 代理创建 | 初始化后返回代理对象 |
| 注解解析 | 扫描 Bean 上的自定义注解 |
| 客户端增强 | 给 RPC、HTTP 客户端包装监控逻辑 |
| 参数校验 | 启动期检查 Bean 配置是否完整 |
Web 层扩展点
Spring Boot Web 项目常见请求链路:
flowchart TD
A["HTTP 请求"] --> B["Filter"]
B --> C["DispatcherServlet"]
C --> D["HandlerInterceptor"]
D --> E["Controller"]
E --> F["返回值处理"]
F --> G["JSON 响应"]Filter
Filter 是 Servlet 规范层面的扩展,进入 Spring MVC 前就会执行。
适合:
| 场景 | 说明 |
|---|---|
| traceId | 请求一进来就生成 |
| 安全基础过滤 | Token 预处理、IP 黑名单 |
| 请求体包装 | 多次读取 body |
| 跨框架通用逻辑 | 不依赖 Spring MVC |
Demo:
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.UUID;
@Component
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, jakarta.servlet.ServletException {
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("traceId");
}
}
}HandlerInterceptor
Interceptor 是 Spring MVC 层面的扩展,能拿到 Handler 信息。
Demo:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(401);
return false;
}
return true;
}
}注册:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**");
}
}Filter 和 Interceptor 区别:
| 对比 | Filter | Interceptor |
|---|---|---|
| 所属体系 | Servlet | Spring MVC |
| 执行时机 | DispatcherServlet 前 | HandlerMapping 后、Controller 前后 |
| 是否知道 Controller | 不知道 | 知道 handler |
| 适合 | traceId、底层过滤 | 权限、审计、接口日志 |
WebMvcConfigurer
WebMvcConfigurer 是 Spring MVC 常用扩展入口。
常见方法:
| 方法 | 用途 |
|---|---|
addInterceptors | 注册拦截器 |
addCorsMappings | 跨域 |
addFormatters | 类型转换和格式化 |
addArgumentResolvers | 自定义参数解析 |
addResourceHandlers | 静态资源映射 |
configureMessageConverters | 消息转换器 |
自定义参数解析 Demo:
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
String userId = webRequest.getHeader("X-User-Id");
return new LoginUser(userId);
}
}注册:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(
java.util.List<org.springframework.web.method.support.HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CurrentUserArgumentResolver());
}
}Controller 使用:
@GetMapping("/me")
public LoginUser me(@CurrentUser LoginUser user) {
return user;
}这种扩展适合商业系统中的“当前登录用户”“当前租户”“数据权限上下文”等通用参数。
配置绑定扩展点
Spring Boot 推荐用 @ConfigurationProperties 绑定配置。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties(prefix = "collect.retry")
public class CollectRetryProperties {
private int maxTimes = 3;
private long intervalMs = 1000;
public int getMaxTimes() {
return maxTimes;
}
public void setMaxTimes(int maxTimes) {
this.maxTimes = maxTimes;
}
public long getIntervalMs() {
return intervalMs;
}
public void setIntervalMs(long intervalMs) {
this.intervalMs = intervalMs;
}
}自动配置中启用:
@EnableConfigurationProperties(CollectRetryProperties.class)
public class CollectRetryAutoConfiguration {
}扩展点意义:
- 配置从代码中分离。
- 支持类型转换。
- 支持嵌套对象。
- 支持校验。
- 方便生成配置提示元数据。
Actuator 健康检查扩展
自定义健康检查用于告诉监控系统:当前服务依赖的外部组件是否正常。
Demo:检查医院接口是否可用。
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class HospitalApiHealthIndicator implements HealthIndicator {
private final HospitalApiClient hospitalApiClient;
public HospitalApiHealthIndicator(HospitalApiClient hospitalApiClient) {
this.hospitalApiClient = hospitalApiClient;
}
@Override
public Health health() {
try {
boolean ok = hospitalApiClient.ping();
if (ok) {
return Health.up().withDetail("hospitalApi", "ok").build();
}
return Health.down().withDetail("hospitalApi", "unavailable").build();
} catch (Exception ex) {
return Health.down(ex).build();
}
}
}访问:
curl http://127.0.0.1:8080/actuator/health注意:健康检查不能做太重。否则监控探针本身会拖垮服务。
Micrometer 指标扩展
自定义指标用于观察业务运行情况,例如采集成功数、失败数、耗时。
Demo:
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
@Component
public class CollectMetrics {
private final Counter successCounter;
private final Counter failedCounter;
public CollectMetrics(MeterRegistry registry) {
this.successCounter = Counter.builder("collect_success_total")
.description("采集成功次数")
.register(registry);
this.failedCounter = Counter.builder("collect_failed_total")
.description("采集失败次数")
.register(registry);
}
public void success() {
successCounter.increment();
}
public void failed() {
failedCounter.increment();
}
}商业项目里,指标比日志更适合回答“现在整体是否异常”。日志适合查单个请求,指标适合看趋势和告警。
FailureAnalyzer
FailureAnalyzer 用于把启动异常转换成更可读的错误说明。
比如自定义 Starter 要求配置 collect.client.base-url,如果没配置,直接抛空指针很不友好。可以写失败分析器。
自定义异常:
public class CollectClientConfigException extends RuntimeException {
public CollectClientConfigException(String message) {
super(message);
}
}失败分析器:
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
public class CollectClientFailureAnalyzer
extends AbstractFailureAnalyzer<CollectClientConfigException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
CollectClientConfigException cause) {
return new FailureAnalysis(
"采集客户端配置不完整:" + cause.getMessage(),
"请在 application.yml 中配置 collect.client.base-url",
cause
);
}
}声明:
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.collect.CollectClientFailureAnalyzer适合写 Starter 时使用,让接入方看到清楚的错误提示。
自定义 Starter 常用扩展点组合
一个成熟 Starter 往往不是只有自动配置类,而是多个扩展点组合。
flowchart TD
A["自定义 Starter"] --> B["AutoConfiguration"]
A --> C["ConfigurationProperties"]
A --> D["Conditional 注解"]
A --> E["HealthIndicator"]
A --> F["MeterBinder 或 MeterRegistry"]
A --> G["FailureAnalyzer"]
A --> H["EnvironmentPostProcessor"]采集客户端 Starter 可以这样设计:
| 能力 | 扩展点 |
|---|---|
| 默认创建客户端 | @AutoConfiguration + @Bean |
| 配置绑定 | @ConfigurationProperties |
| 用户关闭功能 | @ConditionalOnProperty |
| 用户自定义覆盖 | @ConditionalOnMissingBean |
| 外部接口健康检查 | HealthIndicator |
| 调用次数和耗时 | Micrometer 指标 |
| 缺配置提示 | FailureAnalyzer |
| 默认配置值 | EnvironmentPostProcessor |
扩展点怎么选
| 需求 | 推荐扩展点 | 不推荐 |
|---|---|---|
| 启动前解密配置 | EnvironmentPostProcessor | 在业务 Service 里解密 |
| 修改 BeanDefinition | BeanFactoryPostProcessor | Bean 创建后再改定义 |
| 给 Bean 包装增强 | BeanPostProcessor | 到处手写包装代码 |
| 启动后预热缓存 | ApplicationRunner | Controller 首次请求时懒加载 |
| 记录所有请求 traceId | Filter | 每个 Controller 手动写 |
| 根据注解获取当前用户 | HandlerMethodArgumentResolver | 每个接口手动解析 header |
| 暴露依赖健康状态 | HealthIndicator | 靠日志猜 |
| 自定义启动错误提示 | FailureAnalyzer | 抛晦涩异常 |
扩展点选错为什么会出问题
扩展点最难的不是写接口,而是选时机。Spring Boot 启动过程像一条流水线,越早的扩展点能改的东西越底层,但能使用的对象越少;越晚的扩展点能拿到完整 Bean,但很多配置和条件判断已经结束。
flowchart TD
A["Environment 准备"] --> B["配置绑定和条件判断"]
B --> C["BeanDefinition 注册"]
C --> D["Bean 实例化和初始化"]
D --> E["WebServer 启动"]
E --> F["应用 Ready"]
F --> G["接收业务请求"]| 你想做的事 | 必须发生在 | 选晚了会怎样 |
|---|---|---|
| 配置解密 | 自动配置条件判断之前 | 条件已经按密文或空值判断,默认 Bean 不生效 |
| 动态注册客户端代理 | BeanDefinition 注册阶段 | 容器刷新后再注册,依赖注入已经错过 |
| 包装某类 Bean | Bean 初始化前后 | Bean 已经注入到别处,增强不完整 |
| 启动后预热缓存 | 应用 Ready 前后 | 首次请求变慢,但如果过早做又可能依赖未就绪 |
| 当前用户注入 Controller 参数 | MVC 参数解析阶段 | 每个 Controller 重复解析,逻辑不一致 |
| 输出友好启动失败原因 | 启动失败诊断阶段 | 只能看到底层空指针或连接异常 |
错误案例:配置解密放到 Runner
假设数据库密码在配置文件中是密文:
collect:
client:
base-url: ENC(xxx)如果你在 ApplicationRunner 里解密:
@Component
public class BadDecryptRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 太晚了:自动配置、条件注解、配置绑定已经执行过
}
}此时很多自动配置已经读取过配置。正确做法是用 EnvironmentPostProcessor 在 Environment 阶段插入明文配置,或者在配置中心接入层完成解密。
错误案例:BeanFactoryPostProcessor 里 getBean
BeanFactoryPostProcessor 的职责是修改 BeanDefinition,不是创建 Bean。
public class BadBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
Object service = beanFactory.getBean("orderService");
// 错误:可能导致 Bean 过早创建,BeanPostProcessor、AOP、事务代理还没准备好
}
}如果过早 getBean(),可能绕开后续 BeanPostProcessor,导致事务、AOP、配置注入、生命周期回调不完整。正确做法是只读取和修改 BeanDefinition 元数据。
SmartLifecycle:管理组件启动和停止顺序
ApplicationRunner 适合启动完成后做一次性逻辑;如果你要管理一个长期运行的组件,比如采集连接、后台消费线程、定时轮询客户端,更适合使用 SmartLifecycle。
flowchart TD
A["容器 refresh"] --> B["创建所有单例 Bean"]
B --> C["启动 LifecycleProcessor"]
C --> D["按 phase 启动 SmartLifecycle"]
D --> E["应用运行中"]
E --> F["应用关闭"]
F --> G["按相反顺序 stop"]Demo:启动和停止采集调度器。
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
@Component
public class CollectSchedulerLifecycle implements SmartLifecycle {
private volatile boolean running;
@Override
public void start() {
this.running = true;
// 启动后台采集线程、注册长连接、启动消费循环
}
@Override
public void stop() {
this.running = false;
// 停止拉取新任务,等待正在执行的任务收尾,释放资源
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 100;
}
}phase 越小越早启动,越晚停止。它适合表达组件依赖顺序:
| 组件 | 建议 phase | 原因 |
|---|---|---|
| 基础连接、客户端 | 较小 | 其他组件依赖它 |
| 消费者、采集器 | 中等 | 依赖客户端就绪 |
| 对外接流量的组件 | 较大 | 等内部依赖准备好再接收 |
如果不用 SmartLifecycle,把后台线程随便放到构造方法或 @PostConstruct 里启动,常见问题是:依赖还没准备好、异常不好感知、应用关闭时线程不退出、K8s 滚动发布时任务被中断。
ResponseBodyAdvice:统一响应增强
如果要给所有接口响应补充 traceId、请求时间、统一包装结构,可以使用 ResponseBodyAdvice。它发生在 Controller 返回之后、消息转换器写响应之前。
flowchart TD
A["Controller 返回对象"] --> B["ResponseBodyAdvice"]
B --> C["包装或补充字段"]
C --> D["HttpMessageConverter 序列化 JSON"]
D --> E["返回客户端"]Demo:
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof ApiResult) {
return body;
}
return ApiResult.success(body);
}
}注意边界:
| 场景 | 注意点 |
|---|---|
| 文件下载 | 不要包装二进制流 |
| 字符串返回 | String 转 JSON 包装容易类型冲突 |
| 已经统一返回 | 避免二次包装 |
| 异常响应 | 通常配合 @RestControllerAdvice 统一异常 |
这类扩展点适合做统一协议,不适合塞业务判断。否则所有接口都会被隐藏逻辑影响,排查成本很高。
ExitCodeGenerator:启动失败或退出码
在脚本、容器、CI/CD 里,进程退出码很重要。ExitCodeGenerator 可以让应用在特定失败场景返回明确退出码。
import org.springframework.boot.ExitCodeGenerator;
public class CollectConfigException extends RuntimeException
implements ExitCodeGenerator {
public CollectConfigException(String message) {
super(message);
}
@Override
public int getExitCode() {
return 12;
}
}适合场景:
| 场景 | 退出码意义 |
|---|---|
| 配置缺失 | CI/CD 或运维脚本能识别是配置错误 |
| 外部依赖不可用 | 区分业务启动失败和代码异常 |
| 初始化数据失败 | 避免容器反复假成功 |
它通常和 FailureAnalyzer 搭配:一个负责给人看懂原因,一个负责给脚本或平台识别状态。
AvailabilityState:就绪和存活状态
Spring Boot 支持应用可用性状态,比如 Liveness 和 Readiness。它常和 Kubernetes 探针配合。
| 状态 | 说明 | 常见用途 |
|---|---|---|
| Liveness | 应用进程是否还活着 | 判断是否需要重启容器 |
| Readiness | 应用是否准备好接流量 | 判断是否从 Service 摘除 |
商业项目里很常见的需求是:应用启动了,但字典、连接池、消息订阅、采集通道还没准备好,这时不应该接流量。
flowchart TD
A["应用进程启动"] --> B["Liveness 可用"]
B --> C["加载配置和初始化依赖"]
C --> D{"核心依赖是否准备好"}
D -- "否" --> E["Readiness 拒绝流量"]
D -- "是" --> F["Readiness 接收流量"]如果没有就绪状态,网关或 K8s 可能在应用还没预热完成时就把流量打进来,导致启动后第一批请求失败。
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 扩展点里做重网络调用 | 启动慢或启动失败 | 设置超时,必要时延迟执行 |
BeanFactoryPostProcessor 里 getBean | Bean 过早创建,生命周期错乱 | 只操作 BeanDefinition |
| BeanPostProcessor 返回 null | Bean 注册异常 | 不增强时返回原 Bean |
| Runner 执行长任务 | 应用迟迟不可用 | 异步执行或交给任务系统 |
| Filter 和 Interceptor 职责混乱 | 鉴权、日志重复执行 | 按执行层次拆分职责 |
| HealthIndicator 太重 | 探针拖垮应用 | 轻量检查、超时保护 |
| 构造方法启动后台线程 | Bean 还没完全初始化,关闭时不可控 | 用 SmartLifecycle 管理启停 |
| ResponseBodyAdvice 包装所有响应 | 文件、字符串、异常响应被误包装 | 排除特殊类型,配合统一异常处理 |
| Readiness 没接入 | 应用未准备好就接流量 | 用健康检查和可用性状态控制流量 |
面试标准回答
可以这样答:
Spring Boot 扩展点要按启动阶段理解。启动早期如果要改配置,用 EnvironmentPostProcessor;容器创建后 refresh 前可以用 ApplicationContextInitializer;BeanDefinition 阶段用 BeanFactoryPostProcessor,Bean 初始化前后用 BeanPostProcessor;应用启动完成后做预热可以用 ApplicationRunner 或 CommandLineRunner;Web 请求链路可以用 Filter、Interceptor、WebMvcConfigurer 扩展参数解析、跨域、消息转换;生产监控可以用 HealthIndicator 和 Micrometer 指标;自定义 Starter 还可以用 FailureAnalyzer 优化启动失败提示。
我不会把所有逻辑都塞进一个扩展点,而是先判断要干预哪个生命周期阶段,再选择最小合适的扩展点。比如配置解密必须发生在自动配置读取前,适合 EnvironmentPostProcessor;当前登录用户注入 Controller 参数,适合 HandlerMethodArgumentResolver;采集服务依赖医院接口健康状态,适合 HealthIndicator。追问:
| 追问 | 回答要点 |
|---|---|
| EnvironmentPostProcessor 和 Initializer 区别 | 前者改 Environment,后者改 ApplicationContext |
| Runner 什么时候执行 | 容器启动完成后执行 |
| Filter 和 Interceptor 区别 | Filter 属于 Servlet,Interceptor 属于 Spring MVC |
| BeanPostProcessor 能做什么 | 初始化前后增强 Bean,AOP 代理也和它有关 |
| Starter 常用哪些扩展点 | AutoConfiguration、Properties、Conditional、HealthIndicator、FailureAnalyzer |
关联知识点
- Spring Boot 自动配置与扩展点全过程:理解扩展点在启动全过程中的位置和选型依据。
- Spring Boot 自动配置原理:理解扩展点如何和自动配置配合。
- Spring Boot 启动原理:理解扩展点所在启动阶段。
- Spring Boot Starter:理解扩展点如何沉淀为可复用 Starter。
- Spring 核心扩展点:理解 Spring 容器底层扩展。
- Actuator 监控:理解健康检查和指标端点。
本章小结
Spring Boot 扩展点的价值,是把通用能力放到正确生命周期阶段,而不是侵入每个业务类。配置类、Starter、监控、Web 请求链路、启动诊断都可以通过扩展点标准化。真正成熟的项目,不是到处复制工具代码,而是把可复用能力沉淀成自动配置和扩展点组合。
