一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - SpringBoot之自定義Filter獲取請求參數與響應結果案例詳解

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

2021-12-16 13:02沉潛飛動 Java教程

這篇文章主要介紹了SpringBoot之自定義Filter獲取請求參數與響應結果案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下

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

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

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

定義 Filter

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

從類名上推斷,OncePerRequestFilter是每次請求只執行一次,但是,難道Filter在一次請求中還會執行多次嗎?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 容器,所以定義了只會執行一次的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是比較簡單的,只要能夠注意幾個關鍵點就可以了。不過后續還有擴展的空間,比如:

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

源碼

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

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

原文鏈接:https://www.howardliu.cn/spring-request-recorder/

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 日比免费视频 | 边摸边吃奶玩乳尖视频 | 成人亚洲欧美日韩中文字幕 | 亚洲国产精品久久人人爱 | 九九大香尹人视频免费 | 日本xxxxx69hd日本 | 亚洲天堂男人天堂 | 国产在线一区二区杨幂 | 免费观看视频在线 | 黑人k8经典 | 青青草人人| 日本xx高清视频免费观看 | 欧美一区二区日韩一区二区 | 人皮高跟鞋在线观看 | 国产香蕉国产精品偷在线观看 | jiizz亚洲护士厕所 | 肉文高h调教| 欧美大片一区二区 | 91视频国产精品 | 91国内精品线免费播放 | 国产精品 色 | 91制片在线观看 | 韩国美女主播在线 | 成人不卡在线 | 青青草在观免费 | 精品一区久久 | 国色天香论坛社区在线视频 | 久久精品国产欧美日韩99热 | 美女扒开腿让男人桶爽动态图片 | 沉香如屑西瓜视频免费观看完整版 | 国产实拍会所女技师在线 | 亚洲福利一区二区 | 天天天做天天天天爱天天想 | 8x8x极品国产在线 | 国产一区风间由美在线观看 | 国产成人久久精品推最新 | 扒开大腿狠狠挺进视频 | 强漂亮白丝女教师小说 | 二次元美女脱裤子让男人桶爽 | 午夜黄视频 | 国产码一区二区三区 |