如何基于状态重写Spring Boot Actuator默认/health响应状态码
如何重写Spring Boot Actuator /health端点的响应状态码
这个需求很常见——默认的/health端点不管健康状态是UP、DOWN还是UNKNOWN,都返回200的HTTP状态码,有时候我们希望用状态码直接反映服务健康情况,方便监控系统快速识别。不用自定义端点的话,有两种靠谱的实现方式:
方案一:自定义HealthEndpointWebExtension(官方推荐方式)
Spring Boot Actuator的端点是通过扩展机制实现的,我们可以继承官方的HealthEndpointWebExtension,重写它的方法来修改响应状态码。这种方式最贴合Actuator的设计,不会破坏原有端点的功能。
实现代码
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.web.servlet.WebEndpointResponse; import org.springframework.boot.actuate.health.web.servlet.HealthEndpointWebExtension; import org.springframework.stereotype.Component; @Component public class CustomHealthEndpointWebExtension extends HealthEndpointWebExtension { // 注入原始的HealthEndpoint作为委托对象 public CustomHealthEndpointWebExtension(HealthEndpoint delegate) { super(delegate); } @Override public WebEndpointResponse<Health> getHealth(boolean showDetails) { // 先获取原始的健康响应 WebEndpointResponse<Health> originalResponse = super.getHealth(showDetails); Health health = originalResponse.getBody(); Status status = health.getStatus(); // 根据健康状态映射自定义HTTP状态码 int statusCode = switch (status.getCode()) { case "UP" -> 200; case "DOWN" -> 400; case "UNKNOWN" -> 300; default -> originalResponse.getStatus(); // 自定义状态默认保留原码 }; // 返回修改后的响应 return new WebEndpointResponse<>(health, statusCode); } }
说明
- 这个类会自动替换掉默认的
HealthEndpointWebExtension,所有对/health的请求都会经过我们重写的方法。 - 如果你用的是WebFlux应用,需要换成
org.springframework.boot.actuate.health.web.reactive.HealthEndpointWebExtension,对应的响应类是同包下的WebEndpointResponse。
方案二:使用Servlet Filter拦截响应(通用方式)
如果你不想深入Actuator的内部机制,也可以用一个普通的Servlet Filter来拦截/health的请求,解析响应体中的健康状态后修改HTTP状态码。这种方式更灵活,适合各种场景。
实现代码
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; @Component public class HealthStatusCodeFilter implements Filter { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 只拦截/actuator/health的请求(注意路径要和你的Actuator配置一致) if ("/actuator/health".equals(httpRequest.getRequestURI())) { // 用自定义包装类捕获响应内容 ContentCapturingResponseWrapper responseWrapper = new ContentCapturingResponseWrapper(httpResponse); chain.doFilter(request, responseWrapper); // 解析响应体中的Health对象 String responseBody = responseWrapper.getCapturedContent(); Health health = objectMapper.readValue(responseBody, Health.class); Status status = health.getStatus(); // 设置自定义状态码 int customCode = switch (status.getCode()) { case "UP" -> 200; case "DOWN" -> 400; case "UNKNOWN" -> 300; default -> responseWrapper.getStatus(); }; // 将修改后的状态码和原响应内容写回客户端 httpResponse.setStatus(customCode); httpResponse.getOutputStream().write(responseWrapper.getCapturedContentAsBytes()); httpResponse.getOutputStream().flush(); } else { // 非/health请求直接放行 chain.doFilter(request, response); } } // 自定义响应包装类,用于捕获响应体内容 private static class ContentCapturingResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream content = new ByteArrayOutputStream(); public ContentCapturingResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener listener) {} @Override public void write(int b) throws IOException { content.write(b); } }; } public String getCapturedContent() { return content.toString(StandardCharsets.UTF_8); } public byte[] getCapturedContentAsBytes() { return content.toByteArray(); } } }
说明
- 注意要根据你的Actuator配置调整拦截路径,如果自定义了
management.endpoints.web.base-path,要修改判断条件中的请求URI。 - WebFlux应用需要换成
WebFilter,响应包装逻辑也要适配Reactive环境。
额外注意事项
- 确保已经暴露了/health端点:在
application.properties或application.yml中配置management.endpoints.web.exposure.include=health。 - 如果有自定义的健康状态(比如除了UP/DOWN/UNKNOWN之外的状态),记得在switch语句中添加对应的状态码映射,或者保留默认值。
- 两种方案都不需要自定义端点,完全基于原有的/health端点扩展,不会影响原有功能。
内容的提问来源于stack exchange,提问作者Débora




