聊聊如何實現(xiàn)一個特別的責(zé)任鏈

前言

什么是責(zé)任鏈

責(zé)任鏈?zhǔn)且环N設(shè)計模式阱佛,它讓多個對象有機(jī)會處理同一個請求,這些對象形成一個鏈缚柏。請求從鏈的一端開始,逐個傳遞給鏈上的對象碟贾,直到某個對象處理它或者請求未被處理币喧。這樣,發(fā)送請求者無需知道哪個對象會處理袱耽,實現(xiàn)了發(fā)送者與接收者的解耦杀餐,增加了系統(tǒng)的靈活性

責(zé)任鏈的常用場景

  • 權(quán)限與認(rèn)證系統(tǒng):在登錄認(rèn)證、權(quán)限驗證流程中朱巨,不同的處理者可以檢查用戶名密碼怜浅、驗證權(quán)限級別、處理單點登錄等蔬崩。請求(如訪問資源)沿著責(zé)任鏈傳遞恶座,直到找到合適的處理器來授權(quán)或拒絕訪問。
  • 日志記錄與錯誤處理:根據(jù)日志級別(如DEBUG, INFO, WARNING,
    ERROR)或錯誤類型沥阳,不同的處理器負(fù)責(zé)記錄或處理相應(yīng)的日志信息或異常跨琳。開發(fā)者可以靈活地插入或移除處理邏輯,而不影響其他日志處理桐罕。
  • 請求過濾與處理:在Web服務(wù)或API網(wǎng)關(guān)中脉让,責(zé)任鏈可用于實現(xiàn)一系列預(yù)處理任務(wù),如參數(shù)校驗功炮、請求限流溅潜、IP黑名單過濾、會話管理等薪伏,每個處理環(huán)節(jié)關(guān)注于特定的驗證或轉(zhuǎn)換邏輯滚澜。
  • 命令與事件處理:在處理一系列可選的或有順序依賴的命令或事件時,責(zé)任鏈允許按照預(yù)定的邏輯順序嘗試執(zhí)行處理邏輯嫁怀,直到命令被執(zhí)行或事件被妥善處理设捐。
  • 工作流與審批流程:在企業(yè)應(yīng)用中借浊,審批流程可以設(shè)計成責(zé)任鏈,每個節(jié)點代表一個審批層級或角色萝招,請求(如報銷單)依次經(jīng)過各個審批節(jié)點蚂斤,直至最終批準(zhǔn)或拒絕。
  • UI事件處理:在圖形界面應(yīng)用程序中槐沼,事件(如鼠標(biāo)點擊曙蒸、鍵盤輸入)可以通過責(zé)任鏈分發(fā)給不同的組件,每個組件決定是否消費此事件岗钩,未處理的事件繼續(xù)傳遞給鏈中的下一個組件逸爵。

今天給大家的介紹的責(zé)任鏈有點特殊,它是基于Pipeline-Valve模型凹嘲,這種模型跟常規(guī)的責(zé)任鏈模式有點區(qū)別

  • 每個Pipeline都是有特定的Valve师倔,而且是在管道的最后一個執(zhí)行,這個Valve叫BaseValve周蹭,并且BaseValve是不可刪除的趋艘;
  • 在上層容器的管道的BaseValve中會調(diào)用下層容器的管道。

示例圖:

ade76302cbfbe1d5da6e65b1bcb0e194_cb210ea701e6ba81adb4dbae15d6f022.png

如何實現(xiàn)Pipeline-Valve模型

1凶朗、定義Valve接口

該接口表示處理鏈中的單個處理單元瓷胧。

public interface Valve extends Ordered {

    Valve getNextValve();

    void setNextValve(Valve next);

    void invoke(ValveContext context);

    default boolean isBaiscValve() {
        return false;
    }


}

2、定義抽象valve(可選)

注:定義該抽象valve主要是為了復(fù)用

public abstract class AbstractValve implements Valve {

    protected Valve nextValve;

    @Override
    public Valve getNextValve() {
        return nextValve;
    }

    @Override
    public void setNextValve(Valve next) {
       this.nextValve = next;
    }

    @Override
    public void invoke(ValveContext context) {
         doInvoke(context);
         if(nextValve!=null){
             nextValve.invoke(context);
         }
    }

    public abstract void doInvoke(ValveContext context);


}

3棚愤、 定義Pipeline接口

