歡迎光臨
我們一直在努力

SOFA 原始碼分析 —— 過濾器設計

文章摘要: 通常會呼叫 filter 的 invoke 方法並傳入上一個 FilterInvoker 到 FilterInvoker 的構造方法中

通常 Web 伺服器在處理請求時,都會使用過濾器模式,無論是 Tomcat ,還是 Netty,過濾器的好處是能夠將處理的流程進行分離和解耦,比如一個 Http 請求進入伺服器,可能需要解析 http 報頭,許可權驗證,國際化處理等等,過濾器可以很好的將這些過程隔離,並且,過濾器可以隨時解除安裝,安裝。

每個 Web 伺服器的過濾器思想都是類似的,只是實現方式略有不同。

比如 Tomcat,Tomcat 使用了一個 FilterChain 物件儲存了所有的 filter,通過迴圈所有 filter 來完成過濾處理。關於 Tomcat 的過濾器原始碼請看樓主之前的文章: 深入理解 Tomcat(九)原始碼剖析之請求過程

Netty 使用了 pipeline 作為過濾器管道,管道中使用 handler 做攔截處理,而 handler 使用一個 handlerInvoker(Context) 做隔離處理,也就是將 handler 和 handler 隔離開來,中間使用 這個 Context 上下文進行流轉。關於 Netty 的 pipeline 可以檢視樓主之前的文章 : Netty 核心元件 Pipeline 原始碼分析(一)之剖析 pipeline 三巨頭 Netty 核心元件 Pipeline 原始碼分析(二)一個請求的 pipeline 之旅

而 SOFA 使用了和上面的兩個略有不同,我們今天通過原始碼分析一下。

設計

SOFA 的過濾器由 3 個主要的類組成:

  1. FilterInvoker 過濾器包裝的Invoker物件,主要是隔離了filter和service的關係;
  2. Filter 過濾器(可通過 SPI 擴充套件)
  3. FilterChain 過濾器鏈起始介面,其實就是一個 Invoker。

我們看看這 3 個類的主要方法,就知道如何設計的了。

Filter 主要方法:

public abstract SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException;

invoke 方法,是一個抽象方法,使用者可以自己實現,而方法體就是使用者的處理邏輯。通常這個方法的結尾是:

return invoker.invoke(request);

呼叫了引數 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 包裝的過濾器,而引數 invoker 就是他的下一個 invoker 節點。當執行 FilterInvoker 的 invoke 方法的時候,通常會呼叫 filter 的 invoke 方法,並傳入 invoker 引數。

這就回到我們上面分析的 filter 的 invoke 方法,該方法內部會呼叫 invoker 的 invoke 方法,完成一次輪迴。

再看看 FilterChain 。

FilterChain 主要方法

FilterChain 是框架直接操作的例項,每個呼叫者都間接持有一個 FilterChain 例項,而這個例項相當於過濾器連結串列的頭節點。

構造方法:

protected FilterChain(List filters, FilterInvoker lastInvoker, AbstractInterfaceConfig config) {
    // 呼叫過程外面包裝多層自定義filter
    // 前面的過濾器在最外層
    invokerChain = lastInvoker;
    if (CommonUtils.isNotEmpty(filters)) {
        loadedFilters = new ArrayList();
        for (int i = filters.size() - 1; i >= 0; i--) {// 從最大的開始,從小到大開始執行
            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);
            }
        }
    }
}

在構造過濾器鏈的時候,會傳入一個過濾器陣列,並傳入一個 FilterInvoker,這個 Invoker 是真正的業務方法,框架會在該 invoke 方法中反射呼叫介面的實現類,也就是業務程式碼。

上面的構造方法主要邏輯是:

倒序迴圈 List 中的 Filter 例項,將 Filter 用 FilterInvoker 封裝,並傳入上一個 FilterInvoker 到 FilterInvoker 的構造方法中,形成連結串列。而單獨傳入的 FilterInvoker 則會放到最後一個節點。`

所以,最終,當 FilterChain 呼叫過濾器鏈的時候,會從 order 最小的過濾器開始,最後執行業務方法。

注意:SOFA 過濾器中,真正執行業務方法的不是 Filter,而是 FilterInvoker 的具體實現類,在 invoke 方法中,會反射呼叫介面實現類的方法。原因是過濾器最後呼叫的 invoker.invoke。就不用再構造一個 filter 了。

以上就是 SOFA 的過濾器設計。從總體上來講,和 Tomcat 的 過濾器類似,只是 Tomcat 使用的陣列,並且將 Service 區分看待,即執行完所有的過濾器後,執行 Service。而 SOFA 使用的是一個連結串列,並沒有區分對待 Service。

One more thing

Filter 是個介面,並且標註了 @Extensible(singleton = false) 註解,表示這是一個擴充套件點,這個是 SOFA 微核心的一個設計。所有的中介軟體都可以通過擴充套件點加入到框架中。

而擴充套件點其實有點類似 Spring 的 Bean,Spring Bean 和核心數據結構是 BeanDefine,SOFA 的 擴充套件點核心數據結構則是 ExtensionClass,該類定義了擴充套件點的所有相關資訊。

SOFA 會將所有的擴充套件點放在一個 ExtensionLoader 的 ConcurrentHashMap 中。

ExtensionLoader 可以稱之為擴充套件類載入器,一個 ExtensionLoader 對應一個可擴充套件的介面。

總結

從設計上來說,SOFA 的過濾器更類似 Tomcat 的過濾器,相對於 Netty 的過濾器各有特色。Netty 的過濾器可以隨時插拔,也許從業務上來說,SOFA 並不需要這樣的功能吧。

而同時,Filter 基於 SOFA 的擴充套件點來的。Dubbo 作者說過:

大凡發展的比較好的框架,都遵守微核的理念, Eclipse的微核是OSGi, Spring的微核是BeanFactory,Maven的微核是Plexus, 通常核心是不應該帶有功能性的,而是一個生命週期和整合容器, 這樣各功能可以通過相同的方式互動及擴充套件,並且任何功能都可以被替換, 如果做不到微核,至少要平等對待第三方, 即原作者能實現的功能,擴充套件者應該可以通過擴充套件的方式全部做到, 原作者要把自己也當作擴充套件者,這樣才能保證框架的可持續性及由內向外的穩定性。

微核外掛式,平等對待第三方對於框架來說,非常重要。

未經允許不得轉載:頭條楓林網 » SOFA 原始碼分析 —— 過濾器設計