欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java設(shè)計(jì)模式責(zé)任鏈模式原理案例詳解

 更新時(shí)間:2021年09月15日 15:40:06   作者:大忽悠愛(ài)忽悠  
一個(gè)事件需要經(jīng)過(guò)多個(gè)對(duì)象處理是一個(gè)挺常見(jiàn)的場(chǎng)景,譬如采購(gòu)審批流程,請(qǐng)假流程,軟件開(kāi)發(fā)中的異常處理流程,web請(qǐng)求處理流程等各種各樣的流程,可以考慮使用責(zé)任鏈模式來(lái)實(shí)現(xiàn)

引言

以請(qǐng)假流程為例,一般公司普通員工的請(qǐng)假流程簡(jiǎn)化如下:

在這里插入圖片描述

普通員工發(fā)起一個(gè)請(qǐng)假申請(qǐng),當(dāng)請(qǐng)假天數(shù)小于3天時(shí)只需要得到主管批準(zhǔn)即可;當(dāng)請(qǐng)假天數(shù)大于3天時(shí),主管批準(zhǔn)后還需要提交給經(jīng)理審批,經(jīng)理審批通過(guò),若請(qǐng)假天數(shù)大于7天還需要進(jìn)一步提交給總經(jīng)理審批。

使用 if-else 來(lái)實(shí)現(xiàn)這個(gè)請(qǐng)假流程的簡(jiǎn)化代碼如下:

public class LeaveApproval
{
    public boolean process(String request, int number) {
        boolean result = handleByDirector(request); // 主管處理
        if (result == false) {  // 主管不批準(zhǔn)
            return false;
        } else if (number < 3) {    // 主管批準(zhǔn)且天數(shù)小于 3
            return true;
        }
        result = handleByManager(request); // 準(zhǔn)管批準(zhǔn)且天數(shù)大于等于 3,提交給經(jīng)理處理
        if (result == false) {   // 經(jīng)理不批準(zhǔn)
            return false;
        } else if (number < 7) { // 經(jīng)理批準(zhǔn)且天數(shù)小于 7
            return true;
        }
        result = handleByTopManager(request);   // 經(jīng)理批準(zhǔn)且天數(shù)大于等于 7,提交給總經(jīng)理處理
        if (result == false) { // 總經(jīng)理不批準(zhǔn)
            return false;
        }
        return true;    // 總經(jīng)理最后批準(zhǔn)
    }
    private boolean handleByDirector(String request)
    {
        // 主管處理該請(qǐng)假申請(qǐng)
        if(request.length()>10)
            return false;
        return true;
    }
    private boolean handleByManager(String request) {
        // 經(jīng)理處理該請(qǐng)假申請(qǐng)
        if(request.length()>5)
        return false;
        return true;
    }
    private boolean handleByTopManager(String request) {
        // 總經(jīng)理處理該請(qǐng)假申請(qǐng)
        if(request.length()>3)
            return false;
        return true;
    }
}

問(wèn)題看起來(lái)很簡(jiǎn)單,三下五除二就搞定,但是該方案存在幾個(gè)問(wèn)題:

  •  LeaveApproval 類(lèi)比較龐大,各個(gè)上級(jí)的審批方法都集中在該類(lèi)中,違反了 “單一職責(zé)原則”,測(cè)試和維護(hù)難度大
  •  當(dāng)需要修改該請(qǐng)假流程,譬如增加當(dāng)天數(shù)大于30天時(shí)還需提交給董事長(zhǎng)處理,必須修改該類(lèi)源代碼(并重新進(jìn)行嚴(yán)格地測(cè)試),違反了"開(kāi)閉原則"
  • 該流程缺乏靈活性,流程確定后不可再修改(除非修改源代碼),客戶(hù)端無(wú)法定制流程

使用責(zé)任鏈模式可以解決上述問(wèn)題。

責(zé)任鏈模式定義

避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。職責(zé)鏈模式是一種對(duì)象行為型模式。

