在spring中,通常可以使用切面編程方式對web請求記錄操作日志。但是這種方式存在一個問題,那就是只能記錄url中的請求參數,無法記錄POST或者PUT請求的報文體,因為報文體是放在request對象的InputStream中的,只能讀取一次。解決方法就是利用HttpServletRequestWrapper先讀取InputStream,記錄到一個頭參數中,然后再重新放到InputStream中去。
代碼如下:
先創建一個WrappedHttpServletRequest類:
import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class WrappedHttpServletRequest extends HttpServletRequestWrapper { private byte[] bytes; private WrappedServletInputStream wrappedServletInputStream; public WrappedHttpServletRequest(HttpServletRequest request) throws IOException { super(request); // 讀取輸入流里的請求參數,並保存到bytes里 bytes = IOUtils.toByteArray(request.getInputStream()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); this.wrappedServletInputStream = new WrappedServletInputStream(byteArrayInputStream); // 很重要,把post參數重新寫入請求流 reWriteInputStream(); } /** * 把參數重新寫進請求里 */ public void reWriteInputStream() { wrappedServletInputStream .setStream(new ByteArrayInputStream(bytes != null ? bytes : new byte[0])); } @Override public ServletInputStream getInputStream() throws IOException { return wrappedServletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(wrappedServletInputStream)); } /** * 獲取post參數,可以自己再轉為相應格式 */ public String getRequestParams() throws IOException { return new String(bytes, this.getCharacterEncoding()); } private class WrappedServletInputStream extends ServletInputStream { public void setStream(InputStream stream) { this.stream = stream; } private InputStream stream; public WrappedServletInputStream(InputStream stream) { this.stream = stream; } @Override public int read() throws IOException { return stream.read(); } @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} } }
再創建一個LogFilter對象:
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Component @WebFilter(value = "/*", filterName = "logFilter") @Slf4j public class LogFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { WrappedHttpServletRequest requestWrapper = new WrappedHttpServletRequest((HttpServletRequest) request); // 獲取請求參數 String requestBody = requestWrapper.getRequestParams(); if (!StringUtils.isBlank(requestBody)) { if (requestBody.length() >= 8192) { requestBody = requestBody.substring(0, 8192); } request.setAttribute("request-body", requestBody); // 這里創建一個參數頭,把要記錄的報文放到參數頭里面,在切面中讀取這個參數頭 } // 這里doFilter傳入我們實現的子類 chain.doFilter(requestWrapper, response); } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public void destroy() {} }