在开发web服务的时候,一般都会对Request或Response进行多次读取,比如在过滤器或拦截器中,统一打印收到request和响应reponse的内容日志,方便查询问题。但是如果是使用SpringBoot Web默认的HttpServletRequest或HttpServletResponse时,这两个对象的数据流只能被读取一次,比如如果在拦截器中读取了request的内容,那么在Controller中取参数时,数据变为成空字符串了。现在SpringBoot也意识到了这个问题,提供了两个Request和Reponse的包装类,可以解决这个问题,但是也有几个小坑,需要注意回避一下。
请求包装类:ContentCachingRequestWrapper
这个是对HttpServletRequest的包装类,在使用它的时候,一般会调用它的这两个方法:

但是可能会让你失望,如果是在拦截器的preHandler里面直接调用,返回是空字符串或字节长度是0的数组,因为只有它包括的类中的getInputStream被读取之后,包装类才会有缓存,如果是在Controller调用之后再调用,是没有问题的。
为了解决在preHandler中调用返回为空的问题,需要稍等修改一下,重写一下getInputStream方法:
package cn.jw.starter.web.core.interceptors;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
/**
*
* @author 王广帅
* @version 1.0.0
* @since 2026/2/7 23:25
*/
public class JwContentCachingRequestWrapper extends ContentCachingRequestWrapper {
public JwContentCachingRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 先读取一次数据,再使用
StreamUtils.copyToByteArray(super.getInputStream());
final ByteArrayInputStream in = new ByteArrayInputStream(this.getContentAsByteArray());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return in.read();
}
};
}
}
响应包装类:ContentCachingResponseWrapper
这个是HttpServletRequest的包装,在Filter中包装之后,Controller类层可以直接使用,但是Controller处理之后,响应的数据是会缓存在包装类中,而真正的HttpServletResponse中是没有数据的。因此,需要在响应前,在过滤器过拦截器中进行一次数据的复制:
/**
* 更新响应(不操作这一步,会导致接口响应空白)
*
* @param response
* 响应对象
* @throws IOException
* /
*/
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (responseWrapper != null) {
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
}
完整代码
过滤器中添加包装类
package cn.jw.starter.web.core.filters;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import cn.hutool.extra.spring.SpringUtil;
import cn.jw.starter.web.core.interceptors.JwContentCachingRequestWrapper;
import cn.jw.starter.web.core.properties.JwWebProperties;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 缓存请求消息的filter,
*
* @author 王广帅
* @since 2024/4/3 20:31
*/
public class CacheRequestBodyFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String uri = request.getRequestURI();
// 静态资源都不要走这个过滤
if (uri.endsWith(".html") || uri.endsWith(".js") || uri.endsWith(".css") || uri.endsWith(".ico")) {
return true;
}
JwWebProperties jwWebProperties = SpringUtil.getBean(JwWebProperties.class);
if (jwWebProperties.getIgnoreCacheBodyUriList().contains(uri)) {
return true;
}
return super.shouldNotFilter(request);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getContentLength() > 0 && !(request instanceof JwContentCachingRequestWrapper)) {
request = new JwContentCachingRequestWrapper(request);
request.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
}
filterChain.doFilter(request, response);
updateResponse(response);
}
/**
* 更新响应(不操作这一步,会导致接口响应空白)
*
* @param response
* 响应对象
* @throws IOException
* /
*/
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (responseWrapper != null) {
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
}
}
拦截器统一打印日志
package cn.jw.starter.web.core.interceptors;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import cn.hutool.extra.spring.SpringUtil;
import cn.jw.starter.common.utils.JwUidUtil;
import cn.jw.starter.web.core.properties.JwWebProperties;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 请求日志记录
*
* @author wgslucky
* @since 2024/4/30 1:19
**/
public class RequestLogInterceptor implements HandlerInterceptor, Ordered {
private final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);
private static final String TID = "tid";
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 100;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String tid = JwUidUtil.getUidOfSystem();
MDC.put(TID, tid);
if (this.logger.isDebugEnabled()) {
String requestUri = request.getRequestURI();
JwWebProperties jwWebProperties = SpringUtil.getBean(JwWebProperties.class);
if (jwWebProperties.getIgnoreCacheBodyUriList().contains(requestUri)) {
return true;
}
String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
logger.debug("({})-> {}", requestUri, body);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (this.logger.isDebugEnabled()) {
String requestUri = request.getRequestURI();
JwWebProperties jwWebProperties = SpringUtil.getBean(JwWebProperties.class);
if (jwWebProperties.getIgnoreCacheBodyUriList().contains(requestUri)) {
MDC.remove(TID);
return;
}
ContentCachingResponseWrapper responseWrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (responseWrapper != null) {
String responseBody =
IOUtils.toString(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
logger.debug("({}) <-- {}", requestUri, responseBody);
}
}
MDC.remove(TID);
}
}