SpringBoot-打印请求的入参和出参
作者:mmseoamin日期:2024-04-29

SpringBoot-打印请求的入参和出参

明确问题点

  • 在Controller层接受参数分为两种情况

    情况一:接口使用 @RequestParam 接收参数

    情况二:接口使用 @RequestBody 接收参数

    • 针对情况一,代码写起来就非常简单了,我们只需要在拦截器中通过request.getParameterMap() 来获得全部 Parameter 参数就可以了

    • 但是当接口使用 @RequestBody 接收参数时,我们在拦截器中使用同样的方法获取参数,就会出现流已关闭的异常,也就导致参数读取失败了 … 这是因为 Spring 已经对 @RequestBody 提前进行处理,而 HttpServletReqeust 获取输入流时仅允许读取一次,所以会报java.io.IOException: Stream closed。

    • 解决方法就是获取HttpServletReqeust数据将它保存下来

      具体代码如下:

      import org.springframework.stereotype.Component;
      import javax.servlet.*;
      import javax.servlet.annotation.WebFilter;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletRequestWrapper;
      import java.io.BufferedReader;
      import java.io.ByteArrayInputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.nio.charset.StandardCharsets;
      /**
       * HttpServletRequest 过滤器
       * 解决: request.getInputStream()只能读取一次的问题
       * 目标: 流可重复读
       */
      @Component
      @WebFilter(filterName = "HttpServletRequestFilter",urlPatterns = "/")
      public class HttpServletRequestFilter implements Filter {
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              ServletRequest requestWrapper = null;
              if(request instanceof HttpServletRequest) {
                  requestWrapper = new RequestWrapper((HttpServletRequest) request);
              }
              //获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新 request对象中
              // 在chain.doFiler方法中传递新的request对象
              if(null == requestWrapper) {
                   chain.doFilter(request, response);
              } else {
                  chain.doFilter(requestWrapper, response);
              }
          }
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              Filter.super.init(filterConfig);
          }
          @Override
          public void destroy() {
              Filter.super.destroy();
          }
          /***
           * HttpServletRequest 包装器
           * 解决: request.getInputStream()只能读取一次的问题
           * 目标: 流可重复读
           */
          public class RequestWrapper extends HttpServletRequestWrapper {
              /**
               * 请求体
               */
              private String body;
              public RequestWrapper(HttpServletRequest request) {
                  super(request);
                  body=getBody(request);
              }
              /**
               * 获取请求体
               * @param request 请求
               * @return 请求体
               */
              private String getBody(HttpServletRequest request) {
                  return HttpContextUtils.getBodyString(request);
              }
              /**
               * 获取请求体
               * @return 请求体
               */
              public String getBody() {
                  return body;
              }
              @Override
              public BufferedReader getReader() throws IOException {
                  return new BufferedReader(new InputStreamReader(getInputStream()));
              }
              @Override
              public ServletInputStream getInputStream() throws IOException {
                  // 创建字节数组输入流
                  final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
                  return new ServletInputStream() {
                      @Override
                      public boolean isFinished() {
                          return false;
                      }
                      @Override
                      public boolean isReady() {
                          return false;
                      }
                      @Override
                      public void setReadListener(ReadListener readListener) {
                      }
                      @Override
                      public int read() throws IOException {
                          return bais.read();
                      }
                  };
              }
          }
      }
      
      import javax.servlet.ServletRequest;
      import javax.servlet.http.HttpServletRequest;
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.nio.charset.StandardCharsets;
      import java.util.Enumeration;
      import java.util.HashMap;
      import java.util.Map;
      public class HttpContextUtils {
          /**
           * 获取query参数
           * @param request
           * @return
           */
          public static Map getParameterMapAll(HttpServletRequest request) {
              Enumeration parameters = request.getParameterNames();
              Map params = new HashMap<>();
              while (parameters.hasMoreElements()) {
                  String parameter = parameters.nextElement();
                  String value = request.getParameter(parameter);
                  params.put(parameter, value);
              }
              return params;
          }
          /**
           * 获取请求Body
           *
           * @param request
           * @return
           */
          public static String getBodyString(ServletRequest request) {
              StringBuilder sb = new StringBuilder();
              InputStream inputStream = null;
              BufferedReader reader = null;
              try {
                  inputStream = request.getInputStream();
                  reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                  String line = "";
                  while ((line = reader.readLine()) != null) {
                      sb.append(line);
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  if (inputStream != null) {
                      try {
                          inputStream.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  if (reader != null) {
                      try {
                          reader.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
              return sb.toString();
          }
      }
      

      过滤器执行之后进入到拦截器。然后拦截器根据request类型不同,解析方式也不同

      import com.alibaba.fastjson.JSON;
      import com.alibaba.fastjson.JSONArray;
      import com.alibaba.nacos.common.utils.MapUtils;
      import com.alibaba.nacos.common.utils.StringUtils;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Component;
      import org.springframework.web.servlet.HandlerInterceptor;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.util.Map;
      import java.util.Set;
      @Component
      @Slf4j
      public class LogPrintInterceptor implements HandlerInterceptor {
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              String requestURI=request.getRequestURI();
              StringBuffer buffer=new StringBuffer();
              if(request instanceof HttpServletRequestFilter.RequestWrapper){
                  buffer.append(HttpContextUtils.getBodyString(request));
              }
              if(StringUtils.isEmpty(buffer.toString())){
                  Map parameterMap = request.getParameterMap();
                  try {
                      if (MapUtils.isNotEmpty(parameterMap)) {
                          Set keySet = parameterMap.keySet();
                          for (String key : keySet) {
                              Object values = parameterMap.get(key);
                              try {
                                  JSONArray jsonarray = JSON.parseArray(JSON.toJSONString(values));
                                  buffer.append(key + "->" + jsonarray + "; ");
                              } catch (Exception e) {
                                  buffer.append(key + "->" + values + "; ");
                              }
                          }
                      }
                  } catch (Exception e) {
                      log.error("LogPrintInterceptor print error", e);
                  }
              }
              log.info("LogPrintInterceptor preHandle print log,requestURI={},params=[{}]",requestURI, buffer);
              return true;
          }
      }
      

      打印返回参数采用继承ResponseBodyAdvice的方式来实现

      import com.alibaba.fastjson.JSON;
      import com.techwolf.samplebase.common.resp.ApiResp;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.core.MethodParameter;
      import org.springframework.http.MediaType;
      import org.springframework.http.converter.HttpMessageConverter;
      import org.springframework.http.server.ServerHttpRequest;
      import org.springframework.http.server.ServerHttpResponse;
      import org.springframework.http.server.ServletServerHttpRequest;
      import org.springframework.http.server.ServletServerHttpResponse;
      import org.springframework.web.bind.annotation.ControllerAdvice;
      import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      @ControllerAdvice
      @Slf4j
      public class InterceptResponse implements ResponseBodyAdvice {
          @Override
          public boolean supports(MethodParameter returnType, Class> converterType) {
              return true;
          }
          @Override
          public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
              ServletServerHttpResponse responseTemp = (ServletServerHttpResponse) response;
              HttpServletResponse resp = responseTemp.getServletResponse();
              ServletServerHttpRequest sshr = (ServletServerHttpRequest) request;
              HttpServletRequest req = sshr.getServletRequest();
              String requestURI=req.getRequestURI();
              if(body instanceof ApiResp){
                  ApiResp apiResp=(ApiResp) body;
                  if(apiResp!=null){
                      log.info("InterceptResponse beforeBodyWrite print log,requestURI={},result={}",requestURI, JSON.toJSONString(body));
                  }
              }
              return body;
          }
      }
       
      

      在AdminMvcConfig可以配置多个拦截器

      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.config.annotation.EnableWebMvc;
      import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      @Configuration
      @EnableWebMvc
      public class AdminMvcConfig implements WebMvcConfigurer {
          @Autowired
          private LogPrintInterceptor logPrintInterceptor;
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
             registry.addInterceptor(logPrintInterceptor).addPathPatterns("/api/**");
          }
      }