1. 程式人生 > >從原始碼角度理解Java設計模式--責任鏈模式

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

本文內容思維導圖如下:

                                       

一、責任鏈模式介紹

責任鏈模式定義:為請求建立一個處理此請求物件的鏈。

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

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

缺點:每個請求從鏈頭遍歷到鏈尾,影響效能。程式碼除錯時候不方便。

型別:行為型。

類圖:

原始碼中的典型應用:

  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過濾器鏈。

二、請假示例

員工在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中都用到責任鏈模式:


進入第一個,過濾器鏈 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方法通過巢狀遞迴的方式來串成一條鏈。

當全部過濾器都執行完畢,也就是if (pos < n) 為false時,才會呼叫後面的servlet.service(request, response) 方法。需要注意的是在 if (pos < n) 方法體的最後有一個 return;這樣就保證了最後一個filter處理完結束遞迴呼叫doFilter方法,而servlet.service(request, response) 方法才得以執行。這一點在請假流程裡也有體現。

 

參考:

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

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