微服務架構spring cloud
在上一篇文章中,我們瞭解了 Spring Cloud Gateway 作為閘道器所具備的基礎功能:路由。本篇我們將關注它的另一個功能:過濾器。
Spring Cloud Gateway 已經內建了很多實用的過濾器,但並不能完全滿足我們的需求。本文我們就來實現自定義過濾器。雖然現在 Spring Cloud Gateway 的文件還不完善,但是我們依舊可以照貓畫虎來定製自己的過濾器。
Filter 的作用
其實前邊在介紹 Zuul 的的時候已經介紹過 Zuul 的 Filter 的作用了,同作為閘道器服務,Spring Cloud Gateway 的 Filter 作用也類似。
這裡就簡單用兩張圖來解釋一下吧。
當使用微服務構建整個 API 服務時,一般有許多不同的應用在執行,如上圖所示的mst-user-service
、mst-good-service
和mst-order-service
,這些服務都需要對客戶端的請求的進行 Authentication。最簡單粗暴的方法就是像上圖一樣,為每個微服務應用都實現一套用於校驗的過濾器或攔截器。
對於這樣的問題,更好的做法是通過前置的閘道器服務來完成這些非業務性質的校驗,就像下圖
Filter 的生命週期
Spring Cloud Gateway 的 Filter 的生命週期不像 Zuul 的那麼豐富,它只有兩個:“pre” 和 “post”。
“pre”和 “post” 分別會在請求被執行前呼叫和被執行後呼叫,和 Zuul Filter 或 Spring Interceptor 中相關生命週期類似,但在形式上有些不一樣。
Zuul 的 Filter 是通過filterType()
方法來指定,一個 Filter 只能對應一種型別,要麼是 “pre” 要麼是“post”。Spring Interceptor 是通過重寫HandlerInterceptor
中的三個方法來實現的。而 Spring Cloud Gateway 基於 Project Reactor 和 WebFlux,採用響應式程式設計風格,開啟它的 Filter 的介面GatewayFilter
filter
。
僅通過這一個方法,怎麼來區分是 “pre” 還是 “post” 呢?我們下邊就通過自定義過濾器來看看。
自定義過濾器
現在假設我們要統計某個服務的響應時間,我們可以在程式碼中
複製
|
|
每次都要這麼寫是不是很煩?Spring 告訴我們有個東西叫 AOP。但是我們是微服務啊,在每個服務裡都寫也很煩。這時候就該閘道器的過濾器登臺表演了。
自定義過濾器需要實現GatewayFilter
和Ordered
。其中GatewayFilter
中的這個方法就是用來實現你的自定義的邏輯的
複製
|
|
而Ordered
中的int getOrder()
方法是來給過濾器設定優先級別的,值越大則優先順序越低。
好了,讓我們來擼程式碼吧
複製
|
|
我們在請求剛剛到達時,往ServerWebExchange
中放入了一個屬性elapsedTimeBegin
,屬性值為當時的毫秒級時間戳。然後在請求執行結束後,又從中取出我們之前放進去的那個時間戳,與當前時間的差值即為該請求的耗時。因為這是與業務無關的日誌所以將Ordered
設為Integer.MAX_VALUE
以降低優先順序。
現在再來看我們之前的問題:怎麼來區分是 “pre” 還是 “post” 呢?其實就是chain.filter(exchange)
之前的就是 “pre” 部分,之後的也就是then
裡邊的是 “post” 部分。
建立好 Filter 之後我們將它新增到我們的 Filter Chain 裡邊
複製
|
|
複製
|
|
自定義全域性過濾器
前邊講了自定義的過濾器,那個過濾器只是區域性的,如果我們有多個路由就需要一個一個來配置,並不能通過像下面這樣來實現全域性有效(也未在 Fluent Java API 中找到能設定 defaultFilters 的方法)
複製
|
|
這在我們要全域性統一處理某些業務的時候就顯得比較麻煩,比如像最開始我們說的要做身份校驗,有沒有簡單的方法呢?這時候就該全域性過濾器出場了。
有了前邊的基礎,我們建立全域性過濾器就簡單多了。只需要把實現的介面GatewayFilter
換成GlobalFilter
,就完事大吉了。比如下面的 Demo 就是從請求引數中獲取token
欄位,如果能獲取到就 pass,獲取不到就直接返回401
錯誤,雖然簡單,但足以說明問題了。
複製
|
|
然後在 Spring Config 中配置這個 Bean
複製
|
|
重啟應用就能看到效果了
複製
|
|
官方說,未來的版本將對這個介面作出一些調整:
This interface and usage are subject to change in future milestones.
from Spring Cloud Gateway - Global Filters
自定義過濾器工廠
如果你還對上一篇關於路由的文章有印象,你應該還得我們在配置中有這麼一段
複製
|
|
StripPrefix
、AddResponseHeader
這兩個實際上是兩個過濾器工廠(GatewayFilterFactory),用這種配置的方式更靈活方便。
我們就將之前的那個ElapsedFilter
改造一下,讓它能接收一個boolean
型別的引數,來決定是否將請求引數也打印出來。
複製
|
|
過濾器工廠的頂級介面是GatewayFilterFactory
,我們可以直接繼承它的兩個抽象類來簡化開發AbstractGatewayFilterFactory
和AbstractNameValueGatewayFilterFactory
,這兩個抽象類的區別就是前者接收一個引數(像StripPrefix
和我們建立的這種),後者接收兩個引數(像AddResponseHeader
)。
GatewayFilter apply(Config config)
方法內部實際上是建立了一個GatewayFilter
的匿名類,具體實現和之前的幾乎一樣,就不解釋了。
靜態內部類Config
就是為了接收那個boolean
型別的引數服務的,裡邊的變數名可以隨意寫,但是要重寫List<String> shortcutFieldOrder()
這個方法。
這裡注意一下,一定要呼叫一下父類的構造器把Config
型別傳過去,否則會報ClassCastException
複製
|
|
工廠類我們有了,再把它註冊到 Spring 當中
複製
|
|
然後新增配置(主要改動在第 8 行)
複製
|
|
複製
|
|
總結
本文主要介紹了 Spring Cloud Gateway 的過濾器,我們實現了自定義區域性過濾器、自定義全域性過濾器和自定義過濾器工廠,相信大家對 Spring Cloud Gateway 的過濾器有了一定的瞭解。之後我們將繼續在過濾器的基礎上研究 如何使用 Spring Cloud Gateway 實現限流和 fallback。