SpringMVC框架中使用Filter實(shí)現(xiàn)請(qǐng)求日志打印方式
之前利用HttpServletRequest.getInputStream()和RequestWrapper實(shí)現(xiàn)了請(qǐng)求的requestBody獲取,現(xiàn)在提出將一個(gè)請(qǐng)求的RequestBody和ResponseBody都提出來(lái)并打印日志&落入數(shù)據(jù)庫(kù),以便統(tǒng)計(jì)和查找問(wèn)題。
查找資料后確定兩種技術(shù)方案
1. 使用AOP對(duì)所有Controller的方法進(jìn)行環(huán)繞通知處理;
2. 使用Filter攔截所有的Request和Response,并獲取body。
最后選擇了第二種方式,具體實(shí)現(xiàn)記錄如下。
具體實(shí)現(xiàn)
日志記錄過(guò)濾器
public class RequestFilter implements Filter{ private static final String LOG_FORMATTER_IN = "請(qǐng)求路徑:{%s},請(qǐng)求方法:{%s},參數(shù):{%s},來(lái)源IP:{%s},請(qǐng)求開(kāi)始時(shí)間{%s},返回:{%s},請(qǐng)求結(jié)束時(shí)間{%s},用時(shí):{%s}ms,操作類(lèi)型:{%s},操作人:{%s}"; public static final String USER_TOKEN_REDIS_PREFIX = "token_prefix"; private static final Logger log = LoggerFactory.getLogger(RequestFilter.class); //request攔截的conten-type列表 private List<String> contentTypes; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; //請(qǐng)求路徑 String path = httpServletRequest.getRequestURI(); String method = httpServletRequest.getMethod(); //所有請(qǐng)求參數(shù)的Map Map<String,String> paramMap = new HashMap<>(); //請(qǐng)求的真實(shí)IP String requestedIP = RequestUtils.getRealIP(httpServletRequest); //是否攔截并包裝請(qǐng)求,如果需要攔截則會(huì)獲取RequestBody,一般為application/json才攔截 boolean filterRequestFlag = checkFilter(request.getContentType()); if (filterRequestFlag) { httpServletRequest = new MyRequestBodyReaderWrapper(httpServletRequest); } //獲取所有queryString和requestBody Map<String, String> requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest); if (requestParamMap != null && !requestParamMap.isEmpty()){ paramMap.putAll(requestParamMap); } //獲取header參數(shù) Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest); if (headerMap != null && !headerMap.isEmpty()){ paramMap.putAll(headerMap); } //獲取路徑參數(shù) Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest); if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){ paramMap.putAll(uriTemplateMap); } //包裝Response,重寫(xiě)getOutputStream()和getWriter()方法,并用自定義的OutputStream和Writer來(lái)攔截和保存ResponseBody MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse); //請(qǐng)求開(kāi)始時(shí)間 Long dateStart = System.currentTimeMillis(); //Spring通過(guò)DispatchServlet處理請(qǐng)求 chain.doFilter(httpServletRequest, responseWrapper); //請(qǐng)求結(jié)束時(shí)間 Long dateEnd = System.currentTimeMillis(); String responseBody; if (responseWrapper.getMyOutputStream() == null){ if (responseWrapper.getMyWriter() != null){ responseBody = responseWrapper.getMyWriter().getContent(); //一定要flush,responseBody會(huì)被復(fù)用 responseWrapper.getMyWriter().myFlush(); } }else { responseBody = responseWrapper.getMyOutputStream().getBuffer(); //一定要flush,responseBody會(huì)被復(fù)用 responseWrapper.getMyOutputStream().myFlush(); } String params = JSONObject.toJSONString(paramMap); log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart)); } /** * 判斷請(qǐng)求/返回是否為application/json * 是則進(jìn)行攔截, * 否則退出 * @param contentType 請(qǐng)求/響應(yīng)類(lèi)型 */ private boolean checkFilter(String contentType) { boolean filterFlag = false;//是否繼續(xù)攔截 for (String p : getContentTypes()) { if (StringUtils.contains(contentType, p)){ filterFlag = true; } } if (StringUtils.isEmpty(contentType)){ filterFlag = true; } return filterFlag; } }
Request包裝器
/** * HttpServletRequest的包裝器,為了在攔截器階段獲取requestBody且不妨礙SpringMVC再次獲取requestBody */ @Slf4j public class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper { //存放JSON數(shù)據(jù)主體 private final byte[] body; public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException { super(request); body = getBody(request).getBytes(Charset.forName("UTF-8")); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } /** * 獲取請(qǐng)求Body */ public static String getBody(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = 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) { log.error("MyRequestBodyReaderWrapper.getBody()異常-->",e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.error("MyRequestBodyReaderWrapper.getBody()異常-->",e); } } if (reader != null) { try { reader.close(); } catch (IOException e) { log.error("MyRequestBodyReaderWrapper.getBody()異常-->",e); } } } return sb.toString(); } }
RequestUtils
/** * 請(qǐng)求工具類(lèi) */ public class RequestUtils { private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class); /** * 獲取所有的請(qǐng)求頭 * @param request * @return */ public static Map<String,String> getHeaders(HttpServletRequest request){ Map<String,String> headerMap = new HashMap<>(); List<String> headers = getCommonHeaders(); headers.add("Postman-Token"); headers.add("Proxy-Connection"); headers.add("X-Lantern-Version"); headers.add("Cookie"); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); if (headers.contains(headerName)){ continue; } headerMap.put(headerName,request.getHeader(headerName)); } return headerMap; } /** * 獲取請(qǐng)求的路徑參數(shù) * @param request * @return */ public static Map<String, String> getUriTemplateVar(HttpServletRequest request) { NativeWebRequest webRequest = new ServletWebRequest(request); Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return uriTemplateVars; } /** * 獲取請(qǐng)求的真實(shí)IP * @param request * @return */ public static String getRealIP(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { //多次反向代理后會(huì)有多個(gè)ip值,第一個(gè)ip才是真實(shí)ip int index = ip.indexOf(","); if (index != -1) { return ip.substring(0, index); } else { return ip; } } ip = request.getHeader("X-Real-IP"); if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { return ip; } return request.getRemoteAddr(); } /** * 從Request中獲取所有的請(qǐng)求參數(shù),包括GET/POST/PATCH等請(qǐng)求,不包括路徑參數(shù) * @param request * @return */ public static Map<String,String> getRequestParamMap(HttpServletRequest request) { Map<String,String> paramMap = new HashMap<>(); //獲取QueryString中的參數(shù),GET方式 或application/x-www-form-urlencoded Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request); if (queryParamMap != null){ paramMap.putAll(queryParamMap); } //獲取Body中的參數(shù),POST/PATCH等方式,application/json Map<String,String> bodyParamMap = null; try { //當(dāng)為POST請(qǐng)求且 application/json時(shí),request被RequestFilter處理為wrapper類(lèi) if (!(request instanceof MyRequestBodyReaderWrapper)){ return paramMap; } MyRequestBodyReaderWrapper readerWrapper = (MyRequestBodyReaderWrapper) request; String requestBody = new String(readerWrapper.getBody(), "UTF-8"); if (com.zhongan.health.common.utils.StringUtils.isNotBlank(requestBody)){ /** * 該方法為了避免 fastJson在 反序列化多層json時(shí),改變對(duì)象順序 */ bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField); } } catch (Exception e) { logger.error("獲取請(qǐng)求Body異常-->",e); } if (bodyParamMap != null){ paramMap.putAll(bodyParamMap); } return paramMap; } private static List<String> getCommonHeaders(){ List<String> headers = new ArrayList<>(); Class<HttpHeaders> clazz = HttpHeaders.class; Field[] fields = clazz.getFields(); for (Field field : fields) { field.setAccessible(true); if (field.getType().toString().endsWith("java.lang.String") && Modifier.isStatic(field.getModifiers())){ try { headers.add((String) field.get(HttpHeaders.class)); } catch (IllegalAccessException e) { logger.error("反射獲取屬性值異常-->",e); } } } return headers; } }
Response包裝器
/** *該包裝器主要是重寫(xiě)getOutputStream()和getWriter()方法,給調(diào)用者返回自定義的OutputStream和Writer,以便參與輸出的過(guò)程并記錄保存responseBody。 */ public class MyResponseWrapper extends HttpServletResponseWrapper { private ResponsePrintWriter writer; private MyServletOutputStream out; public MyResponseWrapper(HttpServletResponse response) { super(response); } @Override public ServletOutputStream getOutputStream() throws IOException { //一定要先判斷當(dāng)前out為空才能去新建out對(duì)象,否則一次請(qǐng)求會(huì)出現(xiàn)多個(gè)out對(duì)象 if (out == null){ out = new MyServletOutputStream(super.getOutputStream()); } return out; } @Override public PrintWriter getWriter() throws IOException { //一定要先判斷當(dāng)前writer為空才能去新建writer對(duì)象,否則一次請(qǐng)求會(huì)出現(xiàn)多個(gè)writer對(duì)象 if (writer == null){ writer = new ResponsePrintWriter(super.getWriter()); } return writer; } public ResponsePrintWriter getMyWriter() { return writer; } public MyServletOutputStream getMyOutputStream(){ return out; } }
自定義Writer
/** *自定義Writer,重寫(xiě)write方法,并記錄保存ResponseBody */ public class ResponsePrintWriter extends PrintWriter{ private StringBuffer buffer; public ResponsePrintWriter(PrintWriter out) { super(out); buffer = new StringBuffer(); } public String getContent(){ return buffer == null ? null : buffer.toString(); } @Override public void flush() { super.flush(); } //清空buffer,以便下一次重新使用 public void myFlush(){ buffer = null; } @Override public void write(char[] buf, int off, int len) { super.write(buf, off, len); char[] destination = new char[len]; System.arraycopy(buf,off,destination,0,len); buffer.append(destination); } @Override public void write(String s) { super.write(s); buffer.append(s); } }
自定義OutputStream
/** * 自定義輸出流包裝器,重寫(xiě)write方法,并記錄保存ResponseBody */ public class MyServletOutputStream extends ServletOutputStream { private ServletOutputStream outputStream; private StringBuffer buffer; public MyServletOutputStream(ServletOutputStream outputStream) { this.outputStream = outputStream; buffer = new StringBuffer(); } @Override public void write(int b) throws IOException { outputStream.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { outputStream.write(b, off, len); byte[] bytes = new byte[len]; System.arraycopy(b, off, bytes, 0, len); buffer.append(new String(bytes,"UTF-8")); } @Override public void write(byte[] b) throws IOException { outputStream.write(b); } @Override public void flush() throws IOException { super.flush(); } //清空buffer,以便下一次重新使用 public void myFlush(){ outputStream = null; buffer = null; } public String getBuffer() { if (buffer != null){ return buffer.toString(); } return null; } }
總結(jié)一下
Request.getInputStream
一次請(qǐng)求中只能被調(diào)用一次;Response.getOutputStream()
無(wú)法獲取ResponseBody;Response
的輸出有兩種方式,都需要考慮到并重寫(xiě)
getOutputStream().write()
getWrite().write()
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

Spring Security之默認(rèn)的過(guò)濾器鏈及自定義Filter操作

java Spring松耦合高效應(yīng)用簡(jiǎn)單實(shí)例分析

spring中的注解@@Transactional失效的場(chǎng)景代碼演示

淺析Java中Apache BeanUtils和Spring BeanUtils的用法

Java之SpringBoot集成ActiveMQ消息中間件案例講解

在java List中進(jìn)行模糊查詢的實(shí)現(xiàn)方法

Springboot啟動(dòng)原理和自動(dòng)配置原理解析