設計模式 | 責任鏈模式及典型應用

本文的主要內容:java

  • 介紹責任鏈模式
  • 請假流程示例
  • 責任鏈模式總結
  • 源碼分析Tomcat Filter中的責任鏈模式

更多內容可訪問個人我的博客:laijianfeng.org
關注【小旋鋒】微信公衆號,及時接收博文推送web

長按關注【小旋鋒】微信公衆號
****


責任鏈模式

一個事件須要通過多個對象處理是一個挺常見的場景,譬如採購審批流程,請假流程,軟件開發中的異常處理流程,web請求處理流程等各類各樣的流程,能夠考慮使用責任鏈模式來實現。編程

以請假流程爲例,通常公司普通員工的請假流程簡化以下:後端

普通員工請假簡化流程圖

普通員工發起一個請假申請,當請假天數小於3天時只須要獲得主管批准便可;當請假天數大於3天時,主管批准後還須要提交給經理審批,經理審批經過,若請假天數大於7天還須要進一步提交給總經理審批。設計模式

使用 if-else 來實現這個請假流程的簡化代碼以下:數組

public class LeaveApproval {
    public boolean process(String request, int number) {
        boolean result = handleByDirector(request); // 主管處理
        if (result == false) {  // 主管不批准
            return false;
        } else if (number < 3) {    // 主管批准且天數小於 3
            return true;
        }

        result = handleByManager(request); // 準管批准且天數大於等於 3,提交給經理處理
        if (result == false) {   // 經理不批准
            return false;
        } else if (number < 7) { // 經理批准且天數小於 7
            return true;
        }

        result = handleByTopManager(request);   // 經理批准且天數大於等於 7,提交給總經理處理
        if (result == false) { // 總經理不批准
            return false;
        }
        return true;    // 總經理最後批准
    }

    public boolean handleByDirector(String request) {
        // 主管處理該請假申請
    }

    public boolean handleByManager(String request) {
        // 經理處理該請假申請
    }

    public boolean handleByTopManager(String request) {
        // 總經理處理該請假申請
    }
}
複製代碼

問題看起來很簡單,三下五除二就搞定,可是該方案存在幾個問題bash

  1. LeaveApproval 類比較龐大,各個上級的審批方法都集中在該類中,違反了 "單一職責原則",測試和維護難度大服務器

  2. 當須要修改該請假流程,譬如增長當天數大於30天時還需提交給董事長處理,必須修改該類源代碼(並從新進行嚴格地測試),違反了 "開閉原則"微信

  3. 該流程缺少靈活性,流程肯定後不可再修改(除非修改源代碼),客戶端沒法定製流程app

使用責任鏈模式能夠解決上述問題。

定義

責任鏈模式(Chain of Responsibility Pattern):避免請求發送者與接收者耦合在一塊兒,讓多個對象都有可能接收請求,將這些對象鏈接成一條鏈,而且沿着這條鏈傳遞請求,直到有對象處理它爲止。職責鏈模式是一種對象行爲型模式。

角色

Handler(抽象處理者):它定義了一個處理請求的接口,通常設計爲抽象類,因爲不一樣的具體處理者處理請求的方式不一樣,所以在其中定義了抽象請求處理方法。由於每個處理者的下家仍是一個處理者,所以在抽象處理者中定義了一個抽象處理者類型的對象,做爲其對下家的引用。經過該引用,處理者能夠連成一條鏈。

ConcreteHandler(具體處理者):它是抽象處理者的子類,能夠處理用戶請求,在具體處理者類中實現了抽象處理者中定義的抽象請求處理方法,在處理請求以前須要進行判斷,看是否有相應的處理權限,若是能夠處理請求就處理它,不然將請求轉發給後繼者;在具體處理者中能夠訪問鏈中下一個對象,以便請求的轉發。

類圖以下所示:

責任鏈模式.類圖

純與不純的責任鏈模式

純的責任鏈模式

  • 一個具體處理者對象只能在兩個行爲中選擇一個:要麼承擔所有責任,要麼將責任推給下家,不容許出現某一個具體處理者對象在承擔了一部分或所有責任後 又將責任向下傳遞的狀況
  • 一個請求必須被某一個處理者對象所接收,不能出現某個請求未被任何一個處理者對象處理的狀況

不純的責任鏈模式

  • 容許某個請求被一個具體處理者部分處理後再向下傳遞
  • 或者一個具體處理者處理完某請求後其後繼處理者能夠繼續處理該請求
  • 並且一個請求能夠最終不被任何處理者對象所接收

示例

使用責任鏈模式(不純)重構請假流程

請假信息類,包含請假人姓名和請假天數

@Data
@AllArgsConstructor
public class LeaveRequest {
    private String name;    // 請假人姓名
    private int numOfDays;  // 請假天數
}
複製代碼

抽象處理者類 Handler,維護一個 nextHandler 屬性,該屬性爲當前處理者的下一個處理者的引用;聲明瞭抽象方法 process

@Data
public abstract class Handler {
    protected String name; // 處理者姓名
    protected Handler nextHandler;  // 下一個處理者

    public Handler(String name) {
        this.name = name;
    }

    public abstract boolean process(LeaveRequest leaveRequest); // 處理請假
}
複製代碼

三個具體處理類,分別實現了抽象處理類的 process 方法

// 主管處理者
public class Director extends Handler {
    public Director(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 隨機數大於3則爲批准,不然不批准
        String log = "主管<%s> 審批 <%s> 的請假申請,請假天數: <%d> ,審批結果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) {  // 不批准
            return false;
        } else if (leaveRequest.getNumOfDays() < 3) { // 批准且天數小於3,返回true
            return true;
        }
        return nextHandler.process(leaveRequest);   // 批准且天數大於等於3,提交給下一個處理者處理
    }
}

// 經理
public class Manager extends Handler {
    public Manager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 隨機數大於3則爲批准,不然不批准
        String log = "經理<%s> 審批 <%s> 的請假申請,請假天數: <%d> ,審批結果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) {  // 不批准
            return false;
        } else if (leaveRequest.getNumOfDays() < 7) { // 批准且天數小於7
            return true;
        }
        return nextHandler.process(leaveRequest);   // 批准且天數大於等於7,提交給下一個處理者處理
    }
}

// 總經理
public class TopManager extends Handler {
    public TopManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 隨機數大於3則爲批准,不然不批准
        String log = "總經理<%s> 審批 <%s> 的請假申請,請假天數: <%d> ,審批結果:<%s> ";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "批准" : "不批准"));

        if (result == false) { // 總經理不批准
            return false;
        }
        return true;    // 總經理最後批准
    }
}
複製代碼

客戶端測試

public class Client {
    public static void main(String[] args) {
        Handler zhangsan = new Director("張三");
        Handler lisi = new Manager("李四");
        Handler wangwu = new TopManager("王五");

        // 建立責任鏈
        zhangsan.setNextHandler(lisi);
        lisi.setNextHandler(wangwu);

        // 發起請假申請
        boolean result1 = zhangsan.process(new LeaveRequest("小旋鋒", 1));
        System.out.println("最終結果:" + result1 + "\n");

        boolean result2 = zhangsan.process(new LeaveRequest("小旋鋒", 4));
        System.out.println("最終結果:" + result2 + "\n");

        boolean result3 = zhangsan.process(new LeaveRequest("小旋鋒", 8));
        System.out.println("最終結果:" + result3 + "\n");
    }
}
複製代碼

可能的結果以下:(因爲是否批准是經過隨機數模擬的,因此你的結果可能跟我不一樣)

主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <1> ,審批結果:<批准> 
最終結果:true

主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <4> ,審批結果:<不批准> 
最終結果:false

主管<張三> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准> 
經理<李四> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准> 
總經理<王五> 審批 <小旋鋒> 的請假申請,請假天數: <8> ,審批結果:<批准> 
最終結果:true
複製代碼

類圖以下所示

示例.責任鏈類圖

總結