責(zé)任鏈可以是一條直線(xiàn)、一個(gè)環(huán)或者一個(gè)樹(shù)形結(jié)構(gòu),最常見(jiàn)的職責(zé)鏈?zhǔn)侵本€(xiàn)型,即沿著一條單向的鏈來(lái)傳遞請(qǐng)求,如下圖所示。鏈上的每一個(gè)對(duì)象都是請(qǐng)求處理者,責(zé)任鏈模式可以將請(qǐng)求的處理者組織成一條鏈,并讓請(qǐng)求沿著鏈傳遞,由鏈上的處理者對(duì)請(qǐng)求進(jìn)行相應(yīng)的處理。在此過(guò)程中,客戶(hù)端實(shí)際上無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)以及請(qǐng)求的傳遞,只需將請(qǐng)求發(fā)送到鏈上即可,從而實(shí)現(xiàn)請(qǐng)求發(fā)送者和請(qǐng)求處理者解耦。

在這里插入圖片描述

對(duì)責(zé)任鏈的理解,關(guān)鍵在于對(duì)鏈的理解,即包含如下兩點(diǎn):

  •  鏈?zhǔn)且幌盗泄?jié)點(diǎn)的集合,在責(zé)任鏈中,節(jié)點(diǎn)實(shí)質(zhì)上是指請(qǐng)求的處理者;
  • 鏈的各節(jié)點(diǎn)可靈活拆分再重組,在責(zé)任鏈中,實(shí)質(zhì)上就是請(qǐng)求發(fā)送者與請(qǐng)求處理者的解耦。

類(lèi)圖

在這里插入圖片描述

角色

我們可以從責(zé)任鏈模式的結(jié)構(gòu)圖中看到,具體的請(qǐng)求處理者可以有多個(gè),并且所有的請(qǐng)求處理者均具有相同的接口(繼承于同一抽象類(lèi))。 責(zé)任鏈模式主要包含如下兩個(gè)角色

 Handler(抽象處理者):處理請(qǐng)求的接口,一般設(shè)計(jì)為具有抽象請(qǐng)求處理方法的抽象類(lèi),以便于不同的具體處理者進(jìn)行繼承,從而實(shí)現(xiàn)具體的請(qǐng)求處理方法。此外,由于每一個(gè)請(qǐng)求處理者的下家還是一個(gè)處理者,因此抽象處理者本身還包含了一個(gè)本身的引用( successor)作為其對(duì)下家的引用,以便將處理者鏈成一條鏈;

 ConcreteHandler(具體處理者):它是抽象處理者的子類(lèi),可以處理用戶(hù)請(qǐng)求,在具體處理者類(lèi)中實(shí)現(xiàn)了抽象處理者中定義的抽象請(qǐng)求處理方法,在處理請(qǐng)求之前需要進(jìn)行判斷,看是否有相應(yīng)的處理權(quán)限,如果可以處理請(qǐng)求就處理它,否則將請(qǐng)求轉(zhuǎn)發(fā)給后繼者;在具體處理者中可以訪(fǎng)問(wèn)鏈中下一個(gè)對(duì)象,以便請(qǐng)求的轉(zhuǎn)發(fā)。

在責(zé)任鏈模式里,由每一個(gè)請(qǐng)求處理者對(duì)象對(duì)其下家的引用而連接起來(lái)形成一條請(qǐng)求處理鏈。請(qǐng)求將在這條鏈上一直傳遞,直到鏈上的某一個(gè)請(qǐng)求處理者能夠處理此請(qǐng)求。事實(shí)上,發(fā)出這個(gè)請(qǐng)求的客戶(hù)端并不知道鏈上的哪一個(gè)請(qǐng)求處理者將處理這個(gè)請(qǐng)求,這使得系統(tǒng)可以在不影響客戶(hù)端的情況下動(dòng)態(tài)地重新組織鏈和分配責(zé)任。

核心

實(shí)現(xiàn)責(zé)任鏈模式的關(guān)鍵核心是: 在抽象類(lèi) Handler 里面聚合它自己(持有自身類(lèi)型的引用),并在 handleRequest 方法里判斷其是否能夠處理請(qǐng)求。若當(dāng)前處理者無(wú)法處理,則設(shè)置其后繼者并向下傳遞,直至請(qǐng)求被處理。

