You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何基于状态重写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环境。

额外注意事项

  1. 确保已经暴露了/health端点:在application.propertiesapplication.yml中配置management.endpoints.web.exposure.include=health
  2. 如果有自定义的健康状态(比如除了UP/DOWN/UNKNOWN之外的状态),记得在switch语句中添加对应的状态码映射,或者保留默认值。
  3. 两种方案都不需要自定义端点,完全基于原有的/health端点扩展,不会影响原有功能。

内容的提问来源于stack exchange,提问作者Débora

火山引擎 最新活动