設計模式學習筆記(十四)責任鏈模式實現以及在Filter中的應用

歸斯君發表於2022-04-04

責任鏈模式(Chain Of Responsibility Design Pattern),也叫做職責鏈,是將請求的傳送和接收解耦,讓多個接收物件都有機會處理這個請求。當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止。

責任鏈模式

一、責任鏈模式介紹

其實在日常生活中,有不少使用責任鏈的場景。比如公司採購審批流程,需要各個部門領導的批准同意。在責任鏈模式中,客戶端只需要將請求傳送到責任鏈上,無須關心請求的處理細節和傳遞,請求會自動進行傳遞。

1.1 責任鏈模式的結構

責任鏈模式的結構大致如下所示:

image-20220404203259612

  • abstract Handler:抽象處理者,定義一個處理請求的介面,內部包含抽象處理方法和後繼具體處理者
  • Handler1、Handler2:具體處理者,具體實現抽象處理者的方法,並對請求做一些邏輯處理
  • Client:客戶端,使用職責鏈模式

1.2 責任鏈模式的實現

根據上面的類圖,可以實現如下程式碼:

/**
 * @description: 抽象處理類
 * @author: wjw
 * @date: 2022/4/4
 */
public abstract class Handler {

    private Handler successor;

    public Handler getSuccessor() {
        return successor;
    }

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    /**
     * 處理請求的抽象方法
     * @param request 請求
     */
    public abstract void handleRequest(String request);
}
/**
 * @description: 具體處理者1
 * @author: wjw
 * @date: 2022/4/4
 */
public class Handler1 extends Handler{

    private String handler;

    public Handler1(String handler) {
        this.handler = handler;
    }

    @Override
    public void handleRequest(String request) {
        if ("handler1".equals(request)) {
            System.out.println("具體處理者handler1進行請求處理");
        } else {
            if (getSuccessor() != null) {
                //如果指向下一個具體處理者
                getSuccessor().handleRequest(request);
            } else {
                System.out.println("沒有處理者進行處理");
            }
        }
    }
}
/**
 * @description: 具體處理者2
 * @author: wjw
 * @date: 2022/4/4
 */
public class Handler2 extends Handler{

    private String handler;

    public Handler2(String handler) {
        this.handler = handler;
    }

    @Override
    public void handleRequest(String request) {
        if ("handler2".equals(request)) {
            System.out.println("具體處理者handler2進行請求處理");
        } else {
            if (getSuccessor() != null) {
                getSuccessor().handleRequest(request);
            } else {
                System.out.println("請求沒有被任何處理者處理");
            }
        }
    }
}
/**
 * @description: 客戶端類
 * @author: wjw
 * @date: 2022/4/4
 */
public class Client {
    public static void main(String[] args) {
        Handler handler1 = new Handler1("handler1");
        Handler handler2 = new Handler2("handler2");
        handler1.setSuccessor(handler2);
        handler1.handleRequest("handler1");
        handler1.handleRequest("handler2");

    }
}

測試結果:

具體處理者handler1進行請求處理
具體處理者handler2進行請求處理

二、責任鏈模式的應用場景

職責鏈模式最常見的應用就是用來開發各種框架的過濾器和攔截器,比如Spring Interceptor和Servlet Filter

2.1 在Servlet Filter中的應用

Filter 可以實現對HTTP 請求的過濾功能,比如鑑權、限流、記錄日誌、驗證引數等等。比如一些Servlet 容器(TomCat、Jetty等)就支援Filter的過濾功能。以TomCat 為例:

image-20220404214532930

所以當Servlet 請求到來時,首先會經過Filter 處理,最後再到達Servlet例項。我這裡選取的TomCat版本是SpringBoot自帶的9.0,先來看看FilterChain 的介面:

public interface FilterChain {
    //Filter具體實現
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain是責任鏈模式的具體實現類:

public final class ApplicationFilterChain implements FilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; //Filter配置陣列
    private int pos = 0; //執行Filter的序號
    private int n = 0;	 //目前Filter的個數
    private Servlet servlet = null;

    public ApplicationFilterChain() {
    }
    
	@Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        //Filter 具體實現
        //...
    }
    
    public static ServletRequest getLastServicedRequest() {
        return (ServletRequest)lastServicedRequest.get();
    }

    public static ServletResponse getLastServicedResponse() {
        return (ServletResponse)lastServicedResponse.get();
    }

    void addFilter(ApplicationFilterConfig filterConfig) {
        ApplicationFilterConfig[] newFilters = this.filters;
        int var3 = newFilters.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            ApplicationFilterConfig filter = newFilters[var4];
            if (filter == filterConfig) {
                return;
            }
        }
		//增加Filter
        if (this.n == this.filters.length) {
            newFilters = new ApplicationFilterConfig[this.n + 10];
            System.arraycopy(this.filters, 0, newFilters, 0, this.n);
            this.filters = newFilters;
        }

        this.filters[this.n++] = filterConfig;
    }
}

2.2 在Spring Interceptor中的應用

和Servlet Filter類似,在Spring 中也有對應的過濾器 Interceptor。它是由Spring MVC 框架來實現,借一張來自《設計模式之美》的圖片來說明:

image-20220404221601450

客戶端傳送請求,首先會經過Servlet Filter,然後再經過Spring Interceptor,最後再到達具體的業務中。

和Filter一樣,Interceptor 中也是基於責任鏈模式來實現的,與之相對的HandlerInterceptor是抽象處理介面:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

會有各種具體處理類的實現:

image-20220404222327991

此外在其他框架中也存在著職責鏈模式的使用,比如Dubbo Filter、Netty ChannelPipeline等等

三、責任鏈模式實戰

模擬在618大促期間的業務系統上線審批流程場景:

像是這些⼀線電商類的互聯⽹公司,阿⾥、京東、拼多多等,在618期間都會做⼀些運營活動場景以及 提供的擴容備戰,就像過年期間百度的紅包⼀樣。但是所有開發的這些系統都需要陸續的上線,因為臨 近618有時候也有⼀些緊急的調整的需要上線,但為了保障線上系統的穩定性是儘可能的減少上線的, 也會相應的增強審批⼒度。就像⼀級響應、⼆級響應⼀樣。

⽽這審批的過程在隨著特定時間點會增加不同級別的負責⼈加⼊,每個⼈就像責任鏈模式中的每⼀個核 ⼼點。對於研發並不需要關⼼具體的審批流程處理細節,只需要知道這個上線更嚴格,級別也更 ⾼,但對於研發⼈員來說同樣是點選相同的提審按鈕,等待稽核。

image-20220404223156189

使用責任鏈模式可以將各個服務模組按照一、二、三級進行分離,每個級別可以像Filter一樣用Successor下一個級別的方法進行呼叫。具體結構圖如下(來自《重學Java設計模式》)

image-20220404223642273

具體程式碼結構

│——AuthInfo.java
│——AuthLink.java
│
├─business
│     AuthService.java
│
└─impl
      Level1AuthLink.java
      Level2AuthLink.java
      Level3AuthLink.java

實現程式碼:

/**
 * @description: 鏈路抽象審批
 * @author: wjw
 * @date: 2022/4/4
 */
public abstract class AuthLink {

    /**時間格式化**/
    protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**級別人員ID**/
    protected String levelUserId;
    /**級別人員姓名**/
    protected String levelUserName;
    /**下一個鏈路**/
    private AuthLink next;

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    /**每個具體處理者必須實現的方法**/
    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
/**
 * @description: 責任鏈中返回物件的定義
 * @author: wjw
 * @date: 2022/4/4
 */
public class AuthInfo {

    private String code;
    private String info = " ";

    public AuthInfo(String code, String... infos) { //...表示可變長引數,可以傳入多個引數
        this.code = code;
        for (String s : infos) {
            this.info = this.info.concat(s);
        }
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
/**
 * @description: 一級稽核人
 * @author: wjw
 * @date: 2022/4/4
 */
public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號", orderId, "狀態:一級審批負責人", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號", orderId, "狀態:一級審批負責人", " 時間:", f.format(date), "審批人:", levelUserName);
        }
        return next.doAuth(uId, orderId, authDate);
    }
}
/**
 * @description: 測試類
 * @author: wjw
 * @date: 2022/4/5
 */
public class ApiTest {
    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_AuthLink() throws ParseException {
        AuthLink authLink = new Level3AuthLink("1000013", "王工")
                .appendNext(new Level2AuthLink("1000012", "張經理"))
                .appendNext(new Level3AuthLink("1000011", "趙總裁"));

        logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));

        AuthService.auth("1000013", "100099800423123");
        logger.info("測試結果:{}", "模擬二級審批,王工");
        logger.info("測試結果:{}",JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));

        AuthService.auth("1000012", "100099800423123");
        logger.info("測試結果:{}", "模擬二級負責人審批,張經理");
        logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));

        AuthService.auth("1000011", "100099800423123");
        logger.info("測試結果:{}", "模擬二級負責人審批,趙總裁");
        logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("ethan", "100099800423123", new Date())));

    }
}

最後的測試結果:

10:45:14.496 [main] INFO  ApiTest - 測試結果:{"code":"0001","info":" 單號:100099800423123 狀態:待三級審批 王工"}
10:45:14.500 [main] INFO  ApiTest - 測試結果:模擬二級審批,王工
10:45:14.500 [main] INFO  ApiTest - 測試結果:{"code":"0001","info":" 單號:100099800423123 狀態:待三級審批 趙總裁"}
10:45:14.500 [main] INFO  ApiTest - 測試結果:模擬二級負責人審批,張經理
10:45:14.500 [main] INFO  ApiTest - 測試結果:{"code":"0001","info":" 單號:100099800423123 狀態:待三級審批 趙總裁"}
10:45:14.500 [main] INFO  ApiTest - 測試結果:模擬二級負責人審批,趙總裁
10:45:14.500 [main] INFO  ApiTest - 測試結果:{"code":"0000","info":" 單號: 100099800423123 狀態:三級審批完成 時間:2022-04-05 10:45:14 審批人:趙總裁"}

參考資料

《設計模式之美》

http://c.biancheng.net/view/4024.html

《重學Java設計模式》

相關文章