示例代碼

1、對(duì)請(qǐng)求處理者的抽象

責(zé)任鏈模式的核心在于對(duì)請(qǐng)求處理者的抽象。在實(shí)現(xiàn)過(guò)程中,抽象處理者一般會(huì)被設(shè)定為抽象類(lèi)

其典型實(shí)現(xiàn)代碼如下所示:

public abstract class Handler {
    // protected :維持對(duì)下家的引用
    protected Handler successor;
    public void setSuccessor(Handler successor) {
        this.successor=successor;
    }
    public abstract void handleRequest(String request);
}

上述代碼中,抽象處理者類(lèi)定義了對(duì)下家的引用 (其一般用 protected 進(jìn)行修飾),以便將請(qǐng)求轉(zhuǎn)發(fā)給下家,從而形成一條請(qǐng)求處理鏈。同時(shí),在抽象處理者類(lèi)中還聲明了抽象的請(qǐng)求處理方法,以便由子類(lèi)進(jìn)行具體實(shí)現(xiàn)。

2、對(duì)請(qǐng)求處理者的抽象

具體處理者是抽象處理者的子類(lèi),具體處理者類(lèi)的典型代碼如下:

public class ConcreteHandler extends Handler {
    public void handleRequest(String request) {
        if (請(qǐng)求滿(mǎn)足條件) {
            //處理請(qǐng)求
        }else {
            this.successor.handleRequest(request);  //轉(zhuǎn)發(fā)請(qǐng)求
        }
    }
}

在具體處理類(lèi)中,通過(guò)對(duì)請(qǐng)求進(jìn)行判斷以便做出相應(yīng)的處理,因此,其一般具有兩大作用:

  • 處理請(qǐng)求,不同的具體處理者以不同的形式實(shí)現(xiàn)抽象請(qǐng)求處理方法 handleRequest();
  • 轉(zhuǎn)發(fā)請(qǐng)求,若該請(qǐng)求超出了當(dāng)前處理者類(lèi)的權(quán)限,可以將該請(qǐng)求轉(zhuǎn)發(fā)給下家;

3、責(zé)任鏈的創(chuàng)建

需要注意的是,責(zé)任鏈模式并不創(chuàng)建職責(zé)鏈,職責(zé)鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來(lái)完成,一般由使用該責(zé)任鏈的客戶(hù)端創(chuàng)建。職責(zé)鏈模式降低了請(qǐng)求的發(fā)送者和請(qǐng)求處理者之間的耦合,從而使得多個(gè)請(qǐng)求處理者都有機(jī)會(huì)處理這個(gè)請(qǐng)求。

責(zé)任鏈實(shí)現(xiàn)請(qǐng)假案例

請(qǐng)假信息類(lèi),包含請(qǐng)假人姓名和請(qǐng)假天數(shù)

@Data
@AllArgsConstructor
public class LeaveRequest
{
 String name;//請(qǐng)假人的姓名
 Integer num;//請(qǐng)假天數(shù)
}

抽象處理者類(lèi) Handler,維護(hù)一個(gè) nextHandler 屬性,該屬性為當(dāng)前處理者的下一個(gè)處理者的引用;聲明了抽象方法 process

//抽象處理者
@Data
public abstract class Handler
{
    //維護(hù)自身引用
    protected Handler handler;
   //當(dāng)前處理者的姓名
    protected String name;
    //傳入當(dāng)前處理者的姓名
    public Handler(String name)
    {
        this.name=name;
    }
    //抽象方法,用來(lái)處理請(qǐng)假的請(qǐng)求
    public abstract Boolean process(LeaveRequest leaveRequest);
}

三個(gè)具體處理類(lèi),分別實(shí)現(xiàn)了抽象處理類(lèi)的 process 方法

主管:

