本文內容思維導圖如下:
一、責任鏈模式介紹
責任鏈模式定義:為請求創建一個處理此請求對象的鏈。
適用場景(核心):只要把你的請求拋給第一個處理者,不用關心誰處理的,并且最終會返回你一個結果。
優點:請求者和處理者解耦,請求者不用知道誰處理的,處理者可以不用知道請求的全貌。
缺點:每個請求從鏈頭遍歷到鏈尾,影響性能。代碼調試時候不方便。
類型:行為型。
類圖:
源碼中的典型應用:
- 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方法通過嵌套遞歸的方式來串成一條鏈。
當最后的過濾器執行完畢,也就是走到最后一個return;時,結束遞歸調用doFilter。if (pos < n) 為false,調用后面的servlet.service(request, response) 方法。return;這一點在請假流程里也有體現。
參考: