定義
來自 GoF 的《設計模式》權威定義如下:
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
通過給多個對象處理請求的機會,避免將請求的發送方與其接收方耦合起來。將接收對象鏈起來,並沿着鏈傳遞請求,直到有對象處理它。
這裏注意:它說是直到有對象處理請求爲止這一點,因爲在後續的一些實現中進行了不同的演進。
實現
基礎實現
既然是鏈,我們直接就會想到:使用一個鏈表的數據結構來模擬這個執行鏈。
首先,抽象出Handler
:
-
每個
Handler
都會包含一個Handler
作爲自己執行鏈的next
,鏈表熟悉的味道。 -
每個
Handler
需要實現void handle(Request request)
方法,以達到處理請求的要求。
public abstract class Handler {
protected Handler next;
public void setNext(Handler next) {
this.next = next;
}
abstract void handle(Request request);
}
然後,具體Handler的實現就只需要實現void handle(Request request)
,內部有一個boolean
類型的handled
字段表示自己是否執行,只要自己沒執行並且後面還有Handler,就繼續執行,也就是說有一個Handler
執行就中斷執行鏈,因爲在前面的定義中要求,只要有一個對象處理了請求,就終止。
public class AHandler extends Handler{
void handle(Request request) {
// 是否處理
boolean handled = false;
// do something
if(!handled && next != null){
// 執行下一個
next.handle(request);
}
// do something
}
}
最後,我們來看下最關鍵的HandlerChain
是如何實現的呢?
void addHandler(Handler handler)
方法核心把多個Handler
串聯起來,實現起來有一點技巧,具體說明詳細寫在註釋上。void handle(Request request)
方法就把組裝好的Handler
鏈從頭開始執行直到中斷或執行光全部Handler
public class HandlerChain {
private Handler head;
private Handler tail;
/**
* 將handle組合成一個鏈表
* @param handler
*/
public void addHandler(Handler handler) {
handler.setNext(null);
// 放第一個Handler作爲head時,tail也設置好
if(head == null){
head = handler;
tail = handler;
return;
}
// 在已經有head的情況下說明tail就是上一個handler對象
// 所以就直接把要加的handler放到tail的next裏,此時就已經把兩個前後handler串起來了
// 然後把tail改成最後放入的handler,等待下一個放入操作
// tail作爲中間存放的地方,每次放handler的時候,tail上就是它上一個handler
tail.setNext(handler);
tail = handler;
}
public void handle(Request request){
if(head != null){
head.handle(request);
}
}
}
以上我們就實現瞭如下的一個責任鏈,如下圖:
優化
Handler
的實現代碼需要自己判斷是否執行下一個Handler
,並且需要自己調用,對於擴展Handler
時不友好,所以做個修改。
使用模版模式,修改抽象Handler
:
public abstract class Handler {
protected Handler next = null;
public void setNext(Handler next) {
this.next = next;
}
public void handle(Request request){
boolean handled = doHandle(request);
// 如果鏈上某個處理器能夠處理這個請求,那就不會繼續往下傳遞請求
if(!handled && this.next != null){
next.handle(request);
}
}
abstract boolean doHandle(Request request);
}
這樣Handler
的實現代碼就變得更加符合開閉原則了:
public class AHandler extends Handler {
public boolean doHandle(Request request) {
// 是否處理
boolean handled = false;
// do something
return handled;
}
}
數組實現
再想一下,我們完全可以用一個for循環List<Handler>
,依次執行每個Handler就也能實現責任鏈。
實現代碼在前面的代碼基礎上,主要修改HandlerChain
的代碼:
public class HandlerChain {
List<Handler> handlerList = new ArrayList<Handler>();
/**
* list 數據結構存放
* @param handler
*/
public void addHandler(Handler handler) {
handlerList.add(handler);
}
public void handle(Request request){
for (Handler handler : handlerList) {
boolean handle = handler.handle(request);
// 如果鏈上某個處理器能夠處理這個請求,那就不會繼續往下傳遞請求
if(handle){
break;
}
}
}
}
public class AHandler implements Handler{
public boolean handle(Request request) {
boolean handled = true;
// do something
return handled;
}
}
基本的實現就這些,特別需要提醒的是優化之前的實現和優化後的實現的一個隱藏區別就是,優化前的代碼是可以實現後一個Handler執行完,再執行前一個Handler的後面代碼,類似達到一個嵌套的方法執行鏈路。
演進一
在實際代碼開發中,有很多流程性的代碼組合,可以封裝起來後拆分成多個Handler,然後依次執行,這種結構在後續加入新的代碼時就會清晰一些。
因此,在前面的代碼基礎上,我們只需要去除是否執行的判斷邏輯,並且保證所有Handler全部被執行就可以達到這個效果。
以下是鏈表形式的部分修改後的代碼:
public abstract class Handler {
protected Handler next = null;
public void setNext(Handler next) {
this.next = next;
}
public void handle(Request request){
doHandle(request);
// 請求會被所有的處理器都處理一遍
if(this.next != null){
next.handle(request);
}
}
abstract void doHandle(Request request);
}
public class AHandler extends Handler {
@Override
void doHandle(Request request) {
// do something
}
}
一樣的,我們也用數組形式寫一下:
public class HandlerChain {
List<Handler> handlerList = new ArrayList<Handler>();
/**
* list 數據結構存放
* @param handler
*/
public void addHandler(Handler handler) {
handlerList.add(handler);
}
public void handle(Request request){
for (Handler handler : handlerList) {
handler.handle(request);
}
}
}
演進二
第二個演進就是在servlet
規範中的Filter
機制,Tomcat
的實現就是使用了責任鏈模式。源碼可以從org.apache.catalina.core.ApplicationFilterChain
入手去看就行。這個實現可以說是教科書式的,有必要好好看下源碼。
這邊是根據Tomcat的實現,簡化了代碼,方便直接看到實現機制。
要達到的效果如下圖所示:
首先,定義好Request
,Response
,Servlet
,IHandler
:
public class Request {
public Long id;
}
public class Response {
public String result;
}
public class Servlet {
public void service(Request request, Response response){
System.out.println("do Servlet");
}
}
public interface IHandler {
void handle(Request request, Response response, TwoWayHandlerChain handlerChain);
}
然後,具體的IHandler
實現:
public class BHandler implements IHandler{
public void handle(Request request, Response response, TwoWayHandlerChain handlerChain) {
System.out.println("do B handler1");
// 反過來再調用handlerChain的doHandle
handlerChain.doHandle(request, response);
System.out.println("do B handler2");
}
}
public class AHandler implements IHandler{
public void handle(Request request, Response response, TwoWayHandlerChain handlerChain) {
System.out.println("do A handler1");
handlerChain.doHandle(request, response);
System.out.println("do A handler2");
}
}
最後,關鍵HandlerChain
的實現:
public class TwoWayHandlerChain {
private int pos = 0;
private List<IHandler> handlers = new ArrayList<IHandler>();
private Servlet servlet = new Servlet();
void addHandler(IHandler handler){
handlers.add(handler);
}
void doHandle(Request request, Response response){
if(handlers.size() > pos){
IHandler handler = handlers.get(pos++);
handler.handle(request, response, this);
}else{
servlet.service(request, response);
}
}
}
void doHandle(Request request, Response response)
方法會調用到Handler
,Handler
又會回調HandlerChain
,形成遞歸調用鏈,從而達成上面圖的效果。
寫一個main方法測試一下:
public class Application {
/**
* 打印信息:
* do A handler1
* do B handler1
* do Servlet
* do B handler2
* do A handler2
*/
public static void main(String[] args) {
TwoWayHandlerChain handlerChain = new TwoWayHandlerChain();
handlerChain.addHandler(new AHandler());
handlerChain.addHandler(new BHandler());
handlerChain.doHandle(new Request(), new Response());
}
}
從打印信息來看,Filter既可以在Servlet執行前進行一些操作,還可以在Servlet處理完後再做一些操作,在請求響應的模式中非常有用。
另外,在開源數據庫連接池Druid中,也有Filter機制,實現的效果和這個一模一樣。源碼可以參考:
com.alibaba.druid.filter.FilterChainImpl`
演進三
在Spring mvc
中的Interceptor
機制也是使用了責任鏈模式,可見這個模式在很多開源框架中使用非常流行。
這邊還是模擬框架代碼,手動實現了簡要的代碼。
首先,定義好Controller
,Interceptor
public class Controller {
void process(Request request, Response response){
// do something
}
}
public interface Interceptor {
boolean preHandle(Request request, Response response);
void postHandle(Request request, Response response);
void afterCompletion(Request request, Response response);
}
public class InterceptorA implements Interceptor{
public boolean preHandle(Request request, Response response) {
return false;
}
public void postHandle(Request request, Response response) {
}
public void afterCompletion(Request request, Response response) {
}
}
HandlerExecutionChain的實現基本還原源碼的實現:
public class HandlerExecutionChain {
// 省去數組擴容邏輯
private Interceptor[] interceptors = new Interceptor[100];
private int index = 0;
private int interceptorIndex = -1;
private Controller controller = new Controller();
public void addInterceptor(Interceptor interceptor){
interceptors[index++] = interceptor;
}
void doHandle(Request request, Response response){
if(!applyPreHandle(request, response)){
return;
}
try{
controller.process(request, response);
applyPostHandle(request, response);
}catch (Exception e){
}
triggerAfterCompletion(request, response);
}
private boolean applyPreHandle(Request request, Response response){
for (int i = 0; i < interceptors.length; i++) {
Interceptor interceptor = interceptors[i];
// 以此順序執行,這裏pre執行的時候是可以中斷執行鏈的,中斷後執行triggerAfterCompletion
if (!interceptor.preHandle(request, response)) {
triggerAfterCompletion(request, response);
return false;
}
this.interceptorIndex = i;
}
return true;
}
private void applyPostHandle(Request request, Response response){
// 而post執行是依次倒序執行全部interceptor中的post
for (int i = interceptors.length - 1; i >= 0; i--) {
Interceptor interceptor = interceptors[i];
interceptor.postHandle(request, response);
}
}
private void triggerAfterCompletion(Request request, Response response){
// 從pre跳出的那個interceptor 開始往回倒序執行
for (int i = this.interceptorIndex; i >= 0; i--) {
Interceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response);
}
catch (Throwable ex2) {
// print some logs and do nothing
}
}
}
}
這個實現中,重點是按照自己需要在執行鏈路中引入了三個步驟:applyPreHandle
,applyPostHandle
,triggerAfterCompletion
,整個執行鏈路機制和前面的Filter是一樣的,但是這種實現是不需要在擴展Interceptor的時候再回調Chain的的Handle的。
總結
設計模式可以定義,代碼實現卻沒有限制,只有掌握了思想,使用的招式自然會按實際情況進行演變,可能是學習設計模式的關鍵吧。