SpringMVC打印请求参数和响应数据最优方案

 更新时间:2023年07月19日 11:31:04   作者:brucelwl  
项目中经常需要打印http请求的参数和响应数据,本文给大家讲解如何在SpringMVC打印请求参数和响应数据最优方案,感兴趣的朋友跟随小编一起看看吧

项目中经常需要打印http请求的参数和响应数据, 但会出现如下问题:

  • 打印Request Body参数: 请求content-typeapplication/josn格式,如果直接从HttpServletRequest中获取输入流, 解析请求数据, 会导致SpringMVC后续的请求异常, 读取输入流异常.
  • 打印响应数据: 如果直接从HttpServletResponse中获取输出流, 解析响应数据, 会导致Web服务器(Tomcat)后续响应请求异常.

本文讲解如何在SpringBoot中使用最优方案实现该功能.

常见方案(非最优方案)

常见方案就是通过实现javax.servlet.FilterHttpServletRequestHttpServletResponse进行包装, 将输入/输出流复制一份, 转成字符串打印, 并且不影响后续处理.而且SpringMVC已经为我们提供了相应的包装类实现

  • org.springframework.web.util.ContentCachingRequestWrapper
  • org.springframework.web.util.ContentCachingResponseWrapper

但在使用ContentCachingResponseWrapper时, 一定要记住必须调用ContentCachingResponseWrapper#copyBodyToResponse()将响应数据写回HttpServletResponseServletOutputStream,这样才能成功返回数据到客户端。

注意: 这个并不是最优方案,并且存在诸多缺点

示例代码如下:

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (isStream(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;
        ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response);
        if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request);
        }
        try {
            filterChain.doFilter(requestToUse, responseToUse);
        } finally {
            accessLog(requestToUse, responseToUse);
            responseToUse.copyBodyToResponse();
        }
}
 private boolean isStream(HttpServletRequest request) {
        return MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.ACCEPT))
                || MediaType.TEXT_EVENT_STREAM_VALUE.equalsIgnoreCase(request.getHeader(HttpHeaders.CONTENT_TYPE));
    }

这个方案存在如下几个缺点:

  • 使用包装类包装HttpServletRequestHttpServletResponse会导致输入流和输出流被多拷贝一次
  • 并不是所有的请求类型都适合对HttpServletResponse包装, 即使用Spring提供的ContentCachingResponseWrapper也无法实现, 例如SpringMVC所支持的SSE(org.springframework.web.servlet.mvc.method.annotation.SseEmitter)请求,会导致无法向客户端相应数据. 需要对text/event-stream请求做特殊处理.

SpringWeb 5.1.14版本中ShallowEtagHeaderFilter#disableContentCaching方法的注释中已经说明

/**
	 * This method can be used to disable the content caching response wrapper
	 * of the ShallowEtagHeaderFilter. This can be done before the start of HTTP
	 * streaming for example where the response will be written to asynchronously
	 * and not in the context of a Servlet container thread.
	 * @since 4.2
	 */
	public static void disableContentCaching(ServletRequest request) {
		Assert.notNull(request, "ServletRequest must not be null");
		request.setAttribute(STREAMING_ATTRIBUTE, true);
	}

不方便获取Request Body对应的实体类类型, 不方便知道响应请求的实体类类型

最优方案

实际上SpringMVC提供了如下两个接口,用于在解析Request Body后和响应请求前回调

  • org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceRequestBodyAdvice用于解析Request Body后回调, 可以实现该接口得到解析后的Body实体类,RequestBodyAdviceAdapter适配了RequestBodyAdvice
  • org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdviceResponseBodyAdvice用于在向http请求响应数据之前回调, 可以实现该接口得到响应的实体类

为了能够一块打印请求和响应数据, 必须在请求时记录Request Body对象, 这里就考虑使用HttpServletRequestsetAttribute记录,但是RequestBodyAdvice中无法拿到HttpServletRequest,因此需要在请求时通过ThreadLocal绑定. 见示例ThreadBindRequestContainer

