Spring Boot中保留@RequestBody同时记录完整请求体的方法
保留@RequestBody同时记录完整请求体的解决方案
方案一:自定义请求体缓存过滤器
由于HttpServletRequest的输入流仅能读取一次,我们需要通过HttpServletRequestWrapper缓存请求体,既保证@RequestBody正常解析,又能读取原始内容记录日志。
- 实现缓存请求体的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; } }
- 实现过滤器类:
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
相关产品推荐
相关产品推荐