職責鏈模式的主要優勢

  • 對象僅需知道該請求會被處理便可,且鏈中的對象不須要知道鏈的結構,由客戶端負責鏈的建立,下降了系統的耦合度

  • 請求處理對象僅需維持一個指向其後繼者的引用,而不須要維持它對全部的候選處理者的引用,可簡化對象的相互鏈接

  • 在給對象分派職責時,職責鏈能夠給咱們更多的靈活性,能夠在運行時對該鏈進行動態的增刪改,改變處理一個請求的職責

  • 新增一個新的具體請求處理者時無須修改原有代碼,只須要在客戶端從新建鏈便可,符合 "開閉原則"

職責鏈模式的主要缺點

  • 一個請求可能因職責鏈沒有被正確配置而得不處處理

  • 對於比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到必定影響,且不方便調試

  • 可能由於職責鏈建立不當,形成循環調用,致使系統陷入死循環

適用場景

  • 有多個對象能夠處理同一個請求,具體哪一個對象處理該請求待運行時刻再肯定,客戶端只需將請求提交到鏈上,而無須關心請求的處理對象是誰以及它是如何處理的

  • 在不明確指定接收者的狀況下,向多個對象中的一個提交一個請求

  • 可動態指定一組對象處理請求,客戶端能夠動態建立職責鏈來處理請求,還能夠改變鏈中處理者之間的前後次序

責任鏈模式的典型應用

Tomcat 過濾器中的責任鏈模式

Servlet 過濾器是可用於 Servlet 編程的 Java 類,能夠實現如下目的:在客戶端的請求訪問後端資源以前,攔截這些請求;在服務器的響應發送回客戶端以前,處理這些響應。

Servlet 定義了過濾器接口 Filter 和過濾器連接口 FilterChain 的源碼以下

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
    public void destroy();
}

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

咱們自定義一個過濾器的步驟是:

1)寫一個過濾器類,實現 javax.servlet.Filter 接口,以下所示

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 作一些自定義處理....
        System.out.println("執行doFilter()方法以前...");
        chain.doFilter(request, response);              // 傳遞請求給下一個過濾器
        System.out.println("執行doFilter()方法以後...");
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}
複製代碼

2)在 web.xml 文件中增長該過濾器的配置,譬以下面是攔截全部請求

<filter>  
        <filter-name>MyFilter</filter-name>  
        <filter-class>com.whirly.filter.MyFilter</filter-class>  
</filter>
  
<filter-mapping>  
        <filter-name>MyFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
</filter-mapping>
複製代碼

當啓動 Tomcat 是咱們的過濾器就能夠發揮做用了。那麼過濾器是怎樣運行的呢?

Tomcat 有 Pipeline Valve機制,也是使用了責任鏈模式,一個請求會在 Pipeline 中流轉,Pipeline 會調用相應的 Valve 完成具體的邏輯處理;
其中的一個基礎Valve爲 StandardWrapperValve,其中的一個做用是調用 ApplicationFilterFactory 生成 Filter鏈,具體代碼在 invoke 方法中

在運行過濾器以前須要完成過濾器的加載和初始化,以及根據配置信息生成過濾器鏈

  1. 過濾器的加載具體是在 ContextConfig 類的 configureContext 方法中,分別加載 filterfilterMap 的相關信息,並保存在上下文環境中

  2. 過濾器的初始化在 StandardContext 類的 startInternal 方法中完成,保存在 filterConfigs 中並存到上下文環境中

  3. 請求流轉到 StandardWrapperValve 時,在 invoke 方法中,會根據過濾器映射配置信息,爲每一個請求建立對應的 ApplicationFilterChain,其中包含了目標 Servlet 以及對應的過濾器鏈,並調用過濾器鏈的 doFilter 方法執行過濾器

StandardWrapperValve 調用 ApplicationFilterFactory 爲請求建立過濾器鏈並調用過濾器鏈的關鍵代碼以下:

final class StandardWrapperValve extends ValveBase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        // 省略其餘的邏輯處理...
        // 調用 ApplicationFilterChain.createFilterChain() 建立過濾器鏈
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        
        if (servlet != null && filterChain != null) {
            // 省略
        } else if (request.isAsyncDispatching()) {
            request.getAsyncContextInternal().doInternalDispatch();
        } else if (comet) {
            filterChain.doFilterEvent(request.getEvent());
        } else {
            // 調用過濾器鏈的 doFilter 方法開始過濾
            filterChain.doFilter(request.getRequest(), response.getResponse());
        }