public class Director extends Handler{
    public Director(String name) {
        super(name);
    }
    //處理請(qǐng)假的請(qǐng)求
    @Override
    public Boolean process(LeaveRequest leaveRequest) {
       //隨機(jī)數(shù)大于3,就批準(zhǔn)請(qǐng)求
        boolean result = (new Random().nextInt(10)) > 3;
        String log = "主管: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s";
        System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過(guò)":"不通過(guò)"));
        if(result)//批準(zhǔn)
        {
            //如果請(qǐng)假天數(shù),超過(guò)了3天,那么交給上級(jí)繼續(xù)審批
           if(leaveRequest.num>3)
           {
               return nextHandler.process(leaveRequest);
           }
           //請(qǐng)假天數(shù)小于3,審批通過(guò)
            return true;
        }
        //沒(méi)有通過(guò)審批
        return false;
    }
}

經(jīng)理

public class Manager extends Handler{
    public Manager(String name) {
        super(name);
    }
    //處理請(qǐng)假的請(qǐng)求
    @Override
    public Boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // 隨機(jī)數(shù)大于3則為批準(zhǔn),否則不批準(zhǔn)
        String log = "經(jīng)理: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s";
        System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"批準(zhǔn)":"不通過(guò)"));
        if(result)
        {
            //請(qǐng)假天數(shù)過(guò)多,還是需要提交到更高的一級(jí)去審批
            if(leaveRequest.getNum()>7)
            {
                return nextHandler.process(leaveRequest);
            }
            //否則直接通過(guò)
            return true;
        }
        return false;
    }
}

總經(jīng)理

public class TopManager extends Handler{
    public TopManager(String name) {
        super(name);
    }
    @Override
    public Boolean process(LeaveRequest leaveRequest) {
        //隨機(jī)數(shù)大于3,就批準(zhǔn)請(qǐng)求
        boolean result = (new Random().nextInt(10)) > 3;
        String log = "總經(jīng)理: %s,審批:%s的請(qǐng)假申請(qǐng),請(qǐng)假天數(shù):%d,審批結(jié)果:%s";
        System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通過(guò)":"不通過(guò)"));
        if(result)//批準(zhǔn)
        {
            //默認(rèn)只有三個(gè)處理器,但是如果后續(xù)還要加,也需要留個(gè)位置
            //如果后續(xù)繼續(xù)添加
            if(nextHandler!=null)
            {
                return nextHandler.process(leaveRequest);
            }
            return true;
        }
        //沒(méi)有通過(guò)審批
        return false;
    }
}

處理器鏈類(lèi):

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

客戶(hù)端測(cè)試:

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

在這里插入圖片描述

案例類(lèi)圖

在這里插入圖片描述

與上面所給出的類(lèi)圖不同的是,我通過(guò)一個(gè)處理器鏈類(lèi),把調(diào)用處理器鏈處理業(yè)務(wù)邏輯和客戶(hù)端分離開(kāi)來(lái),進(jìn)一步解耦

可擴(kuò)展性

如果此時(shí)審批流程還需要加上一步,就非常方便

例如,我們需要增加一個(gè)上帝,來(lái)對(duì)請(qǐng)假流程做最終的處理,那么我們只需要?jiǎng)?chuàng)建一個(gè)上帝處理器實(shí)現(xiàn)處理器抽象類(lèi),然后添加進(jìn)處理器鏈中即可

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

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

客戶(hù)端:

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

在這里插入圖片描述

如果還想繼續(xù)添加處理器,就需要在上帝process方法中預(yù)留一個(gè)接口

這樣很麻煩,我這里沒(méi)有繼續(xù)對(duì)方法抽取,進(jìn)行解耦,感興趣的小伙伴,可以繼續(xù)嘗試解耦

純與不純的責(zé)任鏈模式

純的責(zé)任鏈模式

  • 一個(gè)具體處理者對(duì)象只能在兩個(gè)行為中選擇一個(gè):要么承擔(dān)全部責(zé)任,要么將責(zé)任推給下家,不允許出現(xiàn)某一個(gè)具體處理者對(duì)象在承擔(dān)了一部分或全部責(zé)任后又將責(zé)任向下傳遞的情況
  • 一個(gè)請(qǐng)求必須被某一個(gè)處理者對(duì)象所接收,不能出現(xiàn)某個(gè)請(qǐng)求未被任何一個(gè)處理者對(duì)象處理的情況

