轉載-Spring Boot之 Filter(示例:列印request、response日誌)
網上有很多采用spring filter機制列印request/response日誌的部落格, 大都不能很好工作, 下面這個部落格寫的不錯.
https://blog.csdn.net/jy02268879/article/details/84243950
作者用到了下面兩個第三方庫, 其中 apache lang3 的 StringUtils 可以使用 Hutool 庫代替. jodd 庫是一個非常優秀的工具包.
- import jodd.io.StreamUtil;
- import org.apache.commons.lang3.StringUtils;
pom.xml 引入對應的 jodd
<dependency> <groupId>org.jodd</groupId> <artifactId>jodd-core</artifactId> <version>${jodd.all.version}</version> </dependency>
下面內容摘自 https://blog.csdn.net/jy02268879/article/details/84243950
===========================================
Filter(過濾器)
===========================================
一個請求可以被多個過濾器攔截到,會依次進入各個Filter中,放行後直至進入Servlet,Servlet處理請求結束後,回到各個Filter繼續執行後面的程式碼,先執行的Filter,後執行完(Filter是個棧結構,先進後出)。
例如:這裡有5個filter: A,B,C,D,E
執行filter的前置處理的順利是A,B,C,D,E
那麼執行filter的後置處理的順序是E,D,C,B,A
一個請求進來以後的執行順序:
Filter前置處理---->Interceptor(攔截器)前置處理---->正常的controller處理---->Interceptor後置處理---->Filter後置處理
===========================================
一.用@WebFilter註冊過濾器
===========================================
---------------------------------------------------------
1.實現filter介面,或者繼承filter的實現類,
---------------------------------------------------------
RequestFilter.java 繼承OncePerRequestFilter確保一次請求只通過一次該filter。
換言之一次請求不會通過兩次RequestFilter,一次請求不會重複執行自定義RequestFilter中的doFilterInternal方法
package com.sid.util.LogRequestResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; /** * @program: springboot * @description: * @author: Sid * @date: 2018-11-19 09:21 * @since: 1.0 **/ @Order(0) /** * 註冊過濾器 * */ @WebFilter(filterName = "RequestResponseLogFilter", urlPatterns = "/*") public class RequestFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(RequestFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String path = request.getQueryString(); String servletPath = request.getServletPath(); String url = request.getRequestURI(); RequestWrapper requestWrapper = null; StringBuilder sb = new StringBuilder(); if (request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper(request); BufferedReader bufferedReader = requestWrapper.getReader(); String line; while ((line = bufferedReader.readLine()) != null) { sb.append(line); } } ResponseWrapper responseWrapper=new ResponseWrapper( response); if (null == requestWrapper) { filterChain.doFilter(request, response); } else { filterChain.doFilter(requestWrapper, responseWrapper); } logger.info("========================》 url:" + url + " & queryString:" + path+" & servletPath:"+servletPath); logger.info("========================》request uri: {}",request.getRequestURI()); logger.info("========================》request ContentType: {}",request.getContentType()); logger.info("========================》request param: {}",sb.toString()); logger.info("========================》response status: {}",response.getStatus()); logger.info("========================》response ContentType: {}",response.getContentType()); String result=new String(responseWrapper.getResponseData()); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(result.getBytes()); outputStream.flush(); outputStream.close(); // 列印response logger.info("========================》response return data: {} \t" + result); } }
---------------------------------------------------------
2.在spring-boot啟動類上加註解@ServletComponentScan
---------------------------------------------------------
@SpringBootApplication @ServletComponentScan public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
===========================================
二、用FilterRegistrationBean註冊過濾器
===========================================
RequestFilterConfiguration.java
package com.sid.util.LogRequestResponse; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @program: springboot * @description: * @author: Sid * @date: 2018-11-19 13:48 * @since: 1.0 **/ @Configuration public class RequestFilterConfiguration { @Bean public FilterRegistrationBean authFilterRegistrationBean() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RequestFilter()); //設定自定義的Filter registration.addUrlPatterns("/*"); //設定過濾路徑 registration.setName("RequestFilter"); //設定過濾器名稱 registration.setOrder(1); //設定過濾器順序 //registration.addInitParameter("paramName", "paramValue"); //設定初始化引數 這裡不用 return registration; } }
----------------------------------------------------
RequestWrapper的實現
----------------------------------------------------
package com.sid.util.LogRequestResponse; import jodd.io.StreamUtil; import org.apache.commons.lang3.StringUtils; import java.io.*; import java.nio.charset.Charset; import java.util.Enumeration; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * @program: springboot * @description: * @author: Sid * @date: 2018-11-19 12:54 * @since: 1.0 **/ public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; /** * 這個必須加,複製request中的bufferedReader中的值 * @param request * @throws IOException */ public RequestWrapper(HttpServletRequest request) throws IOException { super(request); body = getBodyString(request); } /** * 獲取請求Body * * @param request * @return */ public byte[] getBodyString(final ServletRequest request) throws IOException { String contentType = request.getContentType(); String bodyString =""; if (StringUtils.isNotBlank(contentType) && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"))){ Enumeration<String> pars=request.getParameterNames(); while(pars.hasMoreElements()){ String n=pars.nextElement(); bodyString+=n+"="+request.getParameter(n)+"&"; } bodyString=bodyString.endsWith("&")?bodyString.substring(0, bodyString.length()-1):bodyString; return bodyString.getBytes(Charset.forName("UTF-8")); }else { return StreamUtil.readBytes(request.getReader(), "UTF-8"); } } @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 boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } @Override public int read() throws IOException { return bais.read(); } }; } }
----------------------------------------------------
ResponseWrapper的實現
----------------------------------------------------
package com.sid.util.LogRequestResponse; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponseWrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; /** * @program: springboot * @description: * @author: Sid * @date: 2018-11-19 11:55 * @since: 1.0 **/ public class ResponseWrapper extends HttpServletResponseWrapper { /** * This class implements an output stream in which the data is written into a byte array. * The buffer automatically grows as data is written to it. The data can be retrieved using toByteArray() and toString(). Closing a ByteArrayOutputStream has no effect. The methods in this class can be called after the stream has been closed without generating an IOException. */ private ByteArrayOutputStream buffer = null;//輸出到byte array private ServletOutputStream out = null; private PrintWriter writer = null; public ResponseWrapper(HttpServletResponse resp) throws IOException { super(resp); buffer = new ByteArrayOutputStream();// 真正儲存資料的流 out = new WapperedOutputStream(buffer); writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding())); } /** 過載父類獲取outputstream的方法 */ @Override public ServletOutputStream getOutputStream() throws IOException { return out; } /** 過載父類獲取writer的方法 */ @Override public PrintWriter getWriter() throws UnsupportedEncodingException { return writer; } /** 過載父類獲取flushBuffer的方法 */ @Override public void flushBuffer() throws IOException { if (out != null) { out.flush(); } if (writer != null) { writer.flush(); } } @Override public void reset() { buffer.reset(); } /** 將out、writer中的資料強制輸出到WapperedResponse的buffer裡面,否則取不到資料 */ public byte[] getResponseData() throws IOException { flushBuffer(); return buffer.toByteArray(); } /** 內部類,對ServletOutputStream進行包裝 */ private class WapperedOutputStream extends ServletOutputStream { private ByteArrayOutputStream bos = null; public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException { bos = stream; } @Override public void write(int b) throws IOException { bos.write(b); } @Override public void write(byte[] b) throws IOException { bos.write(b, 0, b.length); } @Override public boolean isReady() { return false; } @Override public void setWriteListener(WriteListener listener) { } } }