聊一聊責任鏈模式

語言: CN / TW / HK

將一堆“事情”串聯在一起,有序執行,就叫責任鏈

一、概述

責任鏈模式(Chain of Responsibility Pattern)是將鏈中每一個節點看作是一個物件,每個節點處理的請求均不同,且內部自動維護一個下一節點物件。當一個請求從鏈式的首端發出時,會沿著鏈的路徑依次傳遞給每一個節點物件,直至有物件處理這個請求為止,屬於行為型模式。 下面放一張足球比賽的圖,通過層層傳遞,最終射門。通過這張圖,可以更好的理解責任鏈模式。

圖一.jpg

二、入門案例

2.1 類圖

圖二.jpg

2.2 基礎類介紹

抽象介面RequestHandler ```java /* * @author 往事如風 * @version 1.0 * @date 2022/10/25 13:41 * @description / public interface RequestHandler {

void doHandler(String req);

} **抽象類BaseRequestHandler**java /* * @author 往事如風 * @version 1.0 * @date 2022/10/25 13:45 * @description / public abstract class BaseRequestHandler implements RequestHandler {

protected RequestHandler next;

public void next(RequestHandler next) {
    this.next = next;
}

} **具體處理類AHandler**java /* * @author 往事如風 * @version 1.0 * @date 2022/10/25 14:00 * @description / public class AHandler extends BaseRequestHandler {

@Override
public void doHandler(String req) {
    // 處理自己的業務邏輯
    System.out.println("A中處理自己的邏輯");
    // 傳遞給下個類(若鏈路中還有下個處理類)
    if (next != null) {
        next.doHandler(req);
    }
}

}

_當然還有具體的處理類B、C等等,這裡不展開贅述。_ **使用類Client**java /* * @author 往事如風 * @version 1.0 * @date 2022/10/25 14:06 * @description / public class Client { public static void main(String[] args) { BaseRequestHandler a = new AHandler(); BaseRequestHandler b = new BHandler(); BaseRequestHandler c = new CHandler(); a.next(b); b.next(c); a.doHandler("鏈路待處理的資料"); } } ```

2.3 處理流程圖

圖三.jpg

三、應用場景

3.1 場景舉例

場景一

前兩年,在一家金融公司待過一段時間,其中就有一個業務場景:一筆訂單進來,會先在後臺通過初審人員進行審批,初審不通過,訂單流程結束。初審通過以後,會轉給終審人員進行審批,不通過,流程結束;通過,流轉到下個業務場景。 對於這塊業務程式碼,之前一代目是一個叫知了的同事,他擼起袖子就是幹,一套if-else幹到底。後來,技術老大CodeReview,點名要求改掉這塊。於是乎,想到用用設計模式吧,然後就噼裡啪啦一頓改。(當然,比較複雜的情況,還是可以用工作流來處理這個場景,當時礙於時間成本,也就放棄了)。

場景二

上家公司對接甲方爸爸的時候,對方會呼叫我們介面,將資料同步過來。同樣,我們需要將處理好的資料,傳給他們。由於雙方傳輸資料都是加密傳輸,所以在接受他們資料之前,需要對資料進行解密,驗籤,引數校驗等操作。同樣,我們給他們傳資料也需要進行加簽,加密操作。

具體案例

話不多說,對於場景二,我來放一些虛擬碼,跟大家一起探討下。 1、一切從註解開始,我這裡自定義了一個註解@Duty,這個註解有spring的@Component註解,也就是標記了這個自定義註解的類,都是交給spring的bean容器去管理。 註解中,有兩個屬性:1.type,定義相同的type型別的bean,會被放到一個責任鏈集合中。2.order,同一個責任鏈集合中,bean的排序,數值越小,會放到鏈路最先的位置,優先處理。 ```java / * @author 往事如風 * @version 1.0 * @date 2022/10/25 16:11 * @description */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Service public @interface Duty { / * 標記具體業務場景 * @return */ String type() default "";

/**
 * 排序:數值越小,排序越前
 * @return
 */
int order() default 0;

} 2、定義一個頂層的抽象介面`IHandler`,傳入2個泛型引數,供後續自定義。java / * @author 往事如風 * @version 1.0 * @date 2022/10/25 15:31 * @description 責任鏈頂層抽象類 */ public interface IHandler { / * 抽象處理類 * @param t * @return */ R handle(T t); } `` 3、定義一個責任鏈bean的管理類HandleChainManager`,用來存放不同業務下的責任鏈路集合。在該類中,有一個Map和兩個方法。

  1. handleMap:這個map會存放責任鏈路中,具體的執行類,key是註解@Duty中定義的type值,value是標記了@Duty註解的bean集合,也就是具體的執行類集合。
  2. setHandleMap:傳入具體執行bean的集合,存放在map中。
  3. executeHandle:從map中找到具體的執行bean集合,並依次執行。 ```java /**
  4. @author 往事如風
  5. @version 1.0
  6. @date 2022/10/25 16:00
  7. @description 責任鏈管理類 / public class HandleChainManager { /*

    • 存放責任鏈路上的具體處理類
    • k-具體業務場景名稱
    • v-具體業務場景下的責任鏈路集合 */ private Map> handleMap;

    /* * 存放系統中責任鏈具體處理類 * @param handlerList / public void setHandleMap(List handlerList) { handleMap = handlerList .stream() .sorted(Comparator.comparingInt(h -> AnnotationUtils.findAnnotation(h.getClass(), Duty.class).order())) .collect(Collectors.groupingBy(handler -> AnnotationUtils.findAnnotation(handler.getClass(), Duty.class).type())); }

    / * 執行具體業務場景中的責任鏈集合 * @param type 對應@Duty註解中的type,可以定義為具體業務場景 * @param t 被執行的引數 */ public R executeHandle(String type, T t) { List handlers = handleMap.get(type); R r = null; if (CollectionUtil.isNotEmpty(handlers)) { for (IHandler handler : handlers) { r = handler.handle(t); } } return r; } } 4、定義一個配置類`PatternConfiguration`,用於裝配上面的責任鏈管理器`HandleChainManager`。java / * @author 往事如風 * @version 1.0 * @date 2022/10/25 15:35 * @description 設計模式配置類 */ @Configuration public class PatternConfiguration {

    @Bean public HandleChainManager handlerChainExecute(List handlers) { HandleChainManager handleChainManager = new HandleChainManager(); handleChainManager.setHandleMap(handlers); return handleChainManager; }

} 5、具體的處理類:`SignChainHandler`、`EncryptionChainHandler`、`RequestChainHandler`,這裡我以`SignChainHandler`為例。 在具體處理類上標記自定義註解`@Duty`,該類會被注入到bean容器中,實現`IHandler`介面,只需關心自己的handle方法,處理具體的業務邏輯。java / * @author 往事如風 * @version 1.0 * @date 2022/10/25 15:31 * @description 加簽類 */ @Duty(type = BusinessConstants.REQUEST, order = 1) public class SignChainHandler implements IHandler { / * 處理加簽邏輯 * @param s * @return / @Override public String handle(String s) { // 加簽邏輯 System.out.println("甲方爸爸要求加簽"); return "加簽"; } } 6、具體怎麼呼叫?這裡我寫了個測試controller直接呼叫,具體如下:java / * @author 往事如風 * @version 1.0 * @date 2022/9/6 17:32 * @description / @RestController @Slf4j public class TestController {

@Resource
private HandleChainManager handleChainManager;

@PostMapping("/send")
public String duty(@RequestBody String requestBody) {
    String response = handleChainManager.executeHandle(BusinessConstants.REQUEST, requestBody);
    return response;
}

} ``` 7、執行結果,會按照註解中標記的order依次執行。

