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

Spring Boot中保留@RequestBody同时记录完整请求体的方法

保留@RequestBody同时记录完整请求体的解决方案

方案一:自定义请求体缓存过滤器

由于HttpServletRequest的输入流仅能读取一次,我们需要通过HttpServletRequestWrapper缓存请求体,既保证@RequestBody正常解析,又能读取原始内容记录日志。

  1. 实现缓存请求体的Wrapper类:
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
    private byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = requestInputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, bytesRead);
        }
        cachedBody = byteArrayOutputStream.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedServletInputStream(cachedBody);
    }

    private static class CachedServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public CachedServletInputStream(byte[] cachedBody) {
            this.inputStream = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {}

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }
    }

    public byte[] getCachedBody() {
        return cachedBody;
    }
}
  1. 实现过滤器类:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
@WebFilter(urlPatterns = "/your-api-path/*") // 替换为你的接口路径
public class RequestBodyLoggingFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(RequestBodyLoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request);
            String requestBody = new String(cachedRequest.getCachedBody(), StandardCharsets.UTF_8);
            logger.info("原始请求体: {}", requestBody);
            chain.doFilter(cachedRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }
}

此方案无需修改原有控制器方法,@RequestBody可正常解析UserDto,过滤器会自动记录请求体。


方案二:使用RequestBodyAdvice

Spring提供的RequestBodyAdvice可在@RequestBody解析前后处理请求,适合在解析前获取原始请求体。

实现RequestBodyAdvice:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
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;

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

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true; // 可指定仅对特定方法/类生效,此处默认对所有@RequestBody生效
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        byte[] bodyBytes = inputMessage.getBody().readAllBytes();
        String requestBody = new String(bodyBytes, StandardCharsets.UTF_8);
        logger.info("请求体: {}", requestBody);
        // 重新包装输入流,避免后续解析时流已关闭
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(bodyBytes);
            }

            @Override
            public org.springframework.http.HttpHeaders getHeaders() {
                return inputMessage.getHeaders();
            }
        };
    }
}

原有控制器方法无需修改,该Advice会自动在解析@RequestBody前记录原始请求体。


方案三:AOP切面(针对特定控制器方法)

若仅需对个别控制器方法记录请求体,可使用AOP切面:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;

@Aspect
@Component
public class RequestBodyLoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(RequestBodyLoggingAspect.class);

    @Before("execution(* com.yourpackage.YourController.createUser(..))") // 替换为你的控制器方法路径
    public void logRequestBody(JoinPoint joinPoint) throws IOException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            // 需配合方案一的缓存过滤器使用,避免输入流已被读取导致的异常
            BufferedReader reader = request.getReader();
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            logger.info("请求体: {}", sb.toString());
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 04:31:05