折騰Java設(shè)計模式之責(zé)任鏈模式

責(zé)任鏈模式

顧名思義鹉究,責(zé)任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈懂版。這種模式給予請求的類型冠场,對請求的發(fā)送者和接收者進行解耦炬丸。這種類型的設(shè)計模式屬于行為型模式。在這種模式中初嘹,通常每個接收者都包含對另一個接收者的引用及汉。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者屯烦,依此類推坷随。

簡介

意圖 避免請求發(fā)送者與接收者耦合在一起,讓多個對象都有可能接收請求驻龟,將這些對象連接成一條鏈温眉,并且沿著這條鏈傳遞請求,直到有對象處理它為止翁狐。

主要解決 職責(zé)鏈上的處理者負責(zé)處理請求类溢,客戶只需要將請求發(fā)送到職責(zé)鏈上即可,無須關(guān)心請求的處理細節(jié)和請求的傳遞露懒,所以職責(zé)鏈將請求的發(fā)送者和請求的處理者解耦了闯冷。

何時使用 在處理消息的時候以過濾很多道。

如何解決 攔截的類都實現(xiàn)統(tǒng)一接口懈词。

關(guān)鍵代碼 Handler 里面聚合它自己蛇耀,在 handleRequest 里判斷是否合適,如果沒達到條件則向下傳遞坎弯。

純責(zé)任鏈與不純責(zé)任鏈

  • 純:純責(zé)任鏈中的節(jié)點只有兩種行為纺涤,一處理責(zé)任躁倒,二將責(zé)任傳遞到下一個節(jié)點。不允許出現(xiàn)某一個節(jié)點處理部分或全部責(zé)任后又將責(zé)任向下傳遞的情況洒琢。

  • 不純:允許某個請求被一個節(jié)點處理部分責(zé)任后再向下傳遞,或者處理完后其后續(xù)節(jié)點可以繼續(xù)處理該責(zé)任褐桌,而且一個責(zé)任可以最終不被任何節(jié)點所處理衰抑。

主要角色

  • Handler(抽象處理者): 定義一個處理請求的接口,提供對后續(xù)處理者的引用
  • ConcreteHandler(具體處理者): 抽象處理者的子類荧嵌,處理用戶請求呛踊,可選將請求處理掉還是傳給下家;在具體處理者中可以訪問鏈中下一個對象啦撮,以便請求的轉(zhuǎn)發(fā)

應(yīng)用實例

1谭网、紅樓夢中的"擊鼓傳花”。
2赃春、JS 中的事件冒泡愉择。
3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理织中,Struts2 的攔截器锥涕,jsp servlet 的 Filter。

優(yōu)點

1狭吼、降低耦合度层坠。它將請求的發(fā)送者和接收者解耦。
2刁笙、簡化了對象破花。使得對象不需要知道鏈的結(jié)構(gòu)。
3疲吸、增強給對象指派職責(zé)的靈活性座每。通過改變鏈內(nèi)的成員或者調(diào)動它們的次序,允許動態(tài)地新增或者刪除責(zé)任摘悴。
4尺栖、增加新的請求處理類很方便。

缺點

1烦租、不能保證請求一定被接收延赌。
2、系統(tǒng)性能將受到一定影響叉橱,而且在進行代碼調(diào)試時不太方便挫以,可能會造成循環(huán)調(diào)用。
3窃祝、可能不容易觀察運行時的特征掐松,有礙于除錯。

使用場景

1、有多個對象可以處理同一個請求大磺,具體哪個對象處理該請求由運行時刻自動確定抡句。
2、在不明確指定接收者的情況下杠愧,向多個對象中的一個提交一個請求待榔。
3、可動態(tài)指定一組對象處理請求流济。

Github項目描述

跳轉(zhuǎn)到我的責(zé)任鏈設(shè)計模式源碼

1.出行方式

travel包里主要對出行方式的責(zé)任鏈模式锐锣。跟進用戶身上的錢,在優(yōu)先級如飛機->火車->大巴的順序下選擇對應(yīng)的出行模式绳瘟。


public class Application {

public static void main(String[] args) {

Handler planeHandler = new PlaneHandler();

Handler trainHandler = new TrainHandler();

Handler busHandler = new BusHandler();

planeHandler.setNext(trainHandler);

trainHandler.setNext(busHandler);

planeHandler.handleRequest("老王", 40d);

planeHandler.handleRequest("張三", 140d);

planeHandler.handleRequest("李四", 240d);

planeHandler.handleRequest("吳老五", 340d);

}

}

img

抽象處理


@Data

public abstract class Handler {

/**

* 下一個鏈節(jié)點

*/

protected Handler next;

public abstract void handleRequest(String name, Double wallet);

}

具體的處理者(飛機雕憔、火車、大巴)


@Slf4j

public class PlaneHandler extends Handler {

private double price = 280d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的錢可以坐飛機糖声。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}錢不夠斤彼,只能徒步啦", name);

}

}


@Slf4j

public class TrainHandler extends Handler {

private double price = 149.99d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的錢只能坐火車。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}錢不夠蘸泻,只能徒步啦", name);

}

}