不純的責(zé)任鏈模式

  • 允許某個(gè)請(qǐng)求被一個(gè)具體處理者部分處理后再向下傳遞
  • 或者一個(gè)具體處理者處理完某請(qǐng)求后其后繼處理者可以繼續(xù)處理該請(qǐng)求
  •  而且一個(gè)請(qǐng)求可以最終不被任何處理者對(duì)象所接收

責(zé)任鏈模式主要優(yōu)點(diǎn)

  • 對(duì)象僅需知道該請(qǐng)求會(huì)被處理即可,且鏈中的對(duì)象不需要知道鏈的結(jié)構(gòu),由客戶(hù)端負(fù)責(zé)鏈的創(chuàng)建,降低了系統(tǒng)的耦合度
  • 請(qǐng)求處理對(duì)象僅需維持一個(gè)指向其后繼者的引用,而不需要維持它對(duì)所有的候選處理者的引用,可簡(jiǎn)化對(duì)象的相互連接
  • 在給對(duì)象分派職責(zé)時(shí),職責(zé)鏈可以給我們更多的靈活性,可以在運(yùn)行時(shí)對(duì)該鏈進(jìn)行動(dòng)態(tài)的增刪改,改變處理一個(gè)請(qǐng)求的職責(zé)
  • 新增一個(gè)新的具體請(qǐng)求處理者時(shí)無(wú)須修改原有代碼,只需要在客戶(hù)端重新建鏈即可,符合 “開(kāi)閉原則”

職責(zé)鏈模式的主要缺點(diǎn)

  • 一個(gè)請(qǐng)求可能因職責(zé)鏈沒(méi)有被正確配置而得不到處理
  • 對(duì)于比較長(zhǎng)的職責(zé)鏈,請(qǐng)求的處理可能涉及到多個(gè)處理對(duì)象,系統(tǒng)性能將受到一定影響,且不方便調(diào)試
  • 可能因?yàn)槁氊?zé)鏈創(chuàng)建不當(dāng),造成循環(huán)調(diào)用,導(dǎo)致系統(tǒng)陷入死循環(huán)

適用場(chǎng)景

  •  有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,具體哪個(gè)對(duì)象處理該請(qǐng)求待運(yùn)行時(shí)刻再確定,客戶(hù)端只需將請(qǐng)求提交到鏈上,而無(wú)須關(guān)心請(qǐng)求的處理對(duì)象是誰(shuí)以及它是如何處理的
  •  在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求
  •  可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求,客戶(hù)端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來(lái)處理請(qǐng)求,還可以改變鏈中處理者之間的先后次序

模擬實(shí)現(xiàn)Tomcat中的過(guò)濾器機(jī)制

第一步:定義封裝請(qǐng)求的類(lèi)Request和封裝處理結(jié)果響應(yīng)的類(lèi)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;
}

第二步:定義具有過(guò)濾功能的接口Filter,具體的過(guò)濾規(guī)則需要實(shí)現(xiàn)該接口

/*
 * 定義接口Filter,具體的過(guò)濾規(guī)則需要實(shí)現(xiàn)這個(gè)接口,最后一個(gè)參數(shù)添加的意義是我們?cè)贛ain函數(shù)中:
 * fc.doFilter(request, response,fc);執(zhí)行這一步的時(shí)候可以按照規(guī)則鏈條一次使用三個(gè)過(guò)濾規(guī)則對(duì)字符串進(jìn)行處理
 */
public interface Filter
{
    void doFilter(Request request,Reponse reponse,FilterChain filterChain);
}

第三步:定義具體的過(guò)濾處理規(guī)則

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);
    }
}
//學(xué)生過(guò)濾器--過(guò)濾出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("年齡滿(mǎn)足要求");
        filterChain.doFilter(request,reponse,filterChain);
    }
}

第四步:定義責(zé)任鏈FilterChain

