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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:53:13