@Slf4j

public class BusHandler extends Handler {

private double price = 59.99d;

@Override

public void handleRequest(String name, Double wallet) {

if (price <= wallet) {

log.info("{}身上的錢只能坐大巴畅卓。", name);

return;

}

if (Objects.nonNull(next)) {

next.handleRequest(name, wallet);

return;

}

log.info("{}錢不夠,只能徒步啦", name);

}

}

2.出行方式2蟋恬,參考Filter鏈的寫法

travel2包是對travel包的重新寫法翁潘。


public class Application {

public static void main(String[] args) {

HandlerChain chain = new HandlerChain();

Handler planeHandler = new PlaneHandler();

Handler trainHandler = new TrainHandler();

Handler busHandler = new BusHandler();

chain.addHandler(planeHandler);

chain.addHandler(trainHandler);

chain.addHandler(busHandler);

chain.handle("老王", 40d);

chain.handle("張三", 140d);

chain.handle("李四", 240d);

chain.handle("吳老五", 340d);

}

}

image-20181223222027677

抽象處理者


public interface Handler {

void handleRequest(String name, Double wallet, HandlerChain chain);

}

具體處理者(飛機、火車歼争、大巴)


@Slf4j

public class PlaneHandler implements Handler {

private double price = 280d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的錢可以坐飛機拜马。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}


@Slf4j

public class TrainHandler implements Handler {

private double price = 149.99d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的錢只能坐火車。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}


@Slf4j

public class BusHandler implements Handler {

private double price = 59.99d;

@Override

public void handleRequest(String name, Double wallet, HandlerChain chain) {

if (price <= wallet) {

log.info("{}身上的錢只能坐大巴沐绒。", name);

chain.reuse();

return;

}

chain.handle(name, wallet);

}

}

責(zé)任鏈管理者


@Slf4j

public class HandlerChain {

private List handlerList = new ArrayList<>();

/**

* 維護當(dāng)前鏈上位置

*/

private int pos;

/**

* 鏈的長度

*/

private int handlerLength;

public void addHandler(Handler handler) {

handlerList.add(handler);

handlerLength = handlerList.size();

}

public void handle(String name, double wallet) {

if (CollectionUtils.isEmpty(handlerList)) {

log.error("有錢俩莽,但沒提供服務(wù),{}也估計就只能步行了乔遮。", name);

return;

}

if (pos >= handlerLength) {

log.error("身上錢不夠扮超,{}也估計就只能步行了。", name);

reuse();

return;

}

Handler handler = handlerList.get(pos++);

if (Objects.isNull(handler)) {

log.error("假服務(wù)蹋肮,{}也估計就只能步行了出刷。", name);

reuse();

return;

}

handler.handleRequest(name, wallet, this);

}

/**

* 鏈重新使用

*/

public void reuse() {

pos = 0;

}

}

學(xué)習(xí)Web項目的Filter

待補充…

補充補充遺留的Filter過濾器中的責(zé)任鏈處理。

本次主要是對Tomcat中的Filter處理簡單的梳理坯辩,如有不正確的地方馁龟,還望指出來,大家互勉漆魔,共進坷檩。

老項目大家可以在web.xml中配置filter却音,現(xiàn)使用Springboot后,也有兩種配置filter方式矢炼,通過創(chuàng)建FilterRegistrationBean的方式和通過注解@WebFilter+@ServletComponentScan的方式系瓢。

三個主要的角色

FIlter,不多介紹了句灌。

FilterChain servlet容器提供的開發(fā)調(diào)用鏈的過濾請求的資源夷陋。通過調(diào)用下一個filter實現(xiàn)過濾,在整體鏈上涯塔。

FilterConfig filter的配置器,在servlet容器在Filter初始化的時候傳遞信息清蚀。

具體的filter匕荸,主要說說Spring中的兩個抽象Filter,GenericFilterBean和OncePerRequestFilter枷邪。

前者主要是做init和destroy的操作榛搔,重點還是init方法,destroy只是空實現(xiàn)而已东揣。

后者主要是做真正的doFilter操作践惑,也是我們在Spring中創(chuàng)建Filter通常繼承的。

而ApplicationFilterChain就算Tomcat中的FilterChain實現(xiàn)嘶卧。


/**

* The int which is used to maintain the current position

* in the filter chain.

*/

private int pos = 0;

/**

* The int which gives the current number of filters in the chain.

*/

private int n = 0;

@Override

public void doFilter(ServletRequest request, ServletResponse response)

throws IOException, ServletException {

//安全相關(guān)的尔觉,暫不關(guān)注

if( Globals.IS_SECURITY_ENABLED ) {

final ServletRequest req = request;

final ServletResponse res = response;

try {

java.security.AccessController.doPrivileged(

new java.security.PrivilegedExceptionAction() {

@Override

public Void run()

throws ServletException, IOException {

internalDoFilter(req,res);

return null;

}

}

);

} catch( PrivilegedActionException pe) {

Exception e = pe.getException();

if (e instanceof ServletException)

throw (ServletException) e;

else if (e instanceof IOException)

throw (IOException) e;

else if (e instanceof RuntimeException)

throw (RuntimeException) e;

else

throw new ServletException(e.getMessage(), e);

}

} else {

//真正的doFilter

internalDoFilter(request,response);

}

}