圖四.jpg

至此,完工。又可以開心的擼程式碼了,然後在具體的執行類中,又是一頓if-else。。。

四、原始碼中運用

4.1Mybatis原始碼中的運用

Mybatis中的快取介面Cache,cache作為一個快取介面,最主要的功能就是新增和獲取快取的功能,作為介面它有11個實現類,分別實現不同的功能,下面是介面原始碼和實現類。 ```java package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache { String getId();

void putObject(Object var1, Object var2);

Object getObject(Object var1);

Object removeObject(Object var1);

void clear();

int getSize();

default ReadWriteLock getReadWriteLock() {
    return null;
}

} ```

圖五.jpg 下面,我們來看下其中一個子類LoggingCache的原始碼。主要看他的putObject方法和getObject方法,它在方法中直接傳給下一個實現去執行。這個實現類其實是為了在獲取快取的時候列印快取的命中率的。 ```java public class LoggingCache implements Cache { private final Log log; private final Cache delegate; protected int requests = 0; protected int hits = 0;

public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(this.getId());
}

// ...
public void putObject(Object key, Object object) {
    this.delegate.putObject(key, object);
}

public Object getObject(Object key) {
    ++this.requests;
    Object value = this.delegate.getObject(key);
    if (value != null) {
        ++this.hits;
    }

    if (this.log.isDebugEnabled()) {
        this.log.debug("Cache Hit Ratio [" + this.getId() + "]: " + this.getHitRatio());
    }

    return value;
}
// ...

} 最後,經過`Cache`介面各種實現類的處理,最終會到達`PerpetualCache`這個實現類。與之前的處理類不同的是,這個類中有一個map,在map中做存取,也就是說,最終快取還是會儲存在map中的。java public class PerpetualCache implements Cache { private final String id; private final Map cache = new HashMap();

public PerpetualCache(String id) {
    this.id = id;
}

// ...

public void putObject(Object key, Object value) {
    this.cache.put(key, value);
}

public Object getObject(Object key) {
    return this.cache.get(key);
}
// ...

} ```

4.2spring原始碼中的運用

4.2.1DispatcherServlet類

DispatcherServlet 核心方法 doDispatch。HandlerExecutionChain只是維護HandlerInterceptor的集合,可以向其中註冊相應的攔截器,本身不直接處理請求,將請求分配給責任鏈上註冊處理器執行,降低職責鏈本身與處理邏輯之間的耦合程度。 java protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }

4.2.2HandlerExecutionChain類

這裡分析的幾個方法,都是從DispatcherServlet類的doDispatch方法中請求的。

  • 獲取攔截器,執行preHandle方法 java boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; }

  • 在applyPreHandle方法中,執行triggerAfterCompletion方法 java void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for(int i = this.interceptorIndex; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable var8) { logger.error("HandlerInterceptor.afterCompletion threw exception", var8); } } } }

  • 獲取攔截器,執行applyPostHandle方法 java void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } }

五、總結

5.1 優點

  1. 將請求與處理解耦。
  2. 請求處理者(節點物件)只需要關注自己感興趣的請求進行處理即可,對於不感興趣的請求,轉發給下一個節點。
  3. 具備鏈式傳遞處理請求功能,請求傳送者無需知曉鏈路結構,只需等待請求處理結果。
  4. 鏈路結構靈活,可以通過改變鏈路的結構動態的新增或刪減責任。
  5. 易於擴充套件新的請求處理類(節點),符合開閉原則

5.2 缺點

  1. 責任鏈太長或者處理時間過長,會影響整體效能。
  2. 如果節點物件存在迴圈引用時,會造成死迴圈,導致系統崩潰。

六、參考原始碼

``` 程式設計文件: https://gitee.com/cicadasmile/butte-java-note

應用倉庫: https://gitee.com/cicadasmile/butte-flyer-parent ```