SpringBoot專案攔截器中通過流獲取Request請求物件中的引數後,控制器中@RequestBody註解引數獲取不到
阿新 • • 發佈:2018-12-10
一、場景
第一次搭建專案開發環境,需要在專案中實現日誌攔截器,用來獲取使用者請求引數日誌,以便在後期維護中出現BUG時能夠快速定位錯誤發生的場景。請求引數一般通過GET和POST方式進行傳遞,GET請求引數獲取通過request.getParameterMap()獲取。而POST請求引數則採用request.getInputStream()獲取。
二、錯誤
在實現中發現如果通過流獲取引數,控制器中@RequestBody註解引數則獲取不到。檢視資料後,瞭解到spring中request.getInputStream() 和request.getReader()只能被獲取一次,而@RequestBody註解引數的底層實現也是通過流來獲取請求引數的。因此才出現了攔截器中通過流獲取引數後,控制器中獲取引數報錯。
三、解決方案
1、日誌攔截器類
package org.cm.channelmanage.web.interceptor; import org.cm.channelmanage.web.handler.BodyReaderHttpServletRequestWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Map; /** * 日誌攔截器 * author:xiaofan * date:2018-08-15 10:26 */ @Component public class LogInterceptor extends HandlerInterceptorAdapter { private Logger logger = LoggerFactory.getLogger(LogInterceptor.class); private String getParamString(Map<String, String[]> map) { StringBuffer sb = new StringBuffer(); for (Map.Entry<String, String[]> e : map.entrySet()) { sb.append(e.getKey()).append("="); String[] value = e.getValue(); if (value != null && value.length == 1) { sb.append(value[0]).append("\t"); } else { sb.append(Arrays.toString(value)).append("\t"); } } return sb.toString(); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { Long startTime = System.currentTimeMillis(); request.setAttribute("startTime",startTime); try { if(handler instanceof HandlerMethod){ StringBuilder sb = new StringBuilder(); sb.append("----------------------開始時間:") .append(new SimpleDateFormat("hh:mm:ss.SSS") .format(startTime)+"").append("------------\n"); sb.append("Controller:").append(((HandlerMethod) handler).getBean().getClass().getName()).append("\n"); sb.append("Method:").append(((HandlerMethod) handler).getMethod().getName()).append("\n"); sb.append("RequestMethod:").append(request.getMethod()).append("\n"); //通過輸入流獲取POST請求中的引數 sb.append("Params:").append(new BodyReaderHttpServletRequestWrapper(request).getBodyString()).append("\n"); sb.append("Params:").append(getParamString(request.getParameterMap())).append("\n"); sb.append("URL:").append(request.getRequestURL()).append("\n"); sb.append("SessionID:").append(request.getSession().getId()).append("\n"); sb.append("ApplyID:").append(request.getSession().getAttribute("applyId")).append("\n"); sb.append("OpenID:").append(request.getSession().getAttribute("openid")).append("\n"); logger.info(sb.toString()); } } catch (Exception e) { e.printStackTrace(); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){ Long startTime = (Long)request.getAttribute("startTime"); Long endTime = System.currentTimeMillis(); Long costTime = endTime - startTime; if(handler instanceof HandlerMethod){ StringBuilder sb = new StringBuilder(); sb.append("CostTime:").append(costTime).append("ms"); logger.info(sb.toString()); } } }
2、自定義一個HttpServletRequestWrapper子類,用來封裝HttpServletRequest請求
package org.cm.channelmanage.web.handler; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; /** * Request請求引數獲取處理類 * author:XiaoFan * 2018/09/18 */ public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); String sessionStream = getBodyString(request); body = sessionStream.getBytes(Charset.forName("UTF-8")); } public String getBodyString(){ return new String(body,Charset.forName("UTF-8")); } /** * 獲取請求Body * * @param request * @return */ private String getBodyString(final ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = cloneInputStream(request.getInputStream()); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("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(); } /** * Description: 複製輸入流</br> * * @param inputStream * @return</br> */ public InputStream cloneInputStream(ServletInputStream inputStream) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; try { while ((len = inputStream.read(buffer)) > -1) { byteArrayOutputStream.write(buffer, 0, len); } byteArrayOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); return byteArrayInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
3、自定義一個過濾器將自定義的請求封裝類傳下去
package org.cm.channelmanage.web.filter;
import org.cm.channelmanage.web.handler.BodyReaderHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(ChannelFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
logger.info("================進入過濾器======================");
// 防止流讀取一次後就沒有了, 所以需要將流繼續寫出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
}
}