主頁 > 後端開發 > 設計模式----責任鏈模式

設計模式----責任鏈模式

2021-09-14 12:33:16 後端開發

責任鏈模式

  • 引言
  • 責任鏈模式定義
  • 類圖
  • 角色
  • 核心
  • 示例代碼
    • 1、對請求處理者的抽象
    • 2、對請求處理者的抽象
    • 3、責任鏈的創建
  • 責任鏈實作請假案例
    • 案例類圖
    • 可擴展性
  • 純與不純的責任鏈模式
    • 純的責任鏈模式
    • 不純的責任鏈模式
  • 責任鏈模式主要優點
  • 職責鏈模式的主要缺點
  • 適用場景
  • 模擬實作Tomcat中的過濾器機制
    • 運行程序如下
    • 分析Tomcat 過濾器中的責任鏈模式
  • 參考文章


引言

一個事件需要經過多個物件處理是一個挺常見的場景,譬如采購審批流程,請假流程,軟體開發中的例外處理流程,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;    // 總經理最后批準
    }

    private boolean handleByDirector(String request)
    {
        // 主管處理該請假申請
        if(request.length()>10)
            return false;
        return true;
    }

    private boolean handleByManager(String request) {
        // 經理處理該請假申請
        if(request.length()>5)
        return false;
        return true;
    }

    private boolean handleByTopManager(String request) {
        // 總經理處理該請假申請
        if(request.length()>3)
            return false;
        return true;
    }
}

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

  • LeaveApproval 類比較龐大,各個上級的審批方法都集中在該類中,違反了 “單一職責原則”,測驗和維護難度大
  • 當需要修改該請假流程,譬如增加當天數大于30天時還需提交給董事長處理,必須修改該類源代碼(并重新進行嚴格地測驗),違反了"開閉原則"
  • 該流程缺乏靈活性,流程確定后不可再修改(除非修改源代碼),客戶端無法定制流程

使用責任鏈模式可以解決上述問題,


責任鏈模式定義

避免請求發送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連接成一條鏈,并且沿著這條鏈傳遞請求,直到有物件處理它為止,職責鏈模式是一種物件行為型模式,

責任鏈可以是一條直線、一個環或者一個樹形結構,最常見的職責鏈是直線型,即沿著一條單向的鏈來傳遞請求,如下圖所示,鏈上的每一個物件都是請求處理者責任鏈模式可以將請求的處理者組織成一條鏈,并讓請求沿著鏈傳遞,由鏈上的處理者對請求進行相應的處理,在此程序中,客戶端實際上無須關心請求的處理細節以及請求的傳遞,只需將請求發送到鏈上即可,從而實作請求發送者和請求處理者解耦,

在這里插入圖片描述
對責任鏈的理解,關鍵在于對鏈的理解,即包含如下兩點:

  • 鏈是一系列節點的集合,在責任鏈中,節點實質上是指請求的處理者;
  • 鏈的各節點可靈活拆分再重組,在責任鏈中,實質上就是請求發送者與請求處理者的解耦,

類圖

在這里插入圖片描述


角色

我們可以從責任鏈模式的結構圖中看到,具體的請求處理者可以有多個,并且所有的請求處理者均具有相同的介面(繼承于同一抽象類), 責任鏈模式主要包含如下兩個角色

  • Handler(抽象處理者):處理請求的介面,一般設計為具有抽象請求處理方法的抽象類,以便于不同的具體處理者進行繼承,從而實作具體的請求處理方法,此外,由于每一個請求處理者的下家還是一個處理者,因此抽象處理者本身還包含了一個本身的參考( successor)作為其對下家的參考,以便將處理者鏈成一條鏈
  • ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理用戶請求,在具體處理者類中實作了抽象處理者中定義的抽象請求處理方法,在處理請求之前需要進行判斷,看是否有相應的處理權限,如果可以處理請求就處理它,否則將請求轉發給后繼者;在具體處理者中可以訪問鏈中下一個物件,以便請求的轉發,

在責任鏈模式里,由每一個請求處理者物件對其下家的參考而連接起來形成一條請求處理鏈,請求將在這條鏈上一直傳遞,直到鏈上的某一個請求處理者能夠處理此請求,事實上,發出這個請求的客戶端并不知道鏈上的哪一個請求處理者將處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任,


核心