該接口主要用來用于管理Valve的集合搓萧,并提供方法來添加Valve、設(shè)置特定valve以及啟動處理流程宛畦。

public interface Pipeline {

    void setBasic(Valve valve);

    void addValve(Valve valve);

    void process(ValveContext context);
}

4瘸洛、定義Pipeline的默認(rèn)實現(xiàn)

public class StandardPipeline implements Pipeline {
    /**
     * 第一個閥門
     */
    protected Valve first;

    /**
     * 最后一個閥門
     */
    protected Valve basic;


    @Override
    public void setBasic(Valve valve) {
        validateValve(valve,true);
        this.basic = valve;
    }

    @Override
    public void addValve(Valve valve) {
        validateValve(valve,false);
        if(first == null){
            this.first = valve;
            valve.setNextValve(basic);
        }else{
            Valve current = first;
            while(current != null){
               if(current.getNextValve() == basic){
                   current.setNextValve(valve);
                   valve.setNextValve(basic);
                   break;
               }
               current = current.getNextValve();
            }
        }

    }

    @Override
    public void process(ValveContext context) {
        if(first != null){
            if(context == null){
                context = new ValveContext();
            }
            first.invoke(context);
        }

    }

    public void validateValve(Valve valve,boolean isCheckBasicValve){
        Assert.notNull(valve, "valve must not be null");
        if(isCheckBasicValve){
            Assert.isTrue(valve.isBaiscValve(), "valve must be basic valve");
        }
    }
}

注: addValue else流程,是為了保證basic閥門一定是在流程最后被調(diào)用

5次和、定義具體valve

@Component
public class FirstValve extends AbstractValve {
    @Override
    public void doInvoke(ValveContext context) {
        String requestId = "lybgeek-" + UUID.randomUUID().toString();
        System.out.println("第一道閥門: requestId-->【" + requestId + "】");
        Map<String,Object> request = context.getRequest();
        request.put("source",FirstValve.class.getSimpleName());
        context.setRequest(request);
        context.setRequestId(requestId);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}



其他閥門類似反肋,就不列舉了

6、通過Pipeline 驅(qū)動valve

public class PipelineMainTest {

    public static void main(String[] args) {
        Pipeline pipeline = new StandardPipeline();
        pipeline.setBasic(new BasicValve());
        pipeline.addValve(new FirstValve());
        pipeline.addValve(new SecondValve());
        pipeline.addValve(new ThirdValve());
        pipeline.process(new ValveContext());
    }
}
2d98e815bd09b594c7e63bfb8c715419_d4c89d88640c5f67bbc8a88edd73a93b.png

總結(jié)

如果大家對tomcat有了解的話踏施,就會知道本文的實現(xiàn)其實就是tomcat的pipeline-valve的簡化版實現(xiàn)石蔗。其次上文pipeline驅(qū)動valve的步驟可以托管給spring,文末demo鏈接的代碼畅形,有做了相應(yīng)實現(xiàn)养距,感興趣的朋友,可以點擊文末鏈接查看

demo鏈接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-pipeline-valve

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末日熬,一起剝皮案震驚了整個濱河市棍厌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖定铜,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阳液,死亡現(xiàn)場離奇詭異怕敬,居然都是意外死亡揣炕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門东跪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畸陡,“玉大人,你說我怎么就攤上這事虽填《」В” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵斋日,是天一觀的道長牲览。 經(jīng)常有香客問我,道長恶守,這世上最難降的妖魔是什么第献? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮兔港,結(jié)果婚禮上庸毫,老公的妹妹穿的比我還像新娘。我一直安慰自己衫樊,他們只是感情好飒赃,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著科侈,像睡著了一般载佳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臀栈,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天刚盈,我揣著相機(jī)與錄音,去河邊找鬼挂脑。 笑死藕漱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崭闲。 我是一名探鬼主播肋联,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刁俭!你這毒婦竟也來了橄仍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侮繁,沒想到半個月后虑粥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡宪哩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年娩贷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锁孟。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡彬祖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出品抽,到底是詐尸還是另有隱情储笑,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布圆恤,位于F島的核電站突倍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盆昙。R本人自食惡果不足惜羽历,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弱左。 院中可真熱鬧窄陡,春花似錦、人聲如沸拆火。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽们镜。三九已至币叹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間模狭,已是汗流浹背颈抚。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嚼鹉,地道東北人贩汉。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像锚赤,于是被迫代替她去往敵國和親匹舞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容