從原始碼角度理解Java設計模式--責任鏈模式
本文內容思維導圖如下:
一、責任鏈模式介紹
責任鏈模式定義:為請求建立一個處理此請求物件的鏈。
適用場景(核心):只要把你的請求拋給第一個處理者,不用關心誰處理的,並且最終會返回你一個結果。
優點:請求者和處理者解耦,請求者不用知道誰處理的,處理者可以不用知道請求的全貌。
缺點:每個請求從鏈頭遍歷到鏈尾,影響效能。程式碼除錯時候不方便。
型別:行為型。
類圖:
原始碼中的典型應用:
- Netty 中的 Pipeline和ChannelHandler通過責任鏈設計模式來組織程式碼邏輯。
- Spring Security 使用責任鏈模式,可以動態地新增或刪除責任(處理 request 請求)。ref:SPRING與設計模式---責任鏈模式
- Spring AOP 通過責任鏈模式來管理 Advisor。
- Dubbo Filter 過濾器鏈也是用了責任鏈模式(連結串列),可以對方法呼叫做一些過濾處理,譬如超時(TimeoutFilter),異常(ExceptionFilter),Token(TokenFilter)等。
- Mybatis 中的 Plugin 機制使用了責任鏈模式,配置各種官方或者自定義的 Plugin,與 Filter 類似,可以在執行 Sql 語句的時候做一些操作。
- Tomcat 呼叫 ApplicationFilterFactory過濾器鏈。
二、請假示例
員工在OA系統中提交請假申請,首先專案經理處理,他能審批3天以內的假期,如果大於3天,則由專案經理則轉交給總經理處理。接下來我們用責任鏈模式實現這個過程。
1、封裝請假資訊實體類
public class LeaveRequest { private String name; // 請假人姓名 private int numOfDays; // 請假天數 private int workingAge; //員工工齡(在公司大於2年則總經理會審批) //省略get..set.. }
2、抽象處理者類 Handler,維護一個nextHandler屬性,該屬性為當前處理者的下一個處理者的引用;聲明瞭抽象方法process,其實在這裡也用了方法模板模式:
public abstract class ApproveHandler {
protected ApproveHandler nextHandler;//下一個處理者(與類一致,這段程式碼很重要)
public void setNextHandler(ApproveHandler approveHandler){
this.nextHandler=approveHandler;
}
public abstract void process(LeaveRequest leaveRequest); // 處理請假(這裡用了模板方法模式)
}
3、專案經理處理者,能處理小於3天的假期,而請假資訊裡沒有名字時,審批不通過:
public class PMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//未填寫姓名的請假單不通過
if(null != leaveRequest.getName()){
if(leaveRequest.getNumOfDays() <= 3){
System.out.println(leaveRequest.getName()+",你通過專案經理審批!");
}else {
System.out.println("專案經理轉交總經理");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}
}else {
System.out.println("請假單未填寫完整,未通過專案經理審批!");
return;
}
}
}
4、總經理處理者,能處理大於3天的假期,且工齡超過2年才會審批通過:
public class GMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//員工在公司工齡超過2年,則審批通過
if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){
System.out.println(leaveRequest.getName()+",你通過總經理審批!");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}else {
System.out.println("在公司年限不夠,長假未通過總經理審批!");
return;
}
}
}
例項程式碼完成,我們測試一下:
public class Test {
public static void main(String[] args) {
PMHandler pm = new PMHandler();
GMHandler gm = new GMHandler();
LeaveRequest leaveRequest = new LeaveRequest();
leaveRequest.setName("張三");
leaveRequest.setNumOfDays(4);//請假4天
leaveRequest.setWorkingAge(3);//工齡3年
pm.setNextHandler(gm);//設定傳遞順序
pm.process(leaveRequest);
}
}
執行結果:
------
專案經理轉交總經理
張三,你通過總經理審批!
------
三、原始碼中的責任鏈模式
Filter介面有非常多的實現類,這裡挑選doFilter方法中的FilterChain引數來看,Tomcat和SpringSecurity中都用到責任鏈模式:
進入第一個,過濾器鏈 ApplicationFilterChain 的關鍵程式碼如下,過濾器鏈實際是一個 ApplicationFilterConfig 陣列:
final class ApplicationFilterChain implements FilterChain, CometFilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 過濾器鏈
private Servlet servlet = null; // 目標
// ...
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
// ...
} else {
internalDoFilter(request,response); // 呼叫 internalDoFilter 方法
}
}
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
// 從過濾器陣列中取出當前過濾器配置,然後下標自增1
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter(); // 從過濾器配置中取出該 過濾器物件
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 {
// 呼叫過濾器的 doFilter,完成一個過濾器的過濾功能
filter.doFilter(request, response, this);
}
return; // 這裡很重要,不會重複執行後面的 servlet.service(request, response)
}
// 執行完過濾器鏈的所有過濾器之後,呼叫 Servlet 的 service 完成請求的處理
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
}
// 省略...
}
這裡可以看出ApplicationFilterChain類扮演了抽象處理者角色,doFilter就類似於剛才請假流程裡的process方法。
當下標小於過濾器陣列長度 n 時,也就是過濾器鏈未執行完,所以從陣列中取出並呼叫當前過濾器的 doFilter方法 ,如果下標一直小於n,則迴圈呼叫doFilter方法通過巢狀遞迴的方式來串成一條鏈。
當全部過濾器都執行完畢,也就是if (pos < n) 為false時,才會呼叫後面的servlet.service(request, response) 方法。需要注意的是在 if (pos < n) 方法體的最後有一個 return;這樣就保證了最後一個filter處理完結束遞迴呼叫doFilter方法,而servlet.service(request, response) 方法才得以執行。這一點在請假流程裡也有體現。
參考: