最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot之自定義Filter獲取請求參數與響應結果案例詳解

 更新時間:2021年09月03日 11:22:44   作者:沉潛飛動  
這篇文章主要介紹了SpringBoot之自定義Filter獲取請求參數與響應結果案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下

一個系統上線,肯定會或多或少的存在異常情況。為了更快更好的排雷,記錄請求參數和響應結果是非常必要的。所以,Nginx 和 Tomcat 之類的 web 服務器,都提供了訪問日志,可以幫助我們記錄一些請求信息。

本文是在我們的應用中,定義一個Filter來實現記錄請求參數和響應結果的功能。

有一定經驗的都知道,如果我們在Filter中讀取了HttpServletRequest或者HttpServletResponse的流,就沒有辦法再次讀取了,這樣就會造成請求異常。所以,我們需要借助 Spring 提供的ContentCachingRequestWrapperContentCachingRequestWrapper實現數據流的重復讀取。

定義 Filter

通常來說,我們自定義的Filter是實現Filter接口,然后寫一些邏輯,但是既然是在 Spring 中,那就借助 Spring 的一些特性。在我們的實現中,要繼承OncePerRequestFilter實現我們的自定義實現。

從類名上推斷,OncePerRequestFilter是每次請求只執(zhí)行一次,但是,難道Filter在一次請求中還會執(zhí)行多次嗎?Spring 官方也是給出定義這個類的原因:

Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. It provides a doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) method with HttpServletRequest and HttpServletResponse arguments.

As of Servlet 3.0, a filter may be invoked as part of a REQUEST or ASYNC dispatches that occur in separate threads. A filter can be configured in web.xml whether it should be involved in async dispatches. However, in some cases servlet containers assume different default configuration. Therefore sub-classes can override the method shouldNotFilterAsyncDispatch() to declare statically if they should indeed be invoked, once, during both types of dispatches in order to provide thread initialization, logging, security, and so on. This mechanism complements and does not replace the need to configure a filter in web.xml with dispatcher types.

Subclasses may use isAsyncDispatch(HttpServletRequest) to determine when a filter is invoked as part of an async dispatch, and use isAsyncStarted(HttpServletRequest) to determine when the request has been placed in async mode and therefore the current dispatch won't be the last one for the given request.

Yet another dispatch type that also occurs in its own thread is ERROR. Subclasses can override shouldNotFilterErrorDispatch() if they wish to declare statically if they should be invoked once during error dispatches.

也就是說,Spring 是為了兼容不同的 Web 容器,所以定義了只會執(zhí)行一次的OncePerRequestFilter

接下來開始定義我們的Filter類:

public class AccessLogFilter extends OncePerRequestFilter {
    //... 這里有一些必要的屬性

    @Override
    protected void doFilterInternal(final HttpServletRequest request,
                                    final HttpServletResponse response,
                                    final FilterChain filterChain)
            throws ServletException, IOException {
        // 如果是被排除的 uri,不記錄 access_log
        if (matchExclude(request.getRequestURI())) {
            filterChain.doFilter(request, response);
            return;
        }

        final String requestMethod = request.getMethod();
        final boolean shouldWrapMethod = StringUtils.equalsIgnoreCase(requestMethod, HttpMethod.PUT.name())
                || StringUtils.equalsIgnoreCase(requestMethod, HttpMethod.POST.name());

        final boolean isFirstRequest = !isAsyncDispatch(request);

        final boolean shouldWrapRequest = isFirstRequest && !(request instanceof ContentCachingRequestWrapper) && shouldWrapMethod;
        final HttpServletRequest requestToUse = shouldWrapRequest ? new ContentCachingRequestWrapper(request) : request;

        final boolean shouldWrapResponse = !(response instanceof ContentCachingResponseWrapper) && shouldWrapMethod;
        final HttpServletResponse responseToUse = shouldWrapResponse ? new ContentCachingResponseWrapper(response) : response;

        final long startTime = System.currentTimeMillis();
        Throwable t = null;
        try {
            filterChain.doFilter(requestToUse, responseToUse);
        } catch (Exception e) {
            t = e;
            throw e;
        } finally {
            doSaveAccessLog(requestToUse, responseToUse, System.currentTimeMillis() - startTime, t);
        }
    }

    // ... 這里是一些必要的方法

這段代碼就是整個邏輯的核心所在,其他的內容從源碼中找到。

分析

這個代碼中,整體的邏輯沒有特別復雜的地方,只需要注意幾個關鍵點就可以了。

  1. 默認的HttpServletRequestHttpServletResponse中的流被讀取一次之后,再次讀取會失敗,所以要使用ContentCachingRequestWrapperContentCachingResponseWrapper進行包裝,實現重復讀取。
  2. 既然我們可以自定義Filter,那我們依賴的組件中也可能會自定義Filter,更有可能已經對請求和響應對象進行過封裝,所以,一定要先進行一步判斷。也就是request instanceof ContentCachingRequestWrapperresponse instanceof ContentCachingResponseWrapper。

只要注意了這兩點,剩下的都是這個邏輯的細化實現。

運行

接下來我們就運行一遍,看看結果。先定義幾種不同的請求:普通 get 請求、普通 post 請求、上傳文件、下載文件,這四個接口幾乎可以覆蓋絕大部分場景。(因為都是比較簡單的寫法,源碼就不贅述了,可以從文末的源碼中找到)

先啟動項目,然后借助 IDEA 的 http 請求工具:

###普通 get 請求
GET http://localhost:8080/index/get?name=howard

###普通 post 請求
POST http://localhost:8080/index/post
Content-Type: application/json

{"name":"howard"}

###上傳文件
POST http://localhost:8080/index/upload
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="history.txt"
Content-Type: multipart/form-data
</Users/liuxh/history.txt
--WebAppBoundary--

###下載文件
GET http://localhost:8080/index/download

再看看打印的日志:

2021-04-29 19:44:57.495  INFO 83448 --- [nio-8080-exec-1] c.h.d.s.filter.AccessLogFilter           : time=44ms,ip=127.0.0.1,uri=/index/get,headers=[host:localhost:8080,connection:Keep-Alive,user-agent:Apache-HttpClient/4.5.12 (Java/11.0.7),accept-encoding:gzip,deflate],status=200,requestContentType=null,responseContentType=text/plain;charset=UTF-8,params=name=howard,request=,response=
2021-04-29 19:44:57.551  INFO 83448 --- [nio-8080-exec-2] c.h.d.s.filter.AccessLogFilter           : time=36ms,ip=127.0.0.1,uri=/index/post,headers=[content-type:application/json,content-length:17,host:localhost:8080,connection:Keep-Alive,user-agent:Apache-HttpClient/4.5.12 (Java/11.0.7),accept-encoding:gzip,deflate],status=200,requestContentType=application/json,responseContentType=application/json,params=,request={"name":"howard"},response={"name":"howard","timestamp":"1619696697540"}
2021-04-29 19:44:57.585  INFO 83448 --- [nio-8080-exec-3] c.h.d.s.filter.AccessLogFilter           : time=20ms,ip=127.0.0.1,uri=/index/upload,headers=[content-type:multipart/form-data; boundary=WebAppBoundary,content-length:232,host:localhost:8080,connection:Keep-Alive,user-agent:Apache-HttpClient/4.5.12 (Java/11.0.7),accept-encoding:gzip,deflate],status=200,requestContentType=multipart/form-data; boundary=WebAppBoundary,responseContentType=application/json,params=,request=,response={"contentLength":"0","contentType":"multipart/form-data"}
2021-04-29 19:44:57.626  INFO 83448 --- [nio-8080-exec-4] c.h.d.s.filter.AccessLogFilter           : time=27ms,ip=127.0.0.1,uri=/index/download,headers=[host:localhost:8080,connection:Keep-Alive,user-agent:Apache-HttpClient/4.5.12 (Java/11.0.7),accept-encoding:gzip,deflate],status=200,requestContentType=null,responseContentType=application/octet-stream;charset=utf-8,params=,request=,response=

文末總結

自定義Filter是比較簡單的,只要能夠注意幾個關鍵點就可以了。不過后續(xù)還有擴展的空間,比如:

  1. 定義排除的請求 uri,可以借助AntPathMatcher實現 ant 風格的定義
  2. 將請求日志單獨存放,可以借助 logback 或者 log4j2 等框架的的日志配置實現,這樣能更加方便的查找日志
  3. 與調用鏈技術結合,在請求日志中增加調用鏈的 TraceId 等,可以快速定位待查詢的請求日志

源碼

附上源碼:https://github.com/howardliu-cn/effective-spring/tree/main/spring-filter

推薦閱讀

到此這篇關于SpringBoot之自定義Filter獲取請求參數與響應結果案例詳解的文章就介紹到這了,更多相關SpringBoot之自定義Filter獲取請求參數與響應結果內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java線性表排序示例分享

    java線性表排序示例分享

    這篇文章主要介紹了java線性表排序示例,需要的朋友可以參考下
    2014-03-03
  • Springboot使用redisson?+?自定義注解實現消息的發(fā)布訂閱(解決方案)

    Springboot使用redisson?+?自定義注解實現消息的發(fā)布訂閱(解決方案)

    Redisson是一個基于Redis的Java駐留內存數據網格(In-Memory?Data?Grid)和分布式鎖框架,它提供了一系列的分布式Java對象和服務,可以幫助開發(fā)者更方便地使用Redis作為數據存儲和分布式鎖的解決方案,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • SpringBoot應用的打包和發(fā)布實現

    SpringBoot應用的打包和發(fā)布實現

    本文主要介紹了SpringBoot應用的打包和發(fā)布實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • 詳解如何查看Elasticsearch的Debug日志

    詳解如何查看Elasticsearch的Debug日志

    這篇文章主要為大家介紹了詳解如何查看Elasticsearch的Debug日志,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • java獲取Date時間的各種方式匯總

    java獲取Date時間的各種方式匯總

    這篇文章針對java獲取Date時間的各種方式匯總,有常用的時間獲取方式,還有一些其他特殊時間獲取方式,感興趣的小伙伴們可以參考一下
    2015-12-12
  • SpringBoot中服務消費的實現

    SpringBoot中服務消費的實現

    本文主要介紹了SpringBoot中服務消費的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • 在spring?boot3中使用native?image的最新方法

    在spring?boot3中使用native?image的最新方法

    這篇文章主要介紹了在spring?boot3中使用native?image?,今天我們用具體的例子來給大家演示一下如何正確的將spring boot3的應用編譯成為native image,需要的朋友可以參考下
    2023-01-01
  • StringBuilder為什么線程不安全深入講解

    StringBuilder為什么線程不安全深入講解

    這篇文章主要給大家介紹了關于StringBuilder為什么線程不安全的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用StringBuilder線程具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-08-08
  • JavaWeb會話技術詳解與案例

    JavaWeb會話技術詳解與案例

    會話技術:在Web開發(fā)中,服務器跟蹤用戶信息的奇數稱為會話技術。會話:指的是一個客戶端與服務器發(fā)生的一系列請求和響應的過程。由于請求包含的信息,在請求被銷毀后也就不存在,多次讓用戶輸入賬號密碼,會影響用戶的使用體驗感,基于此,產生了cookie和session技術
    2021-11-11
  • java讀取txt文件代碼片段

    java讀取txt文件代碼片段

    這篇文章主要為大家詳細介紹了java讀取txt文件的代碼片段,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05

最新評論

宕昌县| 小金县| 温宿县| 柘城县| 正蓝旗| 平安县| 兰州市| 鹤岗市| 辽阳县| 铜陵市| 阳山县| 三亚市| 扎鲁特旗| 务川| 苏尼特左旗| 海南省| 分宜县| 厦门市| 镇平县| 睢宁县| 法库县| 卫辉市| 高淳县| 于都县| 贵南县| 金门县| 志丹县| 达日县| 武宣县| 平顶山市| 太白县| 都匀市| 贺州市| 无为县| 虹口区| 博湖县| 尼勒克县| 大安市| 梧州市| 泰安市| 商河县|