//過(guò)濾鏈條
@Data
public class FilterChain
{
    //用List集合來(lái)存過(guò)濾器
    private List<Filter> filters = new ArrayList<Filter>();
    //用于標(biāo)記規(guī)則的引用順序
   private int index;
   public FilterChain()
   {
       //初始化為0
       index=0;
   }
    //往過(guò)濾器鏈條中添加新的過(guò)濾器
    public FilterChain addFilter(Filter f)
    {
        filters.add(f);
        //代碼的設(shè)計(jì)技巧:Chain鏈添加過(guò)濾規(guī)則結(jié)束后返回添加后的Chain,方便我們下面doFilter函數(shù)的操作
        return this;
    }
    public void doFilter(Request request, Reponse response, FilterChain chain){
        //index初始化為0,filters.size()為3,不會(huì)執(zhí)行return操作
        //說(shuō)明所有過(guò)濾器都執(zhí)行完了
        if(index==filters.size()){
            return;
        }
        //獲取當(dāng)前過(guò)濾器
        Filter f=filters.get(index);
        //下一次獲取的時(shí)候,就是下一個(gè)過(guò)濾器了
        index++;
        //執(zhí)行當(dāng)前過(guò)濾器的過(guò)濾方法
        f.doFilter(request, response, chain);
   }
}

第五步:測(cè)試

public class Client
{
    public static void main(String[] args)
    {
      //創(chuàng)建請(qǐng)求對(duì)象
        Request request=new Request();
        request.setData(new Stu("小朋友",19));
        //創(chuàng)建響應(yīng)對(duì)象
        Reponse reponse=new Reponse();
        //創(chuàng)建一個(gè)過(guò)濾器鏈
        FilterChain filterChain=new FilterChain();
        filterChain.addFilter(new StuAgeFilter());
        filterChain.addFilter(new StuFilter());
        //執(zhí)行
        filterChain.doFilter(request,reponse,filterChain);
        reponse.getData().forEach(x->{
            System.out.println(x);
        });
    }
}

在這里插入圖片描述

運(yùn)行過(guò)程如下

在這里插入圖片描述

分析Tomcat 過(guò)濾器中的責(zé)任鏈模式

Servlet 過(guò)濾器是可用于 Servlet 編程的 Java 類(lèi),可以實(shí)現(xiàn)以下目的:在客戶(hù)端的請(qǐng)求訪(fǎng)問(wèn)后端資源之前,攔截這些請(qǐng)求;在服務(wù)器的響應(yīng)發(fā)送回客戶(hù)端之前,處理這些響應(yīng)。

Servlet 定義了過(guò)濾器接口 Filter 和過(guò)濾器鏈接口 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;
}

我們自定義一個(gè)過(guò)濾器的步驟是:

1)寫(xiě)一個(gè)過(guò)濾器類(lèi),實(shí)現(xiàn) javax.servlet.Filter 接口,如下所示

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        // 做一些自定義處理....
        System.out.println("執(zhí)行doFilter()方法之前...");
        chain.doFilter(request, response);              // 傳遞請(qǐng)求給下一個(gè)過(guò)濾器
        System.out.println("執(zhí)行doFilter()方法之后...");
    }
    @Override
    public void destroy() {
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

2)在 web.xml 文件中增加該過(guò)濾器的配置,譬如下面是攔截所有請(qǐng)求

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

當(dāng)啟動(dòng) Tomcat 是我們的過(guò)濾器就可以發(fā)揮作用了。那么過(guò)濾器是怎樣運(yùn)行的呢?

TomcatPipeline Valve機(jī)制,也是使用了責(zé)任鏈模式,一個(gè)請(qǐng)求會(huì)在 Pipeline 中流轉(zhuǎn),Pipeline 會(huì)調(diào)用相應(yīng)的 Valve 完成具體的邏輯處理;
其中的一個(gè)基礎(chǔ)ValveStandardWrapperValve,其中的一個(gè)作用是調(diào)用 ApplicationFilterFactory 生成 Filter鏈,具體代碼在 invoke 方法中

