溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringMVC框架中如何使用Filter實現請求日志打印

發布時間:2021-11-01 09:14:32 來源:億速云 閱讀:164 作者:小新 欄目:開發技術

這篇文章主要為大家展示了“SpringMVC框架中如何使用Filter實現請求日志打印”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“SpringMVC框架中如何使用Filter實現請求日志打印”這篇文章吧。

  • 具體實現

    日志記錄過濾器

    public class RequestFilter implements Filter{private static final String LOG_FORMATTER_IN = "請求路徑:{%s},請求方法:{%s},參數:{%s},來源IP:{%s},請求開始時間{%s},返回:{%s},請求結束時間{%s},用時:{%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;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    HttpServletRequest httpServletRequest = (HttpServletRequest) request;    HttpServletResponse httpServletResponse = (HttpServletResponse) response;    //請求路徑    String path = httpServletRequest.getRequestURI();    String method = httpServletRequest.getMethod();    //所有請求參數的Map    Map<String,String> paramMap = new HashMap<>();    //請求的真實IP    String requestedIP = RequestUtils.getRealIP(httpServletRequest);    //是否攔截并包裝請求,如果需要攔截則會獲取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參數    Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest);    if (headerMap != null && !headerMap.isEmpty()){       paramMap.putAll(headerMap);    }    //獲取路徑參數    Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest);    if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){        paramMap.putAll(uriTemplateMap);    }    //包裝Response,重寫getOutputStream()和getWriter()方法,并用自定義的OutputStream和Writer來攔截和保存ResponseBody    MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse);    //請求開始時間    Long dateStart = System.currentTimeMillis();    //Spring通過DispatchServlet處理請求    chain.doFilter(httpServletRequest, responseWrapper);    //請求結束時間    Long dateEnd = System.currentTimeMillis();    String responseBody;    if (responseWrapper.getMyOutputStream() == null){            if (responseWrapper.getMyWriter() != null){                responseBody = responseWrapper.getMyWriter().getContent();                //一定要flush,responseBody會被復用                responseWrapper.getMyWriter().myFlush();            }        }else {            responseBody = responseWrapper.getMyOutputStream().getBuffer();            //一定要flush,responseBody會被復用            responseWrapper.getMyOutputStream().myFlush();    }    String params = JSONObject.toJSONString(paramMap);    log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart));}/** * 判斷請求/返回是否為application/json * 是則進行攔截, * 否則退出 * @param contentType 請求/響應類型 */private boolean checkFilter(String contentType) {    boolean filterFlag = false;//是否繼續攔截    for (String p : getContentTypes()) {        if (StringUtils.contains(contentType, p)){            filterFlag = true;        }    }    if (StringUtils.isEmpty(contentType)){        filterFlag = true;    }    return filterFlag;}}

    Request包裝器

    /*** HttpServletRequest的包裝器,為了在攔截器階段獲取requestBody且不妨礙SpringMVC再次獲取requestBody*/@Slf4jpublic class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper {//存放JSON數據主體private final byte[] body;public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException {    super(request);    body = getBody(request).getBytes(Charset.forName("UTF-8"));}@Overridepublic ServletInputStream getInputStream() throws IOException {    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);    return new ServletInputStream() {        @Override        public int read() throws IOException {            return byteArrayInputStream.read();        }    };}@Overridepublic BufferedReader getReader() throws IOException {    return new BufferedReader(new InputStreamReader(this.getInputStream()));}/** * 獲取請求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

    /*** 請求工具類*/public class RequestUtils {private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);/** * 獲取所有的請求頭 * @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;}/** * 獲取請求的路徑參數 * @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;}/** * 獲取請求的真實IP * @param request * @return */public static String getRealIP(HttpServletRequest request) {    String ip = request.getHeader("X-Forwarded-For");    if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {        //多次反向代理后會有多個ip值,第一個ip才是真實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中獲取所有的請求參數,包括GET/POST/PATCH等請求,不包括路徑參數 * @param request * @return */public static Map<String,String> getRequestParamMap(HttpServletRequest request) {    Map<String,String> paramMap = new HashMap<>();    //獲取QueryString中的參數,GET方式 或application/x-www-form-urlencoded    Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request);    if (queryParamMap != null){        paramMap.putAll(queryParamMap);    }    //獲取Body中的參數,POST/PATCH等方式,application/json    Map<String,String> bodyParamMap = null;    try {        //當為POST請求且 application/json時,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時,改變對象順序             */            bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField);        }    } catch (Exception e) {        logger.error("獲取請求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包裝器

    /***該包裝器主要是重寫getOutputStream()和getWriter()方法,給調用者返回自定義的OutputStream和Writer,以便參與輸出的過程并記錄保存responseBody。*/public class MyResponseWrapper extends HttpServletResponseWrapper {private ResponsePrintWriter writer;private MyServletOutputStream out;public MyResponseWrapper(HttpServletResponse response) {    super(response);}@Overridepublic ServletOutputStream getOutputStream() throws IOException {    //一定要先判斷當前out為空才能去新建out對象,否則一次請求會出現多個out對象    if (out == null){        out = new MyServletOutputStream(super.getOutputStream());    }    return out;}@Overridepublic PrintWriter getWriter() throws IOException {    //一定要先判斷當前writer為空才能去新建writer對象,否則一次請求會出現多個writer對象    if (writer == null){        writer = new ResponsePrintWriter(super.getWriter());    }    return writer;}public ResponsePrintWriter getMyWriter() {    return writer;}public MyServletOutputStream getMyOutputStream(){    return out;}}

    自定義Writer

    /***自定義Writer,重寫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();}@Overridepublic void flush() {    super.flush();}//清空buffer,以便下一次重新使用public void myFlush(){    buffer = null;}@Overridepublic 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);}@Overridepublic void write(String s) {    super.write(s);    buffer.append(s);}}

    自定義OutputStream

    /*** 自定義輸出流包裝器,重寫write方法,并記錄保存ResponseBody*/public class MyServletOutputStream extends ServletOutputStream {private ServletOutputStream outputStream;private StringBuffer buffer;public MyServletOutputStream(ServletOutputStream outputStream) {    this.outputStream = outputStream;    buffer = new StringBuffer();}@Overridepublic void write(int b) throws IOException {    outputStream.write(b);}@Overridepublic 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"));}@Overridepublic void write(byte[] b) throws IOException {    outputStream.write(b);}@Overridepublic 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;}}

    總結

之前利用HttpServletRequest.getInputStream()和RequestWrapper實現了請求的requestBody獲取,現在提出將一個請求的RequestBody和ResponseBody都提出來并打印日志&落入數據庫,以便統計和查找問題。

查找資料后確定兩種技術方案

1. 使用AOP對所有Controller的方法進行環繞通知處理;

2. 使用Filter攔截所有的Request和Response,并獲取body。

最后選擇了第二種方式,具體實現記錄如下。

具體實現

日志記錄過濾器

public class RequestFilter implements Filter{private static final String LOG_FORMATTER_IN = "請求路徑:{%s},請求方法:{%s},參數:{%s},來源IP:{%s},請求開始時間{%s},返回:{%s},請求結束時間{%s},用時:{%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;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    HttpServletRequest httpServletRequest = (HttpServletRequest) request;    HttpServletResponse httpServletResponse = (HttpServletResponse) response;    //請求路徑    String path = httpServletRequest.getRequestURI();    String method = httpServletRequest.getMethod();    //所有請求參數的Map    Map<String,String> paramMap = new HashMap<>();    //請求的真實IP    String requestedIP = RequestUtils.getRealIP(httpServletRequest);    //是否攔截并包裝請求,如果需要攔截則會獲取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參數    Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest);    if (headerMap != null && !headerMap.isEmpty()){       paramMap.putAll(headerMap);    }    //獲取路徑參數    Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest);    if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){        paramMap.putAll(uriTemplateMap);    }    //包裝Response,重寫getOutputStream()和getWriter()方法,并用自定義的OutputStream和Writer來攔截和保存ResponseBody    MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse);    //請求開始時間    Long dateStart = System.currentTimeMillis();    //Spring通過DispatchServlet處理請求    chain.doFilter(httpServletRequest, responseWrapper);    //請求結束時間    Long dateEnd = System.currentTimeMillis();    String responseBody;    if (responseWrapper.getMyOutputStream() == null){            if (responseWrapper.getMyWriter() != null){                responseBody = responseWrapper.getMyWriter().getContent();                //一定要flush,responseBody會被復用                responseWrapper.getMyWriter().myFlush();            }        }else {            responseBody = responseWrapper.getMyOutputStream().getBuffer();            //一定要flush,responseBody會被復用            responseWrapper.getMyOutputStream().myFlush();    }    String params = JSONObject.toJSONString(paramMap);    log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart));}/** * 判斷請求/返回是否為application/json * 是則進行攔截, * 否則退出 * @param contentType 請求/響應類型 */private boolean checkFilter(String contentType) {    boolean filterFlag = false;//是否繼續攔截    for (String p : getContentTypes()) {        if (StringUtils.contains(contentType, p)){            filterFlag = true;        }    }    if (StringUtils.isEmpty(contentType)){        filterFlag = true;    }    return filterFlag;}}

Request包裝器

/*** HttpServletRequest的包裝器,為了在攔截器階段獲取requestBody且不妨礙SpringMVC再次獲取requestBody*/@Slf4jpublic class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper {//存放JSON數據主體private final byte[] body;public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException {    super(request);    body = getBody(request).getBytes(Charset.forName("UTF-8"));}@Overridepublic ServletInputStream getInputStream() throws IOException {    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);    return new ServletInputStream() {        @Override        public int read() throws IOException {            return byteArrayInputStream.read();        }    };}@Overridepublic BufferedReader getReader() throws IOException {    return new BufferedReader(new InputStreamReader(this.getInputStream()));}/** * 獲取請求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

/*** 請求工具類*/public class RequestUtils {private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);/** * 獲取所有的請求頭 * @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;}/** * 獲取請求的路徑參數 * @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;}/** * 獲取請求的真實IP * @param request * @return */public static String getRealIP(HttpServletRequest request) {    String ip = request.getHeader("X-Forwarded-For");    if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {        //多次反向代理后會有多個ip值,第一個ip才是真實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中獲取所有的請求參數,包括GET/POST/PATCH等請求,不包括路徑參數 * @param request * @return */public static Map<String,String> getRequestParamMap(HttpServletRequest request) {    Map<String,String> paramMap = new HashMap<>();    //獲取QueryString中的參數,GET方式 或application/x-www-form-urlencoded    Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request);    if (queryParamMap != null){        paramMap.putAll(queryParamMap);    }    //獲取Body中的參數,POST/PATCH等方式,application/json    Map<String,String> bodyParamMap = null;    try {        //當為POST請求且 application/json時,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時,改變對象順序             */            bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField);        }    } catch (Exception e) {        logger.error("獲取請求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包裝器

/***該包裝器主要是重寫getOutputStream()和getWriter()方法,給調用者返回自定義的OutputStream和Writer,以便參與輸出的過程并記錄保存responseBody。*/public class MyResponseWrapper extends HttpServletResponseWrapper {private ResponsePrintWriter writer;private MyServletOutputStream out;public MyResponseWrapper(HttpServletResponse response) {    super(response);}@Overridepublic ServletOutputStream getOutputStream() throws IOException {    //一定要先判斷當前out為空才能去新建out對象,否則一次請求會出現多個out對象    if (out == null){        out = new MyServletOutputStream(super.getOutputStream());    }    return out;}@Overridepublic PrintWriter getWriter() throws IOException {    //一定要先判斷當前writer為空才能去新建writer對象,否則一次請求會出現多個writer對象    if (writer == null){        writer = new ResponsePrintWriter(super.getWriter());    }    return writer;}public ResponsePrintWriter getMyWriter() {    return writer;}public MyServletOutputStream getMyOutputStream(){    return out;}}

自定義Writer

/***自定義Writer,重寫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();}@Overridepublic void flush() {    super.flush();}//清空buffer,以便下一次重新使用public void myFlush(){    buffer = null;}@Overridepublic 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);}@Overridepublic void write(String s) {    super.write(s);    buffer.append(s);}}

自定義OutputStream

/*** 自定義輸出流包裝器,重寫write方法,并記錄保存ResponseBody*/public class MyServletOutputStream extends ServletOutputStream {private ServletOutputStream outputStream;private StringBuffer buffer;public MyServletOutputStream(ServletOutputStream outputStream) {    this.outputStream = outputStream;    buffer = new StringBuffer();}@Overridepublic void write(int b) throws IOException {    outputStream.write(b);}@Overridepublic 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"));}@Overridepublic void write(byte[] b) throws IOException {    outputStream.write(b);}@Overridepublic 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;}}

以上是“SpringMVC框架中如何使用Filter實現請求日志打印”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女