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,操作類型:{%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)類型
*/
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)求工具類
*/
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類
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)配置原理解析