在運(yùn)行過(guò)濾器之前需要完成過(guò)濾器的加載和初始化,以及根據(jù)配置信息生成過(guò)濾器鏈:

  • 過(guò)濾器的加載具體是在 ContextConfig 類(lèi)的 configureContext 方法中,分別加載 filterfilterMap 的相關(guān)信息,并保存在上下文環(huán)境中
  • 過(guò)濾器的初始化在 StandardContext 類(lèi)的 startInternal 方法中完成,保存在 filterConfigs中并存到上下文環(huán)境中
  • 請(qǐng)求流轉(zhuǎn)到 StandardWrapperValve 時(shí),在 invoke 方法中,會(huì)根據(jù)過(guò)濾器映射配置信息,為每個(gè)請(qǐng)求創(chuàng)建對(duì)ApplicationFilterChain,其中包含了目標(biāo) Servlet 以及對(duì)應(yīng)的過(guò)濾器鏈,并調(diào)用過(guò)濾器鏈的 doFilter 方法執(zhí)行過(guò)濾器

StandardWrapperValve 調(diào)用 ApplicationFilterFactory 為請(qǐng)求創(chuàng)建過(guò)濾器鏈并調(diào)用過(guò)濾器鏈的關(guān)鍵代碼如下:

final class StandardWrapperValve extends ValveBase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        // 省略其他的邏輯處理...
        // 調(diào)用 ApplicationFilterChain.createFilterChain() 創(chuàng)建過(guò)濾器鏈
        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 {
            // 調(diào)用過(guò)濾器鏈的 doFilter 方法開(kāi)始過(guò)濾
            filterChain.doFilter(request.getRequest(), response.getResponse());
        }

過(guò)濾器鏈 ApplicationFilterChain 的關(guān)鍵代碼如下,過(guò)濾器鏈實(shí)際是一個(gè) ApplicationFilterConfig 數(shù)組

final class ApplicationFilterChain implements FilterChain, CometFilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 過(guò)濾器鏈
    private Servlet servlet = null; // 目標(biāo)
    // ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            // ...
        } else {
            internalDoFilter(request,response); // 調(diào)用 internalDoFilter 方法
        }
    }
    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            // 從過(guò)濾器數(shù)組中取出當(dāng)前過(guò)濾器配置,然后下標(biāo)自增1
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();  // 從過(guò)濾器配置中取出該 過(guò)濾器對(duì)象
                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 {
                    // 調(diào)用過(guò)濾器的 doFilter,完成一個(gè)過(guò)濾器的過(guò)濾功能
                    filter.doFilter(request, response, this);
                }
            return;  // 這里很重要,不會(huì)重復(fù)執(zhí)行后面的  servlet.service(request, response)
        }
        // 執(zhí)行完過(guò)濾器鏈的所有過(guò)濾器之后,調(diào)用 Servlet 的 service 完成請(qǐng)求的處理
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
            if( Globals.IS_SECURITY_ENABLED ) {

            } else {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
    }
    // 省略...
}

過(guò)濾器

 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("執(zhí)行doFilter()方法之前...");
        chain.doFilter(request, response);              // 傳遞請(qǐng)求給下一個(gè)過(guò)濾器
        System.out.println("執(zhí)行doFilter()方法之后...");
    }

當(dāng)下標(biāo)小于過(guò)濾器數(shù)組長(zhǎng)度 n 時(shí),說(shuō)明過(guò)濾器鏈未執(zhí)行完,所以從數(shù)組中取出當(dāng)前過(guò)濾器,調(diào)用過(guò)濾器的 doFilter 方法完成過(guò)濾處理,在過(guò)濾器的 doFilter 中又調(diào)用 FilterChaindoFilter,回到 ApplicationFilterChain,又繼續(xù)根據(jù)下標(biāo)是否小于數(shù)組長(zhǎng)度來(lái)判斷過(guò)濾器鏈?zhǔn)欠褚褕?zhí)行完,未完則繼續(xù)從數(shù)組取出過(guò)濾器并調(diào)用 doFilter 方法,所以這里的過(guò)濾鏈?zhǔn)峭ㄟ^(guò)嵌套遞歸的方式來(lái)串成一條鏈。

當(dāng)全部過(guò)濾器都執(zhí)行完畢,最后一次進(jìn)入 ApplicationFilterChain.doFilter 方法的時(shí)候 pos < nfalse,不進(jìn)入 if (pos < n) 中,而是執(zhí)行后面的代碼,判斷 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse),若為 http 請(qǐng)求則調(diào)用 servlet.service(request, response); 來(lái)處理該請(qǐng)求。

