🔍 源码精读:AbstractWebEndpointHandlerMapping —— 理解 Servlet 与 WebFlux 的公共逻辑提取
在 Spring Boot Actuator 中,AbstractWebEndpointHandlerMapping 是一个抽象基类,用于提取 Servlet(MVC) 和 Reactor(WebFlux) 两种模型下 Web 端点处理的公共逻辑。
本文将带你深入 AbstractWebEndpointHandlerMapping 源码,解析它是如何实现“一次定义,多模型适配”的设计哲学,理解 Spring Boot 的抽象能力。
一、类图关系与作用
AbstractWebEndpointHandlerMapping (抽象基类)
│
├───────────────┐
↓ ↓
ControllerEndpointHandlerMapping WebFluxEndpointHandlerMapping
(Servlet 模型) (WebFlux 模型)
📁 源码路径:
org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebEndpointHandlerMapping
⚠️ 注意:虽然路径是
servlet,但它被 Servlet 和 WebFlux 模型共同继承。
二、核心职责
AbstractWebEndpointHandlerMapping 封装了两种模型共有的逻辑:
| 职责 | 说明 |
|---|---|
| ✅ 端点路径映射 | 构建 /actuator/{id} 路径 |
| ✅ CORS 配置 | 支持跨域 |
| ✅ 媒体类型处理 | 支持 application/json |
| ✅ 安全配置 | 集成 Spring Security |
| ✅ 基础属性注入 | WebEndpointProperties、EndpointMediaTypes |
✅ 它不关心“如何处理请求”,只关心“如何配置和初始化”。
三、源码结构解析
public abstract class AbstractWebEndpointHandlerMapping extends AbstractEndpointHandlerMapping {
protected AbstractWebEndpointHandlerMapping(
Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes mediaTypes,
String basePath,
CorsConfiguration corsConfiguration) {
super(endpoints, mediaTypes, basePath);
setCorsConfiguration(corsConfiguration);
}
// 提供 protected 方法供子类使用
protected void initCorsConfiguration(CorsConfiguration corsConfiguration) {
// 初始化 CORS
}
@Override
protected void customizeLookupPath(EndpointId endpointId, RequestMappingInfo.Builder builder) {
// 可选:自定义路径查找逻辑
}
}
它继承自更上层的 AbstractEndpointHandlerMapping。
四、AbstractEndpointHandlerMapping —— 更高层抽象
📁 路径:
org.springframework.boot.actuate.endpoint.web.AbstractEndpointHandlerMapping
核心功能:
public abstract class AbstractEndpointHandlerMapping
implements ApplicationContextAware, InitializingBean {
private final Collection<ExposableWebEndpoint> endpoints;
private final EndpointMediaTypes mediaTypes;
private final String basePath;
public AbstractEndpointHandlerMapping(
Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes mediaTypes,
String basePath) {
Assert.notNull(endpoints, "Endpoints must not be null");
Assert.notNull(mediaTypes, "MediaTypes must not be null");
Assert.hasText(basePath, "BasePath must not be empty");
this.endpoints = endpoints;
this.mediaTypes = mediaTypes;
this.basePath = basePath.startsWith("/") ? basePath : "/" + basePath;
}
@Override
public void afterPropertiesSet() {
// 子类实现初始化
initHandlerMethods();
}
protected abstract void initHandlerMethods();
}
✅ 这是所有 Web 端点映射的“根”抽象类。
五、公共逻辑提取详解
1. 构造函数:统一初始化
super(endpoints, mediaTypes, basePath);
endpoints:来自WebEndpointDiscoverer.getEndpoints()mediaTypes:请求/响应的 MIME 类型(默认application/json)basePath:来自WebEndpointProperties.basePath(默认/actuator)
✅ 所有子类都使用相同的初始化参数。
2. 路径构建:getBasePath() + endpointId
String path = getBasePath() + "/" + endpoint.getEndpointId();
getBasePath()来自父类endpoint.getEndpointId()是@Endpoint(id = "xxx")- 生成
/actuator/health、/actuator/metrics等路径
✅ 两种模型都使用相同的路径规则。
3. CORS 配置统一处理
setCorsConfiguration(corsConfiguration);
- 支持
management.endpoints.web.cors.*配置 - 如:
management: endpoints: web: cors: allowed-origins: "https://admin.example.com" allowed-methods: "GET,POST"
✅ 子类无需重复实现 CORS 逻辑。
4. 媒体类型支持
protected final EndpointMediaTypes mediaTypes;
- 用于设置
@RequestMapping的produces - 确保返回
application/json
✅ 统一内容协商机制。
六、子类如何差异化实现?
虽然公共逻辑被提取,但两种模型的请求处理机制完全不同,由子类各自实现。
1. ControllerEndpointHandlerMapping(Servlet)
@Override
protected void initHandlerMethods() {
for (ExposableWebEndpoint endpoint : getEndpoints()) {
registerHandlerMethod(endpoint.getEndpointInstance(), mappingFor(endpoint));
}
}
- 使用
registerHandlerMethod()注册到 Spring MVC 的handlerMethods映射表 - 基于
HandlerMethod反射调用
2. WebFluxEndpointHandlerMapping(WebFlux)
@Override
public Mono<HandlerResult> getHandler(ServerWebExchange exchange) {
return getHandlerInternal(exchange).flatMap(this::createHandlerResult);
}
private Mono<ExposableWebEndpoint> getHandlerInternal(ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getURI().getRawPath();
return findEndpoint(this.endpoints, lookupPath)
.switchIfEmpty(Mono.empty())
.map(Mono::just)
.orElse(Mono.empty());
}
- 实现
WebHandler接口 - 返回
Mono<HandlerResult>,完全响应式
✅ 差异被封装在
initHandlerMethods()和getHandler()中。
七、设计模式:模板方法(Template Method)
AbstractWebEndpointHandlerMapping 典型使用了 模板方法模式:
public abstract class AbstractEndpointHandlerMapping
implements InitializingBean {
@Override
public final void afterPropertiesSet() {
initHandlerMethods(); // 子类实现
}
protected abstract void initHandlerMethods();
}
afterPropertiesSet()是模板方法(固定流程)initHandlerMethods()是抽象方法(由子类实现)
✅ 实现“流程统一,细节可变”。
八、完整调用流程对比
共同流程(由父类完成)
1. 构造 AbstractWebEndpointHandlerMapping
├─ 注入 endpoints, mediaTypes, basePath, cors
↓
2. afterPropertiesSet() → 调用 initHandlerMethods()
↓
3. 构建路径:/actuator/{id}
↓
4. 配置 CORS、Media Types
分支流程(由子类实现)
| 模型 | 子类 | 处理方式 |
|---|---|---|
| Servlet | ControllerEndpointHandlerMapping | 注册 HandlerMethod 到 Spring MVC |
| WebFlux | WebFluxEndpointHandlerMapping | 实现 WebHandler.getHandler() 返回 Mono |
九、源码价值总结
| 优势 | 说明 |
|---|---|
| ✅ 代码复用 | 避免 Servlet 和 WebFlux 重复实现路径、CORS、媒体类型等逻辑 |
| ✅ 扩展性 | 新增 Web 模型(如 gRPC)可继承此抽象类 |
| ✅ 维护性 | 修改 basePath 或 CORS 逻辑只需改一处 |
| ✅ 一致性 | 两种模型的端点路径、行为保持一致 |
| ✅ 解耦 | 将“配置”与“执行”分离 |
十、如何验证抽象有效性?
方法一:修改 basePath
management:
endpoints:
web:
base-path: /manage
启动后:
/manage/health可访问(Servlet)/manage/health可访问(WebFlux)
✅ 证明路径逻辑被统一管理。
方法二:启用 CORS
management:
endpoints:
web:
cors:
allowed-origins: "https://admin.example.com"
两种模型下都能正确返回 Access-Control-Allow-Origin。
✅ 总结:AbstractWebEndpointHandlerMapping 的设计精髓
| 层级 | 类 | 职责 |
|---|---|---|
| L1 | AbstractEndpointHandlerMapping | 最高层抽象:端点集合、basePath、mediaTypes |
| L2 | AbstractWebEndpointHandlerMapping | Web 层抽象:CORS、安全、路径定制 |
| L3 | ControllerEndpointHandlerMapping | Servlet 实现:HandlerMethod |
| L3 | WebFluxEndpointHandlerMapping | WebFlux 实现:WebHandler + Mono |
🎯 核心思想:
公共逻辑上提,差异实现下放,实现“一次定义,多模型适配”。
📘 收获:
- 理解了 Spring Boot Actuator 如何通过继承 + 模板方法支持多编程模型。
- 掌握了
AbstractWebEndpointHandlerMapping是如何提取公共逻辑的。 - 为后续阅读其他
AbstractXXX类(如AbstractEndpoint)提供了范式。


被折叠的 条评论
为什么被折叠?



