Spring Boot:如何在HandlerInterceptor的postHandle读取HttpServletResponse响应?
解决Spring Boot中HandlerInterceptor无法读取HttpServletResponse响应内容的问题
默认的HttpServletResponse实现不支持重复读取响应体——响应流在控制器处理完成后就已被写出,数据不会被缓存,因此在postHandle方法中直接读取会失败。要实现这一点,需要通过包装响应对象并缓存响应内容的方式来解决,具体步骤如下:
1. 自定义响应包装类
创建一个继承HttpServletResponseWrapper的类,用ByteArrayOutputStream缓存响应内容,同时重写输出流相关方法,确保响应数据既被缓存又能正常返回给客户端:
import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; public class CachedBodyHttpServletResponseWrapper extends HttpServletResponseWrapper { private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); private PrintWriter printWriter; public CachedBodyHttpServletResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener writeListener) { // 按需实现,此处留空不影响核心逻辑 } @Override public void write(int b) throws IOException { byteArrayOutputStream.write(b); // 同步写入原始响应流,保证客户端能收到响应 getResponse().getOutputStream().write(b); } }; } @Override public PrintWriter getWriter() throws IOException { if (printWriter == null) { printWriter = new PrintWriter(new StringWriter() { @Override public void write(String str) { try { byteArrayOutputStream.write(str.getBytes(getCharacterEncoding())); // 同步写入原始响应流 getResponse().getWriter().write(str); } catch (IOException e) { throw new RuntimeException(e); } } }); } return printWriter; } // 获取缓存的响应内容,转为字符串 public String getCachedBody() throws IOException { return byteArrayOutputStream.toString(getCharacterEncoding()); } }
2. 实现Filter替换响应对象
创建一个Filter,在请求进入时将原始HttpServletResponse替换为自定义包装类,确保后续控制器的响应写入操作被缓存:
import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class ResponseCachingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { CachedBodyHttpServletResponseWrapper wrappedResponse = new CachedBodyHttpServletResponseWrapper((jakarta.servlet.http.HttpServletResponse) response); chain.doFilter(request, wrappedResponse); } }
如果项目未开启Filter自动扫描,需在配置类手动注册:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<ResponseCachingFilter> responseCachingFilter() { FilterRegistrationBean<ResponseCachingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new ResponseCachingFilter()); registrationBean.addUrlPatterns("/*"); return registrationBean; } }
3. 在Interceptor中读取缓存的响应内容
在postHandle方法中,将response强转为自定义包装类,调用getCachedBody()即可获取响应内容:
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import java.io.IOException; public class LoggingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { // 你的前置日志逻辑 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws IOException { // 读取并处理响应内容 if (response instanceof CachedBodyHttpServletResponseWrapper wrappedResponse) { String responseBody = wrappedResponse.getCachedBody(); // 将responseBody写入日志表的逻辑 System.out.println("响应内容:" + responseBody); } } }
注意事项
- 字符编码:通过
getCharacterEncoding()获取响应编码,避免缓存内容出现乱码。 - 大响应场景:若响应内容过大,缓存会占用较多内存,可考虑改用临时文件存储缓存。
- 流式响应:对于下载大文件等流式输出场景,该缓存方式不适用,需单独处理。
内容的提问来源于stack exchange,提问作者Anas Abdeljalil
相关产品推荐
相关产品推荐