處理完畢之后沿著調(diào)用過(guò)濾器的順序反向退棧,分別執(zhí)行過(guò)濾器中 chain.doFilter() 之后的處理邏輯,需要注意的是在 if (pos < n) 方法體的最后有一個(gè) return;,這樣就保證了只有最后一次進(jìn)入 ApplicationFilterChain.doFilter 方法的調(diào)用能夠執(zhí)行后面的 servlet.service(request, response) 方法

畫(huà)一個(gè)簡(jiǎn)要的調(diào)用棧如下所示:

在這里插入圖片描述

ApplicationFilterChain 類(lèi)扮演了抽象處理者角色,具體處理者角色由各個(gè) Filter 扮演

以上就是java設(shè)計(jì)模式責(zé)任鏈模式原理案例詳解的詳細(xì)內(nèi)容,更多關(guān)于java設(shè)計(jì)模式責(zé)任鏈模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java web支持jsonp的實(shí)現(xiàn)代碼

    java web支持jsonp的實(shí)現(xiàn)代碼

    這篇文章主要介紹了java web支持jsonp的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Feign如何使用protobuf的類(lèi)作為參數(shù)調(diào)用

    Feign如何使用protobuf的類(lèi)作為參數(shù)調(diào)用

    這篇文章主要介紹了Feign如何使用protobuf的類(lèi)作為參數(shù)調(diào)用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法

    SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法

    這篇文章主要介紹了SpringBoot Logback日志記錄到數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • SpringMVC Validator驗(yàn)證示例

    SpringMVC Validator驗(yàn)證示例

    SpringMVC服務(wù)器驗(yàn)證一種是有兩種方式,一種是基于Validator接口,一種是使用Annotaion JSR-303標(biāo)準(zhǔn)的驗(yàn)證,本篇文章主要介紹,有興趣的可以了解一下。
    2017-01-01
  • 深入了解Java線(xiàn)程池:從設(shè)計(jì)思想到源碼解讀

    深入了解Java線(xiàn)程池:從設(shè)計(jì)思想到源碼解讀

    這篇文章將從設(shè)計(jì)思想到源碼解讀,帶大家深入了解Java的線(xiàn)程池,文中的示例代碼講解詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的可以參考一下
    2021-12-12
  • Java加載本地庫(kù)的方法之System.load與System.loadLibrary

    Java加載本地庫(kù)的方法之System.load與System.loadLibrary

    最近在做的工作要用到本地方法,所以下面這篇文章主要介紹了Java加載本地庫(kù)的方法之System.load與System.loadLibrary的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09
  • java配置文件取值的多種方式總結(jié)

    java配置文件取值的多種方式總結(jié)

    這篇文章主要為大家詳細(xì)介紹了java配置文件取值的多種方式,包括一般項(xiàng)目,國(guó)際化項(xiàng)目,springboot項(xiàng)目,文中的示例代碼講解詳細(xì),需要的可以參考下
    2023-11-11
  • SpringBoot Mybatis如何配置多數(shù)據(jù)源并分包

    SpringBoot Mybatis如何配置多數(shù)據(jù)源并分包

    這篇文章主要介紹了SpringBoot Mybatis如何配置多數(shù)據(jù)源并分包,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Spring實(shí)戰(zhàn)之獲得Bean本身的id操作示例

    Spring實(shí)戰(zhàn)之獲得Bean本身的id操作示例

    這篇文章主要介紹了Spring實(shí)戰(zhàn)之獲得Bean本身的id操作,結(jié)合實(shí)例形式分析了spring獲取Bean本身id的相關(guān)配置與實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2019-11-11
  • Spring Boot詳解配置文件有哪些作用與細(xì)則

    Spring Boot詳解配置文件有哪些作用與細(xì)則

    SpringBoot項(xiàng)目是一個(gè)標(biāo)準(zhǔn)的Maven項(xiàng)目,它的配置文件需要放在src/main/resources/下,其文件名必須為application,其存在兩種文件形式,分別是properties和yaml(或者yml)文件
    2022-07-07

最新評(píng)論