設計模式——責任鏈(結合Tomcat中Filter機制)

糖拌蕃茄發表於2020-12-23

設計模式:責任鏈模式

說責任鏈之前,先引入一個場景,假如規定學生請假小於或等於 2 天,班主任可以批准;小於或等於 7 天,系主任可以批准;小於或等於 10 天,院長可以批准;其他情況不予批准;以此為需求,寫一個程式,你會怎麼做?按著過程思維方式,最快最直白的就是,if else嘛,配合java,無非多追加學生類和各個角色的類。下面介紹的設計模式或許會給我們一些啟發。

責任鏈模式

責任鏈又叫做職責鏈,是屬於行為型設計模式,它的初衷是為了解決一個事件需要經過多個物件處理是很常見的場景。責任鏈的運作流程是將所有請求的處理者通過前一物件記住其下一個物件的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止。

在責任鏈模式中,客戶只需要將請求傳送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞。所以責任鏈將請求的傳送者和請求的處理者解耦了。

責任鏈的實現

責任鏈的設計源於資料結構中的連結串列,從模式的定義中就能看出,它需要一串走下去,而每一個處理請求的物件,都需要記錄下一個處理請求的物件,即標準的資料連結串列方式。

職責鏈模式的實現主要包含以下角色。

    1. 抽象處理者(Handler)角色:定義一個處理請求的介面,包含抽象處理方法和一個後繼連線。
    2. 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。
    3. 客戶類(Client)角色:建立處理鏈,並向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞過程。


責任鏈模式的本質是解耦請求與處理,讓請求在處理鏈中能進行傳遞與被處理;理解責任鏈模式應當理解其模式,而不是其具體實現。責任鏈模式的獨到之處是將其節點處理者組合成了鏈式結構,並允許節點自身決定是否進行請求處理或轉發,相當於讓請求流動起來。

UML類圖如下:

 

模式的實現例子:

參照以上的思想,我們針對一開始的場景編寫請假的程式:

public class LeaveApprovalTest {
    public static void main(String[] args) {
        //組裝責任鏈
        Leader teacher1 = new ClassAdviser();
        Leader teacher2 = new DepartmentHead();
        Leader teacher3 = new Dean();
        //Leader teacher4=new DeanOfStudies();
        teacher1.setNext(teacher2);
        teacher2.setNext(teacher3);
        //teacher3.setNext(teacher4);
        //提交請求
        teacher1.handleRequest(8);
    }
}
//抽象處理者:領導類
abstract class Leader {
    private Leader next;
    public void setNext(Leader next) {
        this.next = next;
    }
    public Leader getNext() {
        return next;
    }
    //處理請求的方法
    public abstract void handleRequest(int LeaveDays);
}
//具體處理者1:班主任類
class ClassAdviser extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 2) {
            System.out.println("班主任批准您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數太多,沒有人批准該假條!");
            }
        }
    }
}
//具體處理者2:系主任類
class DepartmentHead extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 7) {
            System.out.println("系主任批准您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數太多,沒有人批准該假條!");
            }
        }
    }
}
//具體處理者3:院長類
class Dean extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 10) {
            System.out.println("院長批准您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數太多,沒有人批准該假條!");
            }
        }
    }
}
//具體處理者4:教務處長類
class DeanOfStudies extends Leader {
    public void handleRequest(int LeaveDays) {
        if (LeaveDays <= 20) {
            System.out.println("教務處長批准您請假" + LeaveDays + "天。");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(LeaveDays);
            } else {
                System.out.println("請假天數太多,沒有人批准該假條!");
            }
        }
    }
}

 

 

Tomcat中Filter的執行過程

 前邊已經講述了關於責任鏈模式的結構與特點,下面介紹其應用場景,責任鏈模式通常在以下幾種情況使用。

    • 多個物件可以處理一個請求,但具體由哪個物件處理該請求在執行時自動確定。
    • 可動態指定一組物件處理請求,或新增新的處理者。
    • 需要在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。

說完了責任鏈的靈活應用,下面結合tomcat中Filter的例子,進行一個標準責任鏈的解析,先來看以下Tomcat的過濾器機制:

 這是一個tomcat處理請求的過程,即它會有多個過濾器,這裡的過濾器串聯起來,形成一條過濾鏈,前端或者瀏覽器發來了request,會經過這條鏈,順著鏈依次經過每個過濾器,最終由servlet處理後,再逐一返回。這有點像棧結構,但是這其中逐一處理,構成一條鏈,又符合責任鏈的設計規則。

檢視一下Tomcat中Filter介面的原始碼:

public interface Filter {
    void init(FilterConfig var1) throws ServletException;
    //熟悉的doFilter(), 熟悉的3個引數request, reponse, filterChain.
    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

下面是過濾鏈的介面原始碼:

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

  具體的過濾鏈的實現,都會帶有一個容器,來存放該鏈中的Filter,即過濾鏈中包含一個個的過濾器。

做一個簡化版的過濾機制

下面我們簡化模擬一下Tomcat處理Filter的過程,

首先定義簡易版的request和response物件

public class Request{
    String msg;
    public void setMsg(String msg){
         this.msg=msg;
   }
}


public class Response{

  public void deal(){
   System.out.println();
}
}

定義Filter介面及兩個實現(http校驗,訊息敏感字元校驗)

public interface Filter{
    void doFilter(Request req,Response rep,Filter filer);
}

public HttpFilter implements Filter{
    void doFilter(Request req,Response rep,Filter filer){
          System.out.println("處理了http驗證"+req.getMsg());
          filter.doFilter(req,rep,filter);
   }
}

public SensitiveFilter implements Filter{
    void doFilter(Request req,Response rep,Filter filer){
          System.out.println("處理了敏感字元替換"+req.getMsg());
          filter.doFilter(req,rep,filter);
   }
}

定義過濾鏈:

public class FilterChain implements Filter{
  
    List<Filter> filterlist = new Arrary<>();
    private int index;  

    public FilterChain addFilter(Filter filter){
            filterlist.add(filter);
            return this;
    }

    void doFilter(Request req,Response res,Filter filter){
          if(index == filterlist.size()){
            return;//這裡是逆序處理響應的關鍵, 當index為容器大小時, 證明對request的處理已經完成, 下面進入對response的處理.
        }
        Filter f = filterlist.get(index);//過濾器鏈按index的順序拿到filter
        index++;
        f.doFilter(request, response, filter);
   }
}

測試程式碼:

public class DemoBox {
    public static void main(String[] args) {
        String msg = "大家好 ";//以下三行模擬一個請求
        Request request = new Request();
        request.setMsg(msg);

        Response response = new Response();//響應

        FilterChain fc = new FilterChain();//過濾器鏈
        HttpFilter f1 = new HttpFilter();//建立過濾器
        SensitiveFilter f2 = new SensitiveFilter();//建立過濾器
       
        fc.add(f1);//把過濾器新增到過濾器鏈中
        fc.add(f2);
        fc.doFilter(request, response, fc);//直接呼叫過濾器鏈的doFilter()方法進行處理

    }
}

   下面按著步驟,詳細解釋一下上面的程式碼

  • 首先我們分別建立一個RequestResponse物件. Request在傳入進後端時需要依次被過濾器進行處理, Response物件在輸出時要依次被過濾器處理.
  • 我們定義了一個Filter介面,它包含處理請求的方法doFilter,這裡的Filter可以理解為責任鏈中的抽象處理者
  • 依次實現了兩個攔截器,HttpFilter,SensitiveFilter,做具體的過濾處理,可以理解為責任鏈中具體處理者的角色
  • 實現一個Filter介面,做一個過濾鏈的類FilterChain,它除了基本的處理功能,還包含了一個過濾器容器FilterList,用它還存放整條鏈的Filter。
  • 接著我們呼叫過濾器鏈的doFilter()方法對request物件進行處理
  • 這時過濾器鏈中的index值為0, 通過index我們找到第一個過濾器並呼叫它的doFilter()方法
  • 進入doFilter()方法後, 首先會對request請求進行處理, 然後又呼叫了過濾器鏈的doFilter()方法. 這就是整個責任鏈模式的精妙之處, 它解釋了為什麼要給doFilter()加上一個過濾器鏈引數, 就是為了讓每個過濾器可以呼叫過濾器鏈本身執行下一個過濾器。
  • 為什麼要呼叫過濾器鏈本身? 因為當呼叫過濾器本身後, 程式將跳轉回到過濾器鏈的doFilter方法執行, 這時index為1, 也就是拿到第二個過濾器, 然後繼續處理。
  • 正是由於這個跳轉, 使得過濾器中對response的處理暫時無法執行, 它必須等待上面的對過濾器鏈的方法返回才能被執行.

相關文章