複製代碼

過濾器鏈 ApplicationFilterChain 的關鍵代碼以下,過濾器鏈實際是一個 ApplicationFilterConfig 數組

final class ApplicationFilterChain implements FilterChain, CometFilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 過濾器鏈
    private Servlet servlet = null; // 目標
    // ...
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            // ...
        } else {
            internalDoFilter(request,response); // 調用 internalDoFilter 方法
        }
    }
    
    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            // 從過濾器數組中取出當前過濾器配置,而後下標自增1
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();  // 從過濾器配置中取出該 過濾器對象

                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    // 調用過濾器的 doFilter,完成一個過濾器的過濾功能
                    filter.doFilter(request, response, this);
                }
            return;  // 這裏很重要,不會重複執行後面的 servlet.service(request, response)
        }
        
        // 執行完過濾器鏈的全部過濾器以後,調用 Servlet 的 service 完成請求的處理
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
            if( Globals.IS_SECURITY_ENABLED ) {
                
            } else {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
    }
    // 省略...
}
複製代碼

過濾器

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("執行doFilter()方法以前...");
        chain.doFilter(request, response);              // 傳遞請求給下一個過濾器
        System.out.println("執行doFilter()方法以後...");
    }
複製代碼

當下標小於過濾器數組長度 n 時,說明過濾器鏈未執行完,因此從數組中取出當前過濾器,調用過濾器的 doFilter 方法完成過濾處理,在過濾器的 doFilter 中又調用 FilterChaindoFilter,回到 ApplicationFilterChain,又繼續根據下標是否小於數組長度來判斷過濾器鏈是否已執行完,未完則繼續從數組取出過濾器並調用 doFilter 方法,因此這裏的過濾鏈是經過嵌套遞歸的方式來串成一條鏈。

當所有過濾器都執行完畢,最後一次進入 ApplicationFilterChain.doFilter 方法的時候 pos < n 爲false,不進入 if (pos < n) 中,而是執行後面的代碼,判斷 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse),若爲 http 請求則調用 servlet.service(request, response); 來處理該請求。

處理完畢以後沿着調用過濾器的順序反向退棧,分別執行過濾器中 chain.doFilter() 以後的處理邏輯,須要注意的是if (pos < n) 方法體的最後有一個 return;,這樣就保證了只有最後一次進入 ApplicationFilterChain.doFilter 方法的調用可以執行後面的 servlet.service(request, response) 方法

畫一個簡要的調用棧以下所示:

Tomcat 過濾器鏈調用棧

ApplicationFilterChain 類扮演了抽象處理者角色,具體處理者角色由各個 Filter 扮演

其餘的責任鏈模式的典型應用

其餘的責任鏈模式的應用基本都是大同小異

FilterChain 的實現類

這裏列舉幾個典型應用:

  1. Netty 中的 PipelineChannelHandler 經過責任鏈設計模式來組織代碼邏輯
  2. Spring Security 使用責任鏈模式,能夠動態地添加或刪除責任(處理 request 請求)
  3. Spring AOP 經過責任鏈模式來管理 Advisor
  4. Dubbo Filter 過濾器鏈也是用了責任鏈模式(鏈表),能夠對方法調用作一些過濾處理,譬如超時(TimeoutFilter),異常(ExceptionFilter),Token(TokenFilter)等
  5. Mybatis 中的 Plugin 機制使用了責任鏈模式,配置各類官方或者自定義的 Plugin,與 Filter 相似,能夠在執行 Sql 語句的時候作一些操做

參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
責任鏈設計模式(過濾器、攔截器)

後記

歡迎評論、轉發、分享,您的支持是我最大的動力

推薦閱讀

設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
設計模式 | 適配器模式及典型應用
設計模式 | 享元模式及典型應用
設計模式 | 組合模式及典型應用
設計模式 | 模板方法模式及典型應用
設計模式 | 迭代器模式及典型應用
設計模式 | 策略模式及典型應用
設計模式 | 觀察者模式及典型應用
設計模式 | 備忘錄模式及典型應用
設計模式 | 中介者模式及典型應用

相關文章
相關標籤/搜索