private void internalDoFilter(ServletRequest request,

ServletResponse response)

throws IOException, ServletException {

//pos 調(diào)用鏈中當(dāng)前連接點所在的位置

//n 調(diào)用鏈總節(jié)點長度

// Call the next filter if there is one

if (pos < n) {

//對節(jié)點進行自增 pos++

ApplicationFilterConfig filterConfig = filters[pos++];

try {

//當(dāng)前節(jié)點小于總長度后,從filter配置類中取出filter

Filter filter = filterConfig.getFilter();

if (request.isAsyncSupported() && "false”.equalsIgnoreCase(

filterConfig.getFilterDef().getAsyncSupported())) {

request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);

}

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 {

//真正的filter

filter.doFilter(request, response, this);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.filter"), e);

}

return;

}

// We fell off the end of the chain -- call the servlet instance

//到了調(diào)用鏈結(jié)尾處芥吟,就真正調(diào)用servlet實例的servlet.service(request, response);

try {

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {

lastServicedRequest.set(request);

lastServicedResponse.set(response);

}

if (request.isAsyncSupported() && !servletSupportsAsync) {

request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,

Boolean.FALSE);

}

// Use potentially wrapped request from this point

if ((request instanceof HttpServletRequest) &&

(response instanceof HttpServletResponse) &&

Globals.IS_SECURITY_ENABLED ) {

final ServletRequest req = request;

final ServletResponse res = response;

Principal principal =

((HttpServletRequest) req).getUserPrincipal();

Object[] args = new Object[]{req, res};

SecurityUtil.doAsPrivilege(“service”,

servlet,

classTypeUsedInService,

args,

principal);

} else {

servlet.service(request, response);

}

} catch (IOException | ServletException | RuntimeException e) {

throw e;

} catch (Throwable e) {

e = ExceptionUtils.unwrapInvocationTargetException(e);

ExceptionUtils.handleThrowable(e);

throw new ServletException(sm.getString("filterChain.servlet"), e);

} finally {

if (ApplicationDispatcher.WRAP_SAME_OBJECT) {

lastServicedRequest.set(null);

lastServicedResponse.set(null);

}

}

}

/**

* Prepare for reuse of the filters and wrapper executed by this chain.

* 重復(fù)使用filter調(diào)用鏈侦铜,pos重設(shè)為0

*/

void reuse() {

pos = 0;

}

重點從ApplicationFilterChain中挑出幾個重要的方法拿出來分析下Filter的調(diào)用鏈,其實還有幾處沒有具體講到钟鸵,ApplicationFilterChain是合適創(chuàng)建的钉稍,F(xiàn)ilter是怎么加入到ApplicationFilterChain中的。這涉及到Tomcat是怎樣加載Content的棺耍,下次分析Tomcat的時候贡未,再來具體分析,它是如何運作的蒙袍,如何加載web.xml俊卤。

參考

維基的責(zé)任鏈模式

責(zé)任鏈模式|菜鳥教程

Filter、FilterConfig害幅、FilterChain|菜鳥教程

南鄉(xiāng)清水的實際項目運用之Responsibility-Chain模式

一起學(xué)設(shè)計模式 - 責(zé)任鏈模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瘾蛋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子矫限,更是在濱河造成了極大的恐慌哺哼,老刑警劉巖佩抹,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異取董,居然都是意外死亡棍苹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門茵汰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枢里,“玉大人,你說我怎么就攤上這事蹂午±覆颍” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵豆胸,是天一觀的道長奥洼。 經(jīng)常有香客問我,道長晚胡,這世上最難降的妖魔是什么灵奖? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮估盘,結(jié)果婚禮上瓷患,老公的妹妹穿的比我還像新娘。我一直安慰自己遣妥,他們只是感情好擅编,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著箫踩,像睡著了一般沙咏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上班套,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天肢藐,我揣著相機與錄音,去河邊找鬼吱韭。 笑死吆豹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的理盆。 我是一名探鬼主播痘煤,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猿规!你這毒婦竟也來了衷快?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤姨俩,失蹤者是張志新(化名)和其女友劉穎蘸拔,沒想到半個月后师郑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡调窍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年宝冕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邓萨。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡地梨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缔恳,到底是詐尸還是另有隱情宝剖,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布歉甚,位于F島的核電站万细,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铃芦。R本人自食惡果不足惜雅镊,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一襟雷、第九天 我趴在偏房一處隱蔽的房頂上張望刃滓。 院中可真熱鬧,春花似錦耸弄、人聲如沸咧虎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砰诵。三九已至,卻和暖如春捌显,著一層夾襖步出監(jiān)牢的瞬間茁彭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工扶歪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留理肺,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓善镰,卻偏偏與公主長得像妹萨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炫欺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348