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

本文內容思惟導圖以下:java

                                       

1、責任鏈模式介紹

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

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

優勢:請求者和處理者解耦,請求者不用知道誰處理的,處理者能夠不用知道請求的全貌。數組

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

類型:行爲型。ide

類圖:性能

源碼中的典型應用:測試

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

2、請假示例

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

一、封裝請假信息實體類spa

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

二、抽象處理者類 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天的假期,而請假信息裏沒有名字時,審批不經過:

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;
        }
    }
}

四、總經理處理者,能處理大於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);
    }
}

運行結果:

------

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

------

3、源碼中的責任鏈模式

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方法經過嵌套遞歸的方式來串成一條鏈。

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

 

參考:

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

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

相關文章
相關標籤/搜索