@Slf4j
@ControllerAdvice
public class RequestResponseBodyAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        HttpServletRequest request = ThreadBindRequestContainer.getServletRequest();
        Method method = parameter.getMethod();
        String declaringClass = method.getDeclaringClass().getSimpleName();
        String handlerMethod = method.toString().substring(method.toString().indexOf(declaringClass));
        request.setAttribute("handlerMethod", handlerMethod);
        request.setAttribute("req_body", body);
        return body;
    }
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
        servletRequest.setAttribute("resp_body", body);
        return body;
    }
}
public class ThreadBindRequestContainer {
    private static final ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();
    public static void bind(HttpServletRequest request) {
        threadLocal.set(request);
    }
    public static void remove() {
        threadLocal.remove();
    }
    public static HttpServletRequest getServletRequest() {
        return threadLocal.get();
    }
}
@Slf4j
public class RequestResponseBodyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ThreadBindRequestContainer.bind(request);
        log.info("请求线程" + Thread.currentThread().getName());
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
    @Override
    public void afterCompletion(HttpServletRequest req, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadBindRequestContainer.remove();
        String requestURI = req.getRequestURI();
        Object req_body = req.getAttribute("req_body");
        Object resp_body = req.getAttribute("resp_body");
        Object handlerMethod = req.getAttribute("handlerMethod");
        log.info("请求url:{},处理方法:{}, 参数:{}, 响应参数:{}", requestURI, handlerMethod, req_body, resp_body);
        log.info("退出线程" + Thread.currentThread().getName() + "\n");
    }
}

到此这篇关于SpringMVC打印请求参数和响应数据最优方案的文章就介绍到这了,更多相关SpringMVC打印请求参数和响应数据内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring MVC异常处理机制示例详解

    Spring MVC异常处理机制示例详解

    这篇文章主要给大家介绍了关于Spring MVC异常处理机制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring MVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-11-11
  • java实现员工工资管理系统

    java实现员工工资管理系统

    这篇文章主要为大家详细介绍了java实现员工工资管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Java中JavaBean对象和Map的互相转换方法实例

    Java中JavaBean对象和Map的互相转换方法实例

    为什么需要将javaBean和map进行转换,在很多应用场景中,需要将key=value形式的数据与javaBean对象相互转换,下面这篇文章主要给大家介绍了关于Java中JavaBean对象和Map的互相转换的相关资料,需要的朋友可以参考下
    2022-11-11
  • 你一定不知道的Java Unsafe用法详解

    你一定不知道的Java Unsafe用法详解

    Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,下面这篇文章主要给大家介绍了关于Java Unsafe用法的相关资料,需要的朋友可以参考下
    2021-10-10
  • 通过实例学习Spring @Required注释原理

    通过实例学习Spring @Required注释原理

    这篇文章主要介绍了通过实例学习Spring @Required注释原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 如何修改json字符串中某个key对应的value值

    如何修改json字符串中某个key对应的value值

    这篇文章主要介绍了如何修改json字符串中某个key对应的value值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • maven多个仓库查询的优先级顺序案例讲解

    maven多个仓库查询的优先级顺序案例讲解

    这篇文章主要介绍了maven多个仓库查询的优先级顺序,考虑到我们常用的配置文件是conf/settings.xml和工程里面的pom.xml文件,我们针对这两个文件的结合来分析仓库的使用顺序,需要的朋友可以参考下
    2023-04-04
  • Java中的length和length()深入分析

    Java中的length和length()深入分析

    java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性。java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法。这篇文章将介绍几个关于Java数组的关键概念。
    2016-11-11
  • Java中Integer类型值相等判断方法

    Java中Integer类型值相等判断方法

    这篇文章主要给大家介绍了关于Java中Integer类型值相等判断的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 国内分布式框架Dubbo使用详解

    国内分布式框架Dubbo使用详解

    这篇文章主要为大家介绍了国内分布式框架Dubbo使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03

最新评论