加打印語句,將請求參數打印出來。後面想一想,之後可能還會遇到這樣的狀況,若是每次遇到,我都去對應的方法中加日誌打印,就變成重複工做。而且日誌打印跟咱們的業務自己沒有任何關係。json
記錄日誌網上主要有三種方法:數組
我選擇了filter。爲何選擇它,由於我以爲它相對於定義切點,而後切點先後處理來講,更加方便;相對於 interceptor, 我更加熟悉這種方式。緩存
定義一個 LogFilter 。 裏面對 HttpServletRequest 進行攔截,根據對應的 content-type 解析請求參數。主要代碼以下app
/** * 功能描述: 打印請求參數 * @author lkb * @date 2020/5/6 * @param * @return */ @Slf4j public class LogFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //日誌 doLog(request, response); // 將request 傳到下一個Filter filterChain.doFilter(request, response); } private void doLog(HttpServletRequest request, HttpServletResponse response){ // 輸出請求體 log.info("request. uri = {}, method = {}, requestParam = {}", request.getRequestURI(), request.getMethod(), getRequestParam(request)); //todo 返回結果也能夠進行處理 } private String getRequestParam(HttpServletRequest request){ String requestParam = ""; String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE); try { if(StringUtils.isNotEmpty(requestContentType)){ if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE) || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) { // xml json requestParam = getRequestBody(request); } else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) { // 文件表單提交 requestParam = getFormParam(request); }else if(requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)){ // 普通表單提交 requestParam = toJson(request.getParameterMap()); } }else{ // 默認普通表單提交 requestParam = toJson(request.getParameterMap()); } }catch (Exception e){ log.error("getRequestParam error"); log.error(e.getMessage(),e); } return requestParam; } ... }
而後,註冊這個filteride
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean logFilter() { final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); final LogFilter logFilter = new LogFilter(); filterRegistrationBean.setFilter(logFilter); return filterRegistrationBean; } }
上面兩步以後,重啓項目,能夠看到請求過來後會打印出請求的uri、method、param 。this
原本覺得這樣就萬事大吉了。可是事實並非如此。加上上面代碼後會發現,再controller 加上的 @requestBody 沒有效果,取不到任何數據,並拋出異常,告訴咱們請求已經被讀取過。 爲何呢?spa
緣由很簡單。由於在 doLog 中獲取請求參數的時候,咱們已經將請求的 inputStream 給讀取了。讀取inputStream 時有一個offset,它表示你從哪裏開始讀取輸入流。由於咱們讀取了一遍 inputStream,因此offset已經在流的最末端了。咱們再去讀取,就會發現沒有東西能夠讀了。若是想重複讀取 inputStream 就須要每次讀取後重置 offset 的值。日誌
固然爲了方便,我並無去從新inputStream 中的reset 方法。而是選擇,在讀取請求後,將請求緩存起來。code
首先,BufferedServletInputStream 繼承自 ServletInputStream。orm
public class BufferedServletInputStream extends ServletInputStream { private ByteArrayInputStream inputStream; public BufferedServletInputStream(byte[] buffer) { this.inputStream = new ByteArrayInputStream( buffer ); } @Override public int available(){ return inputStream.available(); } @Override public int read(){ return inputStream.read(); } @Override public int readLine(byte[] b, int off, int len){ return inputStream.read( b, off, len ); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }
而後,BufferedServletRequestWrapper 繼承 HttpServletRequestWrapper。
@Slf4j public class BufferedServletRequestWrapper extends HttpServletRequestWrapper { private byte[] buffer; public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); InputStream is = request.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buff[] = new byte[1024]; int read; while ((read = is.read(buff)) > 0) { baos.write(buff, 0, read); } this.buffer = baos.toByteArray(); } @Override public ServletInputStream getInputStream() { return new BufferedServletInputStream(this.buffer); } }
裏面使用一個 byte[] buffer 數組將請求緩存起來。
最後,在 LogFilter 中 doLog 前,對請求進行包裝。
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 備份HttpServletRequest request = new BufferedServletRequestWrapper(request); //日誌 doLog(request, response); // 將request 傳到下一個Filter filterChain.doFilter(request, response); }
通過上訴處理,咱們就能夠愉快地用日誌記錄請求參數了。
最後總結一下:
1. 記錄請求參很多天志的方式最好採用切面的思想
2. inputStream 默認只能讀取一次,屢次讀取要從新處理inputStream
3. @requestBody 的原理能夠了解一下