在閻宏博士的《JAVA與模式》一書中開頭是這樣描述責任鏈(Chain of Responsibility)模式的:
責任鏈模式是一種物件的行為模式。在責任鏈模式裡,許多物件由每個物件對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。
從擊鼓傳花談起
擊鼓傳花是一種熱鬧又緊張的飲酒遊戲。在酒宴上賓客依序坐定位置,由一人擊鼓,擊鼓的地方與傳花的地方是分開的,以示公正。開始擊鼓時,花束就開始依序傳遞,鼓聲一落,如果花束在某人手中,則該人就得飲酒。
比如說,賈母、賈赦、賈政、賈寶玉和賈環是五個參加擊鼓傳花遊戲的傳花者,他們組成一個環鏈。擊鼓者將花傳給賈母,開始傳花遊戲。花由賈母傳給賈赦,由賈赦傳給賈政,由賈政傳給賈寶玉,又賈寶玉傳給賈環,由賈環傳回給賈母,如此往復,如下圖所示。當鼓聲停止時,手中有花的人就得執行酒令。
擊鼓傳花便是責任鏈模式的應用。責任鏈可能是一條直線、一個環鏈或一個樹狀結構的一部分。
責任鏈模式的結構
下面使用了一個責任鏈模式的最簡單的實作。
責任鏈模式涉及的角色如下所示:
● 抽象處理者(Handler)角色:定義出一個處理請求的介面。如果需要,介面可以定義出一個方法以設定和傳回對下家的參考。這個角色通常由一個Java抽象類別或Java介面實作。上圖中Handler類別的聚合關係給出了具體子類別對下家的引用,而抽象方法handleRequest()規範了子類別處理請求的操作。
● 具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇將請求處理掉,或將請求傳給下家。由於具體處理者持有對下家的引用,因此,如果需要,特定處理者可以訪問下家。
原始碼
抽象處理者角色
複製代碼代碼如下:
public abstract class Handler {
/**
* 持有後繼的責任對象
*/
protected Handler successor;
/**
* 示意處理請求的方法,雖然這個示意方法是沒有傳入參數的
* 但實際上是可以傳入參數的,根據具體需求來選擇是否要傳遞參數
*/
public abstract void handleRequest();
/**
* 取值方法
*/
public Handler getSuccessor() {
return successor;
}
/**
* 賦值方法,設定後繼的責任對象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
}
具體處理者角色
複製代碼代碼如下:
public class ConcreteHandler extends Handler {
/**
* 處理方法,呼叫此方法處理請求
*/
@Override
public void handleRequest() {
/**
* 判斷是否有後繼的責任對象
* 如果有,就轉送請求給後繼的責任對象
* 如果沒有,則處理請求
*/
if(getSuccessor() != null)
{
System.out.println("放過請求");
getSuccessor().handleRequest();
}else
{
System.out.println("處理請求");
}
}
}
客戶端類別
複製代碼代碼如下:
public class Client {
public static void main(String[] args) {
//組裝責任鏈
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setSuccessor(handler2);
//提交請求
handler1.handleRequest();
}
}
可以看出,客戶端創建了兩個處理者對象,並指定第一個處理者對象的下家是第二個處理者對象,而第二個處理者對象沒有下家。然後客戶端將請求傳遞給第一個處理者物件。
由於此範例的傳遞邏輯非常簡單:只要有下家,就傳給下家處理;如果沒有下家,就自行處理。因此,第一個處理者物件接到請求後,會將請求傳遞給第二個處理者物件。由於第二個處理者對象沒有下家,於是自行處理請求。活動時序圖如下圖所示。
使用場景
來考慮這樣一個功能:申請聚餐費用的管理。
很多公司都是這樣的福利,就是專案組或是部門可以向公司申請一些聚餐費用,用於組織專案組成員或是部門成員進行聚餐活動。
申請聚餐費用的大致流程一般是:由申請人先填寫申請單,然後交給領導審批,如果申請批准下來,領導會通知申請人審批通過,然後申請人去財務領取費用,如果沒有批准下來,領導會通知申請人審批未通過,此事也就此作罷。
不同層級的領導,對於審核的額度是不一樣的,例如,專案經理只能審批500元以內的申請;部門經理能審核1000元以內的申請;而總經理可以審核任意額度的申請。
也就是說,當某人提出聚餐費用申請的請求後,該請求會經由專案經理、部門經理、總經理之中的某一位領導來進行相應的處理,但是提出申請的人並不知道最終會由誰來處理他的請求,一般申請人是把自己的申請提交給專案經理,或許最後是由總經理來處理他的請求。
可以使用責任鏈模式來實現上述功能:當某人提出聚餐費用申請的請求後,該請求會在專案經理―〉部門經理―〉總經理這樣一條領導處理鏈上進行傳遞,發出請求的人並不知道誰會來處理他的請求,每個領導會根據自己的職責範圍,來判斷是處理請求還是把請求交給更高級別的領導,只要有領導處理了,傳遞就結束了。
需要把每位領導的處理獨立出來,實現成單獨的職責處理對象,然後為它們提供一個公共的、抽象的父職責對象,這樣就可以在客戶端來動態地組合職責鏈,實現不同的功能要求了。
原始碼
抽象處理者角色類
複製代碼代碼如下:
public abstract class Handler {
/**
* 持有下一個處理請求的對象
*/
protected Handler successor = null;
/**
* 取值方法
*/
public Handler getSuccessor() {
return successor;
}
/**
* 設定下一個處理請求的對象
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
/**
* 處理聚餐費用的申請
* @param user 申請人
* @param fee 申請的錢數
* @return 成功或失敗的具體通知
*/
public abstract String handleFeeRequest(String user , double fee);
}
具體處理者角色
複製代碼代碼如下:
public class ProjectManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
String str = "";
//專案經理權限比較小,只能在500以內
if(fee < 500)
{
//為了測試,簡單點,只同意張三的請求
if("張三".equals(user))
{
str = "成功:專案經理同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}else
{
//其他人一律不同意
str = "失敗:專案經理不同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}
}else
{
//超過500,繼續傳遞給更高等級的人處理
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(user, fee);
}
}
return str;
}
}
複製代碼代碼如下:
public class DeptManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
String str = "";
//部門經理的權限只能在1000以內
if(fee < 1000)
{
//為了測試,簡單點,只同意張三的請求
if("張三".equals(user))
{
str = "成功:部門經理同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}else
{
//其他人一律不同意
str = "失敗:部門經理不同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}
}else
{
//超過1000,繼續傳遞給更高等級的人處理
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(user, fee);
}
}
return str;
}
}
複製代碼代碼如下:
public class GeneralManager extends Handler {
@Override
public String handleFeeRequest(String user, double fee) {
String str = "";
//總經理的權限很大,只要請求到了這裡,他都可以處理
if(fee >= 1000)
{
//為了測試,簡單點,只同意張三的請求
if("張三".equals(user))
{
str = "成功:總經理同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}else
{
//其他人一律不同意
str = "失敗:總經理不同意【" + user + "】的聚餐費用,金額為" + fee + "元";
}
}else
{
//如果還有後繼的處理對象,繼續傳遞
if(getSuccessor() != null)
{
return getSuccessor().handleFeeRequest(user, fee);
}
}
return str;
}
}
客戶端類別
複製代碼代碼如下:
public class Client {
public static void main(String[] args) {
//先要組裝責任鏈
Handler h1 = new GeneralManager();
Handler h2 = new DeptManager();
Handler h3 = new ProjectManager();
h3.setSuccessor(h2);
h2.setSuccessor(h1);
//開始測試
String test1 = h3.handleFeeRequest("張三", 300);
System.out.println("test1 = " + test1);
String test2 = h3.handleFeeRequest("李四", 300);
System.out.println("test2 = " + test2);
System.out.println("---------------------------------------");
String test3 = h3.handleFeeRequest("張三", 700);
System.out.println("test3 = " + test3);
String test4 = h3.handleFeeRequest("李四", 700);
System.out.println("test4 = " + test4);
System.out.println("---------------------------------------");
String test5 = h3.handleFeeRequest("張三", 1500);
System.out.println("test5 = " + test5);
String test6 = h3.handleFeeRequest("李四", 1500);
System.out.println("test6 = " + test6);
}
}
運行結果如下所示:
純的與不純的責任鏈模式
一個純粹的責任鏈模式要求一個具體的處理者物件只能在兩個行為中選擇一個:一是承擔責任,而是把責任推給下家。不允許某一個具體處理者對像出現在承擔了一部分責任後又把責任向下傳的情況。
在一個純粹的責任鏈模式裡面,一個請求必須被某一個處理者物件所接收;在一個不純的責任鏈模式裡面,一個請求可以最終不被任何接收端物件所接收。
純粹的責任鏈模式的實際例子很難找到,一般看到的例子都是不純的責任鏈模式的實現。有些人認為不純的責任鏈根本不是責任鏈模式,這也許是有道理的。但是在實際的系統裡,純的責任鏈很難找到。如果堅持責任鏈不純便不是責任鏈模式,那麼責任鏈模式就不會有太大意義了。
責任鏈模式在Tomcat中的應用
眾所周知Tomcat中的Filter就是使用了責任鏈模式,而建立一個Filter除了要在web.xml檔中做對應設定外,還需要實作javax.servlet.Filter介面。
複製代碼代碼如下:
public class TestFilter implements Filter{
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
public void destroy() {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
}
使用DEBUG模式所看到的結果如下
其實在真正執行到TestFilter類別之前,會經過很多Tomcat內部的類別。順帶提一下其實Tomcat的容器設定也是責任鏈模式,注意被紅色方框所圈中的類,從Engine到Host再到Context一直到Wrapper都是透過一個鏈傳遞請求。被綠色方框圈中的地方有一個名為ApplicationFilterChain的類,ApplicationFilterChain類所扮演的就是抽象處理者角色,而具體處理者角色由各個Filter扮演。
第一個疑問是ApplicationFilterChain將所有的Filter放在哪裡?
答案是保存在ApplicationFilterChain類別中的一個ApplicationFilterConfig物件的陣列中。
複製代碼代碼如下:
/**
* Filters.
*/
private ApplicationFilterConfig[] filters =
new ApplicationFilterConfig[0];
那ApplicationFilterConfig物件又是什麼呢?
ApplicationFilterConfig是一個Filter容器。以下是ApplicationFilterConfig類別的聲明:
複製代碼代碼如下:
/**
* Implementation of a <code>javax.servlet.FilterConfig</code> useful in
* managing the filter instances instantiated when a web application
* is first started.
*
* @author Craig R. McClanahan
* @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $
*/
當一個web應用程式首次啟動時ApplicationFilterConfig會自動實例化,它會從該web應用的web.xml檔案中讀取配置的Filter的信息,然後裝進該容器。
剛剛看到在ApplicationFilterChain類別中所建立的ApplicationFilterConfig陣列長度為零,那它是在什麼時候被重新賦值的呢?
複製代碼代碼如下:
private ApplicationFilterConfig[] filters =
new ApplicationFilterConfig[0];
是在呼叫ApplicationFilterChain類別的addFilter()方法時。
複製代碼代碼如下:
/**
* The int which gives the current number of filters in the chain.
*/
private int n = 0;
public static final int INCREMENT = 10;
複製代碼代碼如下:
void addFilter(ApplicationFilterConfig filterConfig) {
// Prevent the same filter being added multiple times
for(ApplicationFilterConfig filter:filters)
if(filter==filterConfig)
return;
if (n == filters.length) {
ApplicationFilterConfig[] newFilters =
new ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
filters = newFilters;
}
filters[n++] = filterConfig;
}
變數n用來記錄目前過濾器鏈裡面擁有的過濾器數目,預設情況下n等於0,ApplicationFilterConfig物件陣列的長度也等於0,所以當第一次呼叫addFilter()方法時,if (n == filters .length)的條件成立,ApplicationFilterConfig陣列長度改變。之後filters[n++] = filterConfig;將變數filterConfig放入ApplicationFilterConfig陣列中並將目前過濾器鏈裡面擁有的過濾器數目+1。
那ApplicationFilterChain的addFilter()方法又是在哪裡被呼叫的呢?
是在ApplicationFilterFactory類別的createFilterChain()方法。
複製代碼代碼如下:
public ApplicationFilterChain createFilterChain
(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// get the dispatcher type
DispatcherType dispatcher = null;
if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
}
String requestPath = null;
Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
// If there is no servlet to execute, return null
if (servlet == null)
return (null);
boolean comet = false;
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
comet = req.isComet();
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
if (comet) {
req.setFilterChain(filterChain);
}
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setSupport
(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(t);
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
boolean isCometFilter = false;
if (comet) {
try {
isCometFilter = filterConfig.getFilter() instanceof CometFilter;
} catch (Exception e) {
// Note: The try catch is there because getFilter has a lot of
// declared exceptions. However, the filter is allocated much
// earlier
}
if (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
可以將如上程式碼分成兩段,51行之前為第一段,51行之後為第二段。
第一段的主要目的是建立ApplicationFilterChain物件以及一些參數設定。
第二段的主要目的是從上下文中獲取所有Filter信息,之後使用for循環遍歷並調用filterChain.addFilter(filterConfig);將filterConfig放入ApplicationFilterChain對象的ApplicationFilterConfig數組中。
那ApplicationFilterFactory類別的createFilterChain()方法又是在哪裡被呼叫的呢?
是在StandardWrapperValue類別的invoke()方法中被呼叫的。
由於invoke()方法較長,所以將許多地方省略。
複製代碼代碼如下:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
....省略中間碼// Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
...省略中間代碼
filterChain.doFilter(request.getRequest(), response.getResponse());
...省略中間代碼
}
那正常的流程應該是這樣的:
在StandardWrapperValue類別的invoke()方法中呼叫ApplicationFilterChai類別的createFilterChain()方法――>在ApplicationFilterChai類別的createFilterChain()方法中呼叫ApplicationFilterChain類別的addFilter()方法―――>在ApplicationFilterFilter(中給ApplicationFilterConfig數組賦值。
根據上面的程式碼可以看出StandardWrapperValue類別的invoke()方法在執行完createFilterChain()方法後,會繼續執行ApplicationFilterChain類別的doFilter()方法,然後在doFilter()方法中會呼叫internalDoFilter()方法。
以下是internalDoFilter()方法的部分程式碼
複製代碼代碼如下:
// Call the next filter if there is one
if (pos < n) {
//拿到下一個Filter,將指標往下移動一位
//pos它來識別目前ApplicationFilterChain(目前過濾器鏈)執行到哪個過濾器
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
//取得目前指向的Filter的實例
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);
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的doFilter()方法
filter.doFilter(request, response, this);
}
這裡的filter.doFilter(request, response, this);就是呼叫我們前面建立的TestFilter中的doFilter()方法。而TestFilter中的doFilter()方法會繼續呼叫chain.doFilter(request, response);方法,而這個chain其實就是ApplicationFilterChain,所以呼叫過程又回到了上面呼叫dofilter和呼叫internalDoFilter方法,這樣執行直到裡面的過濾器全部執行。
如果定義兩個篩選器,則Debug結果如下: