1. 程式人生 > >【Tomcat】pipeline valve 設計模式

【Tomcat】pipeline valve 設計模式

這裡側重設計模式的角度,具體結合tomcat細節的執行機制,準備在另一篇寫。

只有先把這個模式抽出來理解清楚,再看tomcat的實現會更簡單。

pipeline模式並不難理解,類似servlet規範中的filter。指的是一個pipeline,需要多個閥門序列處理,流水線作業。

那麼很簡單,我們可以寫出如下的程式碼:

import java.util.ArrayList;
import java.util.List;

public class Pipeline {

    List<Valve> list = new ArrayList<Valve>();

    public Pipeline(){
        list.add(new Valve1());
        list.add(new Valve2());
        list.add(new Valve3());
        list.add(new Valve4());
    }

    public void invoke(){
        for(Valve valve : list){
            valve.invoke();
        }
    }

    public static void main(String args[]){
        new Pipeline().invoke();
    }
}

public interface Valve {
    void invoke();
}

public class Valve1 implements Valve {
    public void invoke() {
        System.out.println("valve1 invoke");
    }
}

其餘valve類似。

上面的程式碼實現很簡單,也可以完成pipeline的模型,既然如此,為什麼還需要搞一個設計模式出來?

必然是上面的程式碼存在一定的問題:

沒有抽象。分兩層,一是對外提供服務,呼叫pipeline的使用方直接依賴了pipeline的具體實現,我們說軟體設計一個重要的原則是“依賴倒置”,如果我們的功能是對外提供的,那麼最好是以介面這種穩定的方式提供,這不是僅僅在pipeline模式中適用的,而是所有軟體設計都應該重視的。依賴介面的好處是我們可以隨意變更介面的實現,而依賴方不需要很大的修改。舉個例子,上面的pipeline是基於list實現的,現在我想要新增一個array實現的,而且呼叫方在某種情況下需要呼叫array實現的,那麼我們必須新建一個ArrayPipeline的類,然後在需要呼叫array的地方把建立pipeline例項的方式改了,呼叫方式也有可能改。如果我們使用了介面,那麼任何呼叫方的程式碼不需要需改,只需要修改介面的賦值語句。如果使用了spring,那麼只需要修改一個bean的定義檔案即可。二是對內實現上,上面的實現pipeline和valve是未解耦的,問題和對外的一樣,需要使用介面解耦,其次pipeline的構建是硬編碼的,事實上,我麼很有可能需要動態新增valve,因此pipeline需要有addValve之類的介面。

綜上,上面的程式碼最大問題是沒有解耦,在之後很難做擴充套件。因此需要抽象出每一種角色的職責。這其實也是所有設計模式出現的初衷,設計模式很大程度上就是為了抽象,讓實現以介面的方式進行。

重新設計:

public interface Pipeline {
    void setBasic(Valve valve);
    void add(Valve valve);
    void remove(Valve valve);
    void invoke();
}

import java.util.ArrayList;
import java.util.List;

public class StandardPipeline implements Pipeline {
    List<Valve> valves = new ArrayList<Valve>();
    Valve basic;

    public void setBasic(Valve valve) {
        basic = valve;
    }

    public void add(Valve valve) {
        valves.add(valve);
    }

    public void remove(Valve valve) {
        valves.remove(valve);
    }

    public void invoke() {
        for(Valve valve : valves){
            valve.invoke();
        }
    }
}

抽象出了valve和pipeline的職責,就形成了上面的程式碼。

tomcat的pipeline規定必須有有個basic的valve,該valve是該流水線的最終處理器,完成這條流水線的主要職責,basic前可以沒有valve,但是basic是必須存在的。就好比可以沒有前面的filter但是必須有最後的serlvet來處理一個http請求。

這裡的pipeline的實現是一種for迴圈方式,如果每一個valve可以決定是否打斷當前的執行鏈,這種for迴圈就無法處理了。當然,我們可以讓每一個valve返回一個boolean值,但是這種方式代價高昂,前提是valve本省沒有返回值。如果是類似linux管道的模式,每一個valve要處理上一個valve的返回值,那麼這種方式就不可行。而且,從邏輯上講,是否往下執行,是在一個valve內部需要解決的事情,pipeline不應該干涉。

所以上面for迴圈的方式假定了每一個valve都必須執行一次,沒有打斷執行鏈的能力。

為了更優雅的解決這個問題,tomcat的pipeline模式加入了一個新的角色context。那麼最後的設計變成了這樣:

public interface PipelineContext {
    void invokeNext();
}
public interface Valve {
    void invoke(PipelineContext context);
}

public class Valve1 implements Valve {
    public void invoke(PipelineContext context) {
        System.out.println("valve1 invoke");
        context.invokeNext();
    }
}

import java.util.ArrayList;
import java.util.List;

public class StandardPipeline implements Pipeline {
    List<Valve> valves;
    Valve basic;
    PipelineContext context;

    public StandardPipeline(){
        valves = new ArrayList<Valve>();
        context = new StandardPipelineContext();
    }

    public void setBasic(Valve valve) {
        basic = valve;
    }

    public void add(Valve valve) {
        valves.add(valve);
    }

    public void remove(Valve valve) {
        valves.remove(valve);
    }

    public void invoke() {
        context.invokeNext();
    }

    private class StandardPipelineContext implements PipelineContext{

        int step = 0;

        public void invokeNext() {
            if(!valves.isEmpty() && step < valves.size())
                valves.get(step++).invoke(context);
            else
                basic.invoke(context);
        }
    }

    public static void main(String args[]){
        Pipeline pipeline = new StandardPipeline();
        pipeline.setBasic(new BasicValve());
        pipeline.add(new Valve1());
        pipeline.add(new Valve2());
        pipeline.add(new Valve3());
        pipeline.add(new Valve4());

        pipeline.invoke();
    }
}

取消了pipeline裡invoke方法的for迴圈。添加了一個context的角色。之前說每一個valve要有決定是否打斷鏈子的能力,那它必須獲取到一些全域性的資訊才行,這裡的context就是為了讓valve具有這種打斷能力。context提供了valve執行下一個valve的能力,這樣valve在內部就可以通過if來判斷是否需要執行下一個valve。

context本身的實現像是一個遊標,提供了一個介面來滑動遊標。這樣,pipeline裡invoke方法就像是一個迭代器模式,沒有for迴圈。

輸出: