Spring cached 的 wrapper 类读取请求响应内容

本文介绍了如何使用Spring的Filter和ContentCachingWrapper类来处理请求和响应体只能读取一次的问题。在日志拦截的场景中,通过ContentCachingRequestWrapper和ContentCachingResponseWrapper可以缓存请求和响应体,以便多次读取。LogFilter类作为OncePerRequestFilter的子类,实现了请求和响应的打印,并在完成后将响应体复制回原始响应。文章还展示了如何根据配置决定是否开启日志记录功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x01:wrapper 类说明

Servlet 的 request body 以及 response body 一旦流被读取了,就无法再次被读取了,因此这对于有些要做拦截业务请求来说,比较麻烦。那么如何处理这个业务场景呢?利用 filter,然后重写 HttpServletRequest 和 HttpServletResponse 包装一层,然后 proceed,最后 response 完之后在把cached 的 body 设置回原始响应。

Spring提供了 ContentCachingRequestWrapper 以及 ContentCachingResponseWrapper 两个类,来解决这类问题。

ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

读取完 Response body 之后

wrappedResponse.copyBodyToResponse();

通过这个设置回去,就可以使得接口调用者可以正常接收响应了。

0x02:日志拦截实例

  • 继承OncePerRequestFilter,顾名思义该filter能够确保在一次请求只通过一次该 filter

  • 实现 Ordered,设置filter的执行顺序,一般设置最低级别执行

package com.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.Ordered;
import org.springframework.web.filter.OncePerRequestFilter;

public class LogFilter extends OncePerRequestFilter implements Ordered {

    @Override
    public int getOrder() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub

    }

}

完整代码大致如下

package com.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

@Component
public class LogFilter extends OncePerRequestFilter implements Ordered {

    @Value("${openLog}")
    private boolean openLog;
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE-8;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper wrappedRequest = null;
        ContentCachingResponseWrapper wrappedResponse = null;
        try{
            if(openLog){
                printHender(request);
                printCookie(request);
                printParameter(request);
                wrappedRequest = new ContentCachingRequestWrapper(request);
                wrappedResponse = new ContentCachingResponseWrapper(response);
                printRequest(wrappedRequest);
                printResponse(wrappedResponse);
            }else{
                filterChain.doFilter(request, response);
            }
        }finally {
            if(wrappedResponse!=null){
                wrappedResponse.copyBodyToResponse();
            }
        }

    }

    /**
     * 打印返回体
     * 
     * @param wrappedRequest
     */
    private void printResponse(ContentCachingResponseWrapper wrappedResponse) {
        try{
            ContentCachingResponseWrapper resp =  WebUtils.getNativeResponse(wrappedResponse, ContentCachingResponseWrapper.class);
            byte[] responseBody = resp.getContentAsByteArray();
            String body = new String(responseBody, resp.getCharacterEncoding());
            //TODO 打印

        }catch (Exception e) {
            // TODO: handle exception
        }

    }

    /**
     * 打印请求体
     * 
     * @param wrappedRequest
     */
    private void printRequest(ContentCachingRequestWrapper wrappedRequest) {
        try{
            ContentCachingRequestWrapper req =  WebUtils.getNativeRequest(wrappedRequest, ContentCachingRequestWrapper.class);
            byte[] reqestBody = req.getContentAsByteArray();
            String body = new String(reqestBody, req.getCharacterEncoding());
            //TODO 打印

        }catch (Exception e) {
            // TODO: handle exception
        }
    }

    /**
     * 打印参数
     * 
     * @param request
     */
    private void printParameter(HttpServletRequest request) {
        // TODO Auto-generated method stub

    }

    /**
     * 打印cookie
     * 
     * @param request
     */
    private void printCookie(HttpServletRequest request) {
        // TODO Auto-generated method stub

    }

    /**
     * 打印请求头
     * 
     * @param request
     */
    private void printHender(HttpServletRequest request) {
        // TODO Auto-generated method stub
        
    }

}

喜欢,在看

### 如何在Spring Boot中使用拦截器获取HTTP响应体 为了实现这一功能,在定义自定义拦截器时,可以继承`HandlerInterceptorAdapter`并重写相应方法。由于默认情况下无法直接访问到HttpServletResponse中的body部分,因此需要通过一些额外的方式处理。 创建一个实现了`Filter`接口的过滤器来读取原始请求响应流,并将其包装成可缓冲的形式以便后续能够多次读取。下面是一个简单的例子: ```java import org.springframework.stereotype.Component; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.*; import java.util.stream.Collectors; @Component public class CustomResponseLoggingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // Wrap the original response object with our custom wrapper. ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(httpResponse); try { chain.doFilter(request, wrappedResponse); byte[] content = wrappedResponse.getContentAsByteArray(); String contentType = wrappedResponse.getContentType(); if(content != null && contentType!=null){ System.out.println("Response Body: " + new String(content,0,content.length,"UTF-8")); // Copy cached data back into stream before sending it out. wrappedResponse.copyBodyToResponse(); } } finally { wrappedResponse.delete(); // Clean up resources held by this wrapper. } } @Override public void destroy() {} } ``` 上述代码展示了如何利用`ContentCachingResponseWrapper`捕获响应内容[^1]。需要注意的是,这里并没有直接操作`HttpServletResponse`对象而是选择了其封装版本——`ContentCachingResponseWrapper`,这使得可以在不影响正常流程的前提下安全地缓存响应数据供之后分析或记录之用。 另外还需要配置该过滤器使其生效,可以通过Java Config方式完成注册工作: ```java @Configuration public class WebMvcConfigurer extends DelegatingWebMvcConfiguration { private final List<Filter> filters; public WebMvcConfigurer(List<Filter> filters) { this.filters = filters; } @Bean public FilterRegistrationBean<CustomResponseLoggingFilter> loggingFilter(){ FilterRegistrationBean<CustomResponseLoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CustomResponseLoggingFilter()); registrationBean.addUrlPatterns("/api/*"); // Specify URL patterns here. return registrationBean; } ... } ``` 此段配置指定了哪些路径下的API调用会被这个新的过滤器所影响。对于更复杂的场景可能还需要考虑线程安全性等问题以及性能开销的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BUG弄潮儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值