實作責任鏈模式的關鍵核心是在抽象類 Handler 里面聚合它自己(持有自身型別的參考),并在 handleRequest 方法里判斷其是否能夠處理請求,若當前處理者無法處理,則設定其后繼者并向下傳遞,直至請求被處理,


示例代碼

1、對請求處理者的抽象

責任鏈模式的核心在于對 請求處理者的抽象,在實作程序中,抽象處理者一般會被設定為 抽象類,其典型實作代碼如下所示:

public abstract class Handler {

    // protected :維持對下家的參考
    protected Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor=successor;
    }

    public abstract void handleRequest(String request);
}

上述代碼中,抽象處理者類定義了對下家的參考 (其一般用 protected 進行修飾)以便將請求轉發給下家,從而形成一條請求處理鏈,同時,在抽象處理者類中還宣告了抽象的請求處理方法,以便由子類進行具體實作


2、對請求處理者的抽象

具體處理者是抽象處理者的子類,具體處理者類的典型代碼如下:

public class ConcreteHandler extends Handler {
    public void handleRequest(String request) {
        if (請求滿足條件) {
            //處理請求
        }else {
            this.successor.handleRequest(request);  //轉發請求
        }
    }
}

在具體處理類中,通過對請求進行判斷以便做出相應的處理,因此,其一般具有兩大作用:

  • 處理請求,不同的具體處理者以不同的形式實作抽象請求處理方法 handleRequest();
  • 轉發請求,若該請求超出了當前處理者類的權限,可以將該請求轉發給下家;

3、責任鏈的創建

需要注意的是,責任鏈模式并不創建職責鏈,職責鏈的創建作業必須由系統的其他部分來完成,一般由使用該責任鏈的客戶端創建,職責鏈模式降低了請求的發送者和請求處理者之間的耦合,從而使得多個請求處理者都有機會處理這個請求,


責任鏈實作請假案例

請假資訊類,包含請假人姓名和請假天數

@Data
@AllArgsConstructor
public class LeaveRequest
{
 String name;//請假人的姓名
 Integer num;//請假天數
}

抽象處理者類 Handler,維護一個 nextHandler 屬性,該屬性為當前處理者的下一個處理者的參考;宣告了抽象方法 process

//抽象處理者
@Data
public abstract class Handler
{
    //維護自身參考
    protected Handler handler;
   //當前處理者的姓名
    protected String name;
    //傳入當前處理者的姓名
    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) {
       //亂數大于3,就批準請求
        boolean result = (new Random().nextInt(10)) > 3;
        String log = "主管: %s,審批:%s的請假申請,請假天數:%d,審批結果:%s";
        System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過":"不通過"));
        if(result)//批準
        {
            //如果請假天數,超過了3天,那么交給上級繼續審批
           if(leaveRequest.num>3)
           {
               return nextHandler.process(leaveRequest);
           }
           //請假天數小于3,審批通過
            return true;
        }
        //沒有通過審批
        return false;
    }
}

經理

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,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"批準":"不通過"));
        if(result)
        {
            //請假天數過多,還是需要提交到更高的一級去審批
            if(leaveRequest.getNum()>7)
            {
                return nextHandler.process(leaveRequest);
            }
            //否則直接通過
            return true;
        }
        return false;
    }
}

總經理

public class TopManager extends Handler{
    public TopManager(String name) {
        super(name);
    }

    @Override
    public Boolean process(LeaveRequest leaveRequest) {
        //亂數大于3,就批準請求
        boolean result = (new Random().nextInt(10)) > 3;
        String log = "總經理: %s,審批:%s的請假申請,請假天數:%d,審批結果:%s";
        System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過":"不通過"));
        if(result)//批準
        {
            //默認只有三個處理器,但是如果后續還要加,也需要留個位置
            //如果后續繼續添加
            if(nextHandler!=null)
            {
                return nextHandler.process(leaveRequest);
            }
            return true;
        }
        //沒有通過審批
        return false;
    }
}

處理器鏈類:

