一、概述
責(zé)任鏈模式(Chain of Responsibility Pattern)是將鏈中每一個節(jié)點看作是一個對象,每個節(jié)點處理的請求均不同,且內(nèi)部自動維護一個下一節(jié)點對象。當(dāng)一個請求從鏈?zhǔn)降氖锥税l(fā)出時,會沿著鏈的路徑依次傳遞給每一個節(jié)點對象,直至有對象處理這個請求為止,屬于行為型模式。下面放一張足球比賽的圖,通過層層傳遞,最終射門。通過這張圖,可以更好的理解責(zé)任鏈模式。

二、入門案例
2.1 類圖

2.2 基礎(chǔ)類介紹
抽象接口RequestHandler
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 13:41
 * @description
 */
public interface RequestHandler {
    void doHandler(String req);
}
抽象類BaseRequestHandler
/**
 * @author 往事如風(fēng)
 * @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
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 14:00
 * @description
 */
public class AHandler extends BaseRequestHandler {
    @Override
    public void doHandler(String req){
        // 處理自己的業(yè)務(wù)邏輯
        System.out.println("A中處理自己的邏輯");
        // 傳遞給下個類(若鏈路中還有下個處理類)
        if (next != null) {
            next.doHandler(req);
        }
    }
}
當(dāng)然還有具體的處理類B、C等等,這里不展開贅述。
 使用類Client
/**
 * @author 往事如風(fēng)
 * @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("鏈路待處理的數(shù)據(jù)");
    }
}
2.3 處理流程圖

三、應(yīng)用場景
3.1 場景舉例
場景一
前兩年,在一家金融公司待過一段時間,其中就有一個業(yè)務(wù)場景:一筆訂單進來,會先在后臺通過初審人員進行審批,初審不通過,訂單流程結(jié)束。初審?fù)ㄟ^以后,會轉(zhuǎn)給終審人員進行審批,不通過,流程結(jié)束;通過,流轉(zhuǎn)到下個業(yè)務(wù)場景。對于這塊業(yè)務(wù)代碼,之前一代目是一個叫知了的同事,他擼起袖子就是干,一套if-else干到底。后來,技術(shù)老大CodeReview,點名要求改掉這塊。于是乎,想到用用設(shè)計模式吧,然后就噼里啪啦一頓改。(當(dāng)然,比較復(fù)雜的情況,還是可以用工作流來處理這個場景,當(dāng)時礙于時間成本,也就放棄了)。
場景二
上家公司對接甲方爸爸的時候,對方會調(diào)用我們接口,將數(shù)據(jù)同步過來。同樣,我們需要將處理好的數(shù)據(jù),傳給他們。由于雙方傳輸數(shù)據(jù)都是加密傳輸,所以在接受他們數(shù)據(jù)之前,需要對數(shù)據(jù)進行解密,驗簽,參數(shù)校驗等操作。同樣,我們給他們傳數(shù)據(jù)也需要進行加簽,加密操作。
具體案例
話不多說,對于場景二,我來放一些偽代碼,跟大家一起探討下。1、一切從注解開始,我這里自定義了一個注解@Duty?,這個注解有spring的@Component注解,也就是標(biāo)記了這個自定義注解的類,都是交給spring的bean容器去管理。注解中,有兩個屬性:1.type,定義相同的type類型的bean,會被放到一個責(zé)任鏈集合中。2.order,同一個責(zé)任鏈集合中,bean的排序,數(shù)值越小,會放到鏈路最先的位置,優(yōu)先處理。
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 16:11
 * @description
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Duty {
    /**
     * 標(biāo)記具體業(yè)務(wù)場景
     * @return
     */
    String type() default "";
    /**
     * 排序:數(shù)值越小,排序越前
     * @return
     */
    int order() default 0;
}
2、定義一個頂層的抽象接口IHandler,傳入2個泛型參數(shù),供后續(xù)自定義。
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 15:31
 * @description 責(zé)任鏈頂層抽象類
 */
public interface IHandler<T, R> {
    /**
     * 抽象處理類
     * @param t
     * @return
     */
    R handle(T t);
}
3、定義一個責(zé)任鏈bean的管理類HandleChainManager,用來存放不同業(yè)務(wù)下的責(zé)任鏈路集合。在該類中,有一個Map和兩個方法。
handleMap:這個map會存放責(zé)任鏈路中,具體的執(zhí)行類,key是注解@Duty?中定義的type值,value是標(biāo)記了@Duty注解的bean集合,也就是具體的執(zhí)行類集合。
setHandleMap:傳入具體執(zhí)行bean的集合,存放在map中。
executeHandle:從map中找到具體的執(zhí)行bean集合,并依次執(zhí)行。
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 16:00
 * @description 責(zé)任鏈管理類
 */
public class HandleChainManager {
    /**
     * 存放責(zé)任鏈路上的具體處理類
     * k-具體業(yè)務(wù)場景名稱
     * v-具體業(yè)務(wù)場景下的責(zé)任鏈路集合
     */
    private Map<String, List<IHandler>> handleMap;
    /**
     * 存放系統(tǒng)中責(zé)任鏈具體處理類
     * @param handlerList
     */
    public void setHandleMap(List<IHandler> 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()));
    }
    /**
     * 執(zhí)行具體業(yè)務(wù)場景中的責(zé)任鏈集合
     * @param type 對應(yīng)@Duty注解中的type,可以定義為具體業(yè)務(wù)場景
     * @param t 被執(zhí)行的參數(shù)
     */
    public <T, R> R executeHandle(String type, T t){
        List<IHandler> handlers = handleMap.get(type);
        R r = null;
        if (CollectionUtil.isNotEmpty(handlers)) {
            for (IHandler<T, R> handler : handlers) {
               r = handler.handle(t);
            }
        }
        return r;
    }
}
4、定義一個配置類PatternConfiguration?,用于裝配上面的責(zé)任鏈管理器HandleChainManager。
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 15:35
 * @description 設(shè)計模式配置類
 */
@Configuration
public class PatternConfiguration {
    @Bean
    public HandleChainManager handlerChainExecute(List<IHandler> handlers){
        HandleChainManager handleChainManager = new HandleChainManager();
        handleChainManager.setHandleMap(handlers);
        return handleChainManager;
    }
}
5、具體的處理類:SignChainHandler、EncryptionChainHandler、RequestChainHandler?,這里我以SignChainHandler?為例。在具體處理類上標(biāo)記自定義注解@Duty?,該類會被注入到bean容器中,實現(xiàn)IHandler接口,只需關(guān)心自己的handle方法,處理具體的業(yè)務(wù)邏輯。
/**
 * @author 往事如風(fēng)
 * @version 1.0
 * @date 2022/10/25 15:31
 * @description 加簽類
 */
@Duty(type = BusinessConstants.REQUEST, order = 1)
public class SignChainHandler implements IHandler<String, String> {
    /**
     * 處理加簽邏輯
     * @param s
     * @return
     */
    @Override
    public String handle(String s){
        // 加簽邏輯
        System.out.println("甲方爸爸要求加簽");
        return "加簽";
    }
}
6、具體怎么調(diào)用?這里我寫了個測試controller直接調(diào)用,具體如下:
/**
 * @author 往事如風(fēng)
 * @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、執(zhí)行結(jié)果,會按照注解中標(biāo)記的order依次執(zhí)行。 

至此,完工。又可以開心的擼代碼了,然后在具體的執(zhí)行類中,又是一頓if-else。。。
四、源碼中運用
4.1Mybatis源碼中的運用
Mybatis中的緩存接口Cache,cache作為一個緩存接口,最主要的功能就是添加和獲取緩存的功能,作為接口它有11個實現(xiàn)類,分別實現(xiàn)不同的功能,下面是接口源碼和實現(xiàn)類。
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;
    }
}

下面,我們來看下其中一個子類LoggingCache的源碼。主要看他的putObject方法和getObject方法,它在方法中直接傳給下一個實現(xiàn)去執(zhí)行。這個實現(xiàn)類其實是為了在獲取緩存的時候打印緩存的命中率的。
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;
    }
    // ...
}最后,經(jīng)過Cache?接口各種實現(xiàn)類的處理,最終會到達PerpetualCache這個實現(xiàn)類。與之前的處理類不同的是,這個類中有一個map,在map中做存取,也就是說,最終緩存還是會保存在map中的。
public class PerpetualCache implements Cache {
    private final String id;
    private final Map<Object, Object> 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的集合,可以向其中注冊相應(yīng)的攔截器,本身不直接處理請求,將請求分配給責(zé)任鏈上注冊處理器執(zhí)行,降低職責(zé)鏈本身與處理邏輯之間的耦合程度。
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方法中請求的。
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方法中,執(zhí)行triggerAfterCompletion方法
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);
            }
        }
    }
}
獲取攔截器,執(zhí)行applyPostHandle方法
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);
        }
    }
}
五、總結(jié)
5.1 優(yōu)點
- 將請求與處理解耦。
 - 請求處理者(節(jié)點對象)只需要關(guān)注自己感興趣的請求進行處理即可,對于不感興趣的請求,轉(zhuǎn)發(fā)給下一個節(jié)點。
 - 具備鏈?zhǔn)絺鬟f處理請求功能,請求發(fā)送者無需知曉鏈路結(jié)構(gòu),只需等待請求處理結(jié)果。
 - 鏈路結(jié)構(gòu)靈活,可以通過改變鏈路的結(jié)構(gòu)動態(tài)的新增或刪減責(zé)任。
 - 易于擴展新的請求處理類(節(jié)點),符合開閉原則。
 
5.2 缺點
責(zé)任鏈太長或者處理時間過長,會影響整體性能。
如果節(jié)點對象存在循環(huán)引用時,會造成死循環(huán),導(dǎo)致系統(tǒng)崩潰。
六、參考源碼
?編程文檔:
https://gitee.com/cicadasmile/butte-java-note
應(yīng)用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent