前言
通常 Web 服務器在處理請求時汁讼,都會使用過濾器模式玄货,無論是 Tomcat 知给,還是 Netty瓤帚,過濾器的好處是能夠將處理的流程進行分離和解耦,比如一個 Http 請求進入服務器涩赢,可能需要解析 http 報頭戈次,權限驗證,國際化處理等等筒扒,過濾器可以很好的將這些過程隔離怯邪,并且,過濾器可以隨時卸載花墩,安裝悬秉。
每個 Web 服務器的過濾器思想都是類似的,只是實現(xiàn)方式略有不同冰蘑。
比如 Tomcat和泌,Tomcat 使用了一個 FilterChain 對象保存了所有的 filter,通過循環(huán)所有 filter 來完成過濾處理祠肥。關于 Tomcat 的過濾器源碼請看樓主之前的文章:
深入理解 Tomcat(九)源碼剖析之請求過程
Netty 使用了 pipeline 作為過濾器管道武氓,管道中使用 handler 做攔截處理,而 handler 使用一個 handlerInvoker(Context) 做隔離處理仇箱,也就是將 handler 和 handler 隔離開來县恕,中間使用 這個 Context 上下文進行流轉。關于 Netty 的 pipeline 可以查看樓主之前的文章 :
Netty 核心組件 Pipeline 源碼分析(一)之剖析 pipeline 三巨頭
Netty 核心組件 Pipeline 源碼分析(二)一個請求的 pipeline 之旅
而 SOFA 使用了和上面的兩個略有不同剂桥,我們今天通過源碼分析一下忠烛。
設計
SOFA 的過濾器由 3 個主要的類組成:
- FilterInvoker 過濾器包裝的Invoker對象,主要是隔離了filter和service的關系权逗;
- Filter 過濾器(可通過 SPI 擴展)
- FilterChain 過濾器鏈起始接口美尸,其實就是一個 Invoker垒拢。
我們看看這 3 個類的主要方法,就知道如何設計的了火惊。
Filter 主要方法:
public abstract SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException;
invoke 方法求类,是一個抽象方法,用戶可以自己實現(xiàn)屹耐,而方法體就是用戶的處理邏輯尸疆。通常這個方法的結尾是:
return invoker.invoke(request);
調(diào)用了參數(shù) invoker 對象的 invoke 方法。我們看看這個 FilterInvoker 惶岭。
FilterInvoker 主要方法
構造方法:
public FilterInvoker(Filter nextFilter, FilterInvoker invoker, AbstractInterfaceConfig config) {
this.nextFilter = nextFilter;
this.invoker = invoker;
this.config = config;
if (config != null) {
this.configContext = config.getConfigValueCache(false);
}
}
樓主這里介紹一下他的主要構造方法寿弱。傳入一個 filter,一個 invoker按灶。
這個 filter 就是當前 invoker 包裝的過濾器症革,而參數(shù) invoker 就是他的下一個 invoker 節(jié)點。當執(zhí)行 FilterInvoker 的 invoke 方法的時候鸯旁,通常會調(diào)用 filter 的 invoke 方法噪矛,并傳入 invoker 參數(shù)。
這就回到我們上面分析的 filter 的 invoke 方法铺罢,該方法內(nèi)部會調(diào)用 invoker 的 invoke 方法艇挨,完成一次輪回。
再看看 FilterChain 韭赘。
FilterChain 主要方法
FilterChain 是框架直接操作的實例缩滨,每個調(diào)用者都間接持有一個 FilterChain 實例,而這個實例相當于過濾器鏈表的頭節(jié)點泉瞻。
構造方法:
protected FilterChain(List<Filter> filters, FilterInvoker lastInvoker, AbstractInterfaceConfig config) {
// 調(diào)用過程外面包裝多層自定義filter
// 前面的過濾器在最外層
invokerChain = lastInvoker;
if (CommonUtils.isNotEmpty(filters)) {
loadedFilters = new ArrayList<Filter>();
for (int i = filters.size() - 1; i >= 0; i--) {// 從最大的開始脉漏,從小到大開始執(zhí)行
Filter filter = filters.get(i);
if (filter.needToLoad(invokerChain)) {
invokerChain = new FilterInvoker(filter, invokerChain, config);
// cache this for filter when async respond
loadedFilters.add(filter);
}
}
}
}
在構造過濾器鏈的時候,會傳入一個過濾器數(shù)組袖牙,并傳入一個 FilterInvoker侧巨,這個 Invoker 是真正的業(yè)務方法,框架會在該 invoke 方法中反射調(diào)用接口的實現(xiàn)類贼陶,也就是業(yè)務代碼钮追。
上面的構造方法主要邏輯是:
倒序循環(huán) List 中的 Filter 實例坪稽,將 Filter 用 FilterInvoker 封裝拒课,并傳入上一個 FilterInvoker 到 FilterInvoker 的構造方法中峰档,形成鏈表。而單獨傳入的 FilterInvoker 則會放到最后一個節(jié)點撮胧。`
所以桨踪,最終,當 FilterChain 調(diào)用過濾器鏈的時候芹啥,會從 order 最小的過濾器開始锻离,最后執(zhí)行業(yè)務方法铺峭。
注意:SOFA 過濾器中,真正執(zhí)行業(yè)務方法的不是 Filter汽纠,而是 FilterInvoker 的具體實現(xiàn)類卫键,在 invoke 方法中,會反射調(diào)用接口實現(xiàn)類的方法虱朵。原因是過濾器最后調(diào)用的 invoker.invoke莉炉。就不用再構造一個 filter 了。
以上就是 SOFA 的過濾器設計碴犬。從總體上來講絮宁,和 Tomcat 的 過濾器類似,只是 Tomcat 使用的數(shù)組服协,并且將 Service 區(qū)分看待绍昂,即執(zhí)行完所有的過濾器后,執(zhí)行 Service偿荷。而 SOFA 使用的是一個鏈表窘游,并沒有區(qū)分對待 Service。
One more thing
Filter 是個接口遭顶,并且標注了 @Extensible(singleton = false)
注解张峰,表示這是一個擴展點泪蔫,這個是 SOFA 微內(nèi)核的一個設計棒旗。所有的中間件都可以通過擴展點加入到框架中。
而擴展點其實有點類似 Spring 的 Bean撩荣,Spring Bean 和核心數(shù)據(jù)結構是 BeanDefine铣揉,SOFA 的 擴展點核心數(shù)據(jù)結構則是 ExtensionClass,該類定義了擴展點的所有相關信息餐曹。
SOFA 會將所有的擴展點放在一個 ExtensionLoader 的 ConcurrentHashMap<String, ExtensionClass<T>> 中逛拱。
ExtensionLoader 可以稱之為擴展類加載器,一個 ExtensionLoader 對應一個可擴展的接口台猴。
總結
從設計上來說朽合,SOFA 的過濾器更類似 Tomcat 的過濾器,相對于 Netty 的過濾器各有特色饱狂。Netty 的過濾器可以隨時插拔曹步,也許從業(yè)務上來說,SOFA 并不需要這樣的功能吧休讳。
而同時讲婚,F(xiàn)ilter 基于 SOFA 的擴展點來的。Dubbo 作者說過:
大凡發(fā)展的比較好的框架俊柔,都遵守微核的理念筹麸,
Eclipse的微核是OSGi活合, Spring的微核是BeanFactory,Maven的微核是Plexus物赶,
通常核心是不應該帶有功能性的白指,而是一個生命周期和集成容器,
這樣各功能可以通過相同的方式交互及擴展酵紫,并且任何功能都可以被替換侵续,
如果做不到微核,至少要平等對待第三方憨闰,
即原作者能實現(xiàn)的功能状蜗,擴展者應該可以通過擴展的方式全部做到,
原作者要把自己也當作擴展者鹉动,這樣才能保證框架的可持續(xù)性及由內(nèi)向外的穩(wěn)定性轧坎。
微核插件式,平等對待第三方 對于框架來說泽示,非常重要缸血。