//處理器鏈
public class HandlerChain
{
    //維護第一個處理器
    private Handler director=new Director("小忽悠");
    //默認有三個處理器
    public HandlerChain()
    {
        //默認有三個處理器鏈
        //并且這三個處理器有先后關系
        director.nextHandler=new Manager("小朋友");
        director.nextHandler.nextHandler=new TopManager("超級大忽悠");
    }
    //添加一個處理器進集合
    public void addHandler(Handler handler)
    {
        Handler temp=director;
     while(temp.nextHandler!=null)
     {
         temp=temp.nextHandler;
     }
        temp.nextHandler=handler;
    }
    //執行處理器鏈
     public void process(LeaveRequest leaveRequest)
    {
        //第一個處理器,如果可以處理器就不需要交給下一個處理器處理了
        //否則,繼續交給下一個處理器處理
        director.process(leaveRequest);
    }
}

客戶端測驗:

public class Client
{
    public static void main(String[] args) {
        LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10);
        HandlerChain handlerChain=new HandlerChain();
        handlerChain.process(leaveRequest);
    }
}

在這里插入圖片描述


案例類圖

在這里插入圖片描述
與上面所給出的類圖不同的是,我通過一個處理器鏈類,把呼叫處理器鏈處理業務邏輯和客戶端分離開來,進一步解耦


可擴展性

  • 如果此時審批流程還需要加上一步,就非常方便
  • 例如,我們需要增加一個上帝,來對請假流程做最終的處理,那么我們只需要創建一個上帝處理器實作處理器抽象類,然后添加進處理器鏈中即可
public class God extends Handler{
    public God(String name) {
        super(name);
    }

    @Override
    public Boolean process(LeaveRequest leaveRequest) {
        System.out.println("上帝保佑你,所以你可以放假了");
        return true;
    }
}

客戶端:

public class Client
{
    public static void main(String[] args) {
        LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10);
        HandlerChain handlerChain=new HandlerChain();
        handlerChain.addHandler(new God("上帝"));
        handlerChain.process(leaveRequest);
    }
}

在這里插入圖片描述
如果還想繼續添加處理器,就需要在上帝process方法中預留一個介面

這樣很麻煩,我這里沒有繼續對方法抽取,進行解耦,感興趣的小伙伴,可以繼續嘗試解耦


純與不純的責任鏈模式

純的責任鏈模式

  • 一個具體處理者物件只能在兩個行為中選擇一個:要么承擔全部責任,要么將責任推給下家,不允許出現某一個具體處理者物件在承擔了一部分或全部責任后又將責任向下傳遞的情況
  • 一個請求必須被某一個處理者物件所接收,不能出現某個請求未被任何一個處理者物件處理的情況

不純的責任鏈模式

  • 允許某個請求被一個具體處理者部分處理后再向下傳遞
  • 或者一個具體處理者處理完某請求后其后繼處理者可以繼續處理該請求
  • 而且一個請求可以最終不被任何處理者物件所接收

責任鏈模式主要優點

  • 物件僅需知道該請求會被處理即可,且鏈中的物件不需要知道鏈的結構,由客戶端負責鏈的創建,降低了系統的耦合度
  • 請求處理物件僅需維持一個指向其后繼者的參考,而不需要維持它對所有的候選處理者的參考,可簡化物件的相互連接
  • 在給物件分派職責時,職責鏈可以給我們更多的靈活性,可以在運行時對該鏈進行動態的增刪改,改變處理一個請求的職責
  • 新增一個新的具體請求處理者時無須修改原有代碼,只需要在客戶端重新建鏈即可,符合 “開閉原則”

職責鏈模式的主要缺點

  • 一個請求可能因職責鏈沒有被正確配置而得不到處理
  • 對于比較長的職責鏈,請求的處理可能涉及到多個處理物件,系統性能將受到一定影響,且不方便除錯
  • 可能因為職責鏈創建不當,造成回圈呼叫,導致系統陷入死回圈

適用場景

  • 有多個物件可以處理同一個請求,具體哪個物件處理該請求待運行時刻再確定,客戶端只需將請求提交到鏈上,而無須關心請求的處理物件是誰以及它是如何處理的
  • 在不明確指定接收者的情況下,向多個物件中的一個提交一個請求
  • 可動態指定一組物件處理請求,客戶端可以動態創建職責鏈來處理請求,還可以改變鏈中處理者之間的先后次序

模擬實作Tomcat中的過濾器機制

定義封裝請求的類Request和封裝處理結果回應的類Response

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Reponse
{
    private List<String> data=new ArrayList<>();
    public void addData(String data)
    {
        this.data.add(data);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Request
{
   private Object data;
}

第二步:定義具有過濾功能的介面Filter,具體的過濾規則需要實作該介面

/*
 * 定義介面Filter,具體的過濾規則需要實作這個介面,最后一個引數添加的意義是我們在Main函式中:
 * fc.doFilter(request, response,fc);執行這一步的時候可以按照規則鏈條一次使用三個過濾規則對字串進行處理
 */
public interface Filter
{
    void doFilter(Request request,Reponse reponse,FilterChain filterChain);
}

第三步:定義具體的過濾處理規則

public class StuAgeFilter implements Filter
{
    @Override
    public void doFilter(Request request, Reponse reponse, FilterChain filterChain) {
        Stu stu = (Stu) request.getData();
        if(stu.getName().contains("忽悠"))
        {
            //名字不符合要求
            reponse.addData("名字不符合要求");
        }
        //名字符合要求
         reponse.addData("名字符合要求");
        filterChain.doFilter(request,reponse,filterChain);
    }
}


//學生過濾器--過濾出18歲以上的
public class StuFilter implements Filter
{
    @Override
    public void doFilter(Request request, Reponse reponse, FilterChain filterChain) {
        Stu stu = (Stu)request.getData();
        if(stu.getAge()<18)
        {
            //不放行
            reponse.addData("年齡不符合要求");
        }
        //放行
        reponse.addData("年齡滿足要求");
        filterChain.doFilter(request,reponse,filterChain);
    }
}

第四步:定義責任鏈FilterChain

//過濾鏈條
@Data
public class FilterChain
{
    //用List集合來存過濾器
    private List<Filter> filters = new ArrayList<Filter>();
    //用于標記規則的參考順序
   private int index;
   public FilterChain()
   {
       //初始化為0
       index=0;
   }
    //往過濾器鏈條中添加新的過濾器
    public FilterChain addFilter(Filter f)
    {
        filters.add(f);
        //代碼的設計技巧:Chain鏈添加過濾規則結束后回傳添加后的Chain,方便我們下面doFilter函式的操作
        return this;
    }
    public void doFilter(Request request, Reponse response, FilterChain chain){
        //index初始化為0,filters.size()為3,不會執行return操作
        //說明所有過濾器都執行完了
        if(index==filters.size()){
            return;
        }
        //獲取當前過濾器
        Filter f=filters.get(index);
        //下一次獲取的時候,就是下一個過濾器了
        index++;
        //執行當前過濾器的過濾方法
        f.doFilter(request, response, chain);
   }
}

第五步:測驗

public class Client
{
    public static void main(String[] args)
    {
      //創建請求物件
        Request request=new Request();
        request.setData(new Stu("小朋友",19));
        //創建回應物件
        Reponse reponse=new Reponse();
        //創建一個過濾器鏈
        FilterChain filterChain=new FilterChain();
        filterChain.addFilter(new StuAgeFilter());
        filterChain.addFilter(new StuFilter());
        //執行
        filterChain.doFilter(request,reponse,filterChain);
        reponse.getData().forEach(x->{
            System.out.println(x);
        });
    }
}

在這里插入圖片描述


運行程序如下

在這里插入圖片描述


分析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 是我們的過濾器就可以發揮作用了,那么過濾器是怎樣運行的呢?

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

在運行過濾器之前需要完成過濾器的加載和初始化,以及根據配置資訊生成過濾器鏈:

  • 過濾器的加載具體是在 ContextConfig 類的 configureContext 方法中,分別加載 filterfilterMap 的相關資訊,并保存在背景關系環境中
  • 過濾器的初始化在 StandardContext 類的 startInternal 方法中完成,保存在 filterConfigs中并存到背景關系環境中
  • 請求流轉到 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 < nfalse,不進入 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) 方法

畫一個簡要的呼叫堆疊如下所示:
在這里插入圖片描述
ApplicationFilterChain 類扮演了抽象處理者角色,具體處理者角色由各個 Filter 扮演


參考文章

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

責任鏈模式綜述(基礎篇)

責任樹模式(責任鏈模式+策略模式的組合)

淺談springMVC中的設計模式(1)——責任鏈模式

責任鏈設計模式----過濾器模擬實作


轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299973.html

標籤:java

上一篇:編碼之道(四):編碼有術,術中有道

下一篇:徹底理解KMP演算法!【爆肝力作 建議收藏】

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more