You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot REST接口反序列化前如何记录指定端点原始请求体

可行实现方案(均不破坏原有@RequestBody自动反序列化与@Valid参数校验能力)


方案1:使用RequestBodyAdvice(精准匹配指定端点,推荐)

Spring 提供的RequestBodyAdvice接口专门用于增强@RequestBody的处理逻辑,其beforeBodyRead方法会在请求体反序列化为Java对象之前触发,完美匹配需求场景。
实现代码如下:

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import org.springframework.util.StreamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ControllerAdvice
public class NotificationRequestBodyLogAdvice extends RequestBodyAdviceAdapter {
    private static final Logger log = LoggerFactory.getLogger(NotificationRequestBodyLogAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 仅匹配目标控制器的目标方法,无额外性能损耗
        return methodParameter.getContainingClass() == NotificationController.class
                && "handleNotification".equals(methodParameter.getMethod().getName());
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 读取原始请求体
        String rawBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
        // 记录日志,可自行追加链路ID、请求IP等排查字段
        log.info("/api/notification 原始请求体: {}", rawBody);
        
        // 重新构造请求流返回给后续反序列化、校验逻辑使用
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() {
                return new ByteArrayInputStream(rawBody.getBytes(StandardCharsets.UTF_8));
            }
            @Override
            public HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };
    }
}

该方案优势:

  • 匹配逻辑精准,仅作用于目标接口,不会影响其他业务端点
  • 完全无侵入,不需要修改原有Controller代码
  • 触发时机正好在反序列化之前,即使后续反序列化/校验抛出异常也已经完成原始请求体记录

方案2:使用ContentCachingRequestWrapper + 过滤器(适合多端点扩展)

如果后续需要对多个接口增加原始请求体日志能力,可以通过过滤器 + Spring 自带的请求包装类实现,代码如下:

import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.util.AntPathMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class RawRequestLogFilter extends OncePerRequestFilter {
    private static final Logger log = LoggerFactory.getLogger(RawRequestLogFilter.class);
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    private static final String TARGET_PATH = "/api/notification";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 包装原生请求,将请求体缓存到内存中,支持多次读取
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        try {
            // 继续执行后续请求逻辑(包括反序列化、参数校验、业务处理)
            filterChain.doFilter(wrappedRequest, response);
        } finally {
            // 仅匹配目标端点时输出日志
            if (PATH_MATCHER.match(TARGET_PATH, request.getRequestURI())) {
                String rawBody = new String(wrappedRequest.getContentAsByteArray(), StandardCharsets.UTF_8);
                log.info("/api/notification 原始请求体: {}", rawBody);
            }
        }
    }
}

注意点:

  • 日志记录放在finally块中,即使请求处理过程中抛出任何异常也能正常落盘原始请求体,满足链路追溯要求
  • ContentCachingRequestWrapper会自动处理请求体缓存,不会影响后续@RequestBody读取请求流

内容的提问来源于stack exchange,提问作者Robert Strauch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 13:27:00