從源碼角度理解Java設計模式--責任鏈模式

本文內容思維導圖如下:


image.png

一、責任鏈模式介紹

責任鏈模式定義:為請求創建一個處理此請求對象的鏈。

適用場景(核心):只要把你的請求拋給第一個處理者,不用關心誰處理的,并且最終會返回你一個結果。

優點:請求者和處理者解耦,請求者不用知道誰處理的,處理者可以不用知道請求的全貌。

缺點:每個請求從鏈頭遍歷到鏈尾,影響性能。代碼調試時候不方便。

類型:行為型。

類圖:

image

源碼中的典型應用:

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

二、請假示例

image

員工在OA系統中提交請假申請,首先項目經理處理,他能審批3天以內的假期,如果大于3天,則由項目經理則轉交給總經理處理。接下來我們用責任鏈模式實現這個過程。

1、封裝請假信息實體類

public class LeaveRequest {
    private String name;    // 請假人姓名
    private int numOfDays;  // 請假天數
    private int workingAge;  //員工工齡(在公司大于2年則總經理會審批)
   //省略get..set..
}

2、抽象處理者類 Handler,維護一個nextHandler屬性,該屬性為當前處理者的下一個處理者的引用;聲明了抽象方法process,其實在這里也用了方法模板模式:

public abstract class ApproveHandler {

    protected  ApproveHandler nextHandler;//下一個處理者(與類一致,這段代碼很重要)

    public void setNextHandler(ApproveHandler approveHandler){
        this.nextHandler=approveHandler;
    }

    public abstract void process(LeaveRequest leaveRequest); // 處理請假(這里用了模板方法模式)

}

3、項目經理處理者,能處理小于3天的假期,而請假信息里沒有名字時,審批不通過:

public class PMHandler extends ApproveHandler{

    @Override
    public void process(LeaveRequest leaveRequest) {
        //未填寫姓名的請假單不通過
        if(null != leaveRequest.getName()){
            if(leaveRequest.getNumOfDays() <= 3){
                System.out.println(leaveRequest.getName()+",你通過項目經理審批!");
            }else {
                System.out.println("項目經理轉交總經理");
                if(null != nextHandler){
                    nextHandler.process(leaveRequest);
                }
            }
        }else {
            System.out.println("請假單未填寫完整,未通過項目經理審批!");
            return;
        }
    }
}

4、總經理處理者,能處理大于3天的假期,且工齡超過2年才會審批通過:

public class GMHandler extends ApproveHandler{

    @Override
    public void process(LeaveRequest leaveRequest) {
        //員工在公司工齡超過2年,則審批通過
        if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){
            System.out.println(leaveRequest.getName()+",你通過總經理審批!");
            if(null != nextHandler){
                nextHandler.process(leaveRequest);
            }
        }else {
            System.out.println("在公司年限不夠,長假未通過總經理審批!");
            return;
        }
    }
}

實例代碼完成,我們測試一下:

public class Test {
    public static void main(String[] args) {
        PMHandler pm = new PMHandler();
        GMHandler gm = new GMHandler();

        LeaveRequest leaveRequest = new LeaveRequest();
        leaveRequest.setName("張三");
        leaveRequest.setNumOfDays(4);//請假4天
        leaveRequest.setWorkingAge(3);//工齡3年

        pm.setNextHandler(gm);//設置傳遞順序
        pm.process(leaveRequest);
    }
}

運行結果:


項目經理轉交總經理
張三,你通過總經理審批!


三、源碼中的責任鏈模式

Filter接口有非常多的實現類,這里挑選doFilter方法中的FilterChain參數來看,Tomcat和SpringSecurity中都用到責任鏈模式:

image

進入第一個,過濾器鏈 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);
        }
    }
    // 省略...
}

這里可以看出ApplicationFilterChain類扮演了抽象處理者角色,doFilter就類似于剛才請假流程里的process方法。

當下標小于過濾器數組長度 n 時,也就是過濾器鏈未執行完,所以從數組中取出并調用當前過濾器的 doFilter方法 ,如果下標一直小于n,則循環調用doFilter方法通過嵌套遞歸的方式來串成一條鏈。

當最后的過濾器執行完畢,也就是走到最后一個return;時,結束遞歸調用doFilter。if (pos < n) 為false,調用后面的servlet.service(request, response) 方法。return;這一點在請假流程里也有體現。

參考:

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

責任鏈設計模式(過濾器、攔截器)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 定義 責任鏈模式是一種對象的行為模式。在責任鏈模式中,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求...
    步積閱讀 2,069評論 1 5
  • 責任鏈模式CoR (Chain of Responsibility) 概述 責任鏈模式是一種設計模式。在責任鏈模式...
    n油炸小朋友閱讀 2,032評論 0 4
  • 被別人問到,自己只知道這個名字,但是完全不知道內容,竟然還是這么常用的設計的模式,丟人啊,然后找到一篇,介紹簡單好...
    壹人城閱讀 1,160評論 0 7
  • 葛余淶 朋友的孩子讀高三,今天拿來一張他兒子寫的作文。我仔細品讀了好幾遍,或許我的能力太有限,新青年的思想確乎不太...
    叫我淶淶淶閱讀 304評論 1 0
  • 夜空美的時候 是因為有繁星和明月 枯樹要引人注目 須等到來年花發 星星是游子 繞山繞水繞枯樹 螢火匯集 殘枝是夜空...
    玫瑰團子閱讀 231評論 0 2