1. 程式人生 > 其它 >設計模式5 責任鏈模式 Chain of Responsibility Pattern

設計模式5 責任鏈模式 Chain of Responsibility Pattern

什麼是責任鏈模式

客戶端發出一個請求,鏈上的物件都有機會來處理這一請求,而客戶端不需要知道誰是具體的處理物件。這樣就實現了請求者和接受者之間的解耦,並且在客戶端可以實現動態的組合職責鏈。使程式設計更有靈活性。

定義:使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。其過程實際上是一個遞迴呼叫。

要點主要是:

1、有多個物件共同對一個任務進行處理。
2、這些物件使用鏈式儲存結構,形成一個鏈,每個物件知道自己的下一個物件。
3、一個物件對任務進行處理,可以新增一些操作後將物件傳遞個下一個任務。也可以在此物件上結束任務的處理,並結束任務。
4、客戶端負責組裝鏈式結構,但是客戶端不需要關心最終是誰來處理了任務。

責任鏈模式類結構圖

 1.抽象處理者(Handler)角色:定義出一個處理請求的介面。如果需要,介面可以定義 出一個方法以設定和返回對下家的引用。
  這個角色通常由一個Java抽象類或者Java介面實現。上圖中Handler類的聚合關係給出了具體子類對下家的引用,
  抽象方法handleRequest()規範了子類處理請求的操作。  
2.具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇將請求處理掉,或者將請求傳給下家。
  由於具體處理者持有對下家的引用,因此,如果需要,具體處理者可以訪問下家

責任鏈模式優缺點

優點:
職責鏈模式的最主要功能就是:動態組合,請求者和接受者解耦。
請求者和接受者鬆散耦合:請求者不需要知道接受者,也不需要知道如何處理。每個職責物件只負責自己的職責範圍,
其他的交給後繼者。各個元件間完全解耦。 動態組合職責:職責鏈模式會把功能分散到單獨的職責物件中,然後在使用時動態的組合形成鏈,從而可以靈活的分配職責物件,
也可以靈活的新增改變物件職責。 缺點: 產生很多細粒度的物件:因為功能處理都分散到了單獨的職責物件中,每個物件功能單一,要把整個流程處理完,需要很多的職責物件,
會產生大量的細粒度職責物件。 不一定能處理:每個職責物件都只負責自己的部分,這樣就可以出現某個請求,即使把整個鏈走完,都沒有職責物件處理它。
這就需要提供預設處理,並且注意構造鏈的有效性。

責任鏈模式應用場景

1.多條件流程判斷 許可權控制
2.ERP系統 流程審批 總經理、人事經理、專案經理
3.Java過濾器的底層實現Filter. 比如:在Java過濾器中客戶端傳送請求到伺服器端,過濾會經過引數過濾、session過濾、
  表單過濾、隱藏過濾、檢測請求頭過濾

這裡Demo兩個閘道器許可權控制責任鏈模式

在閘道器作為微服務程式的入口,攔截客戶端所有的請求實現許可權控制 ,比如先判斷Api介面限流、黑名單、使用者會話/引數過濾。

1.基於鏈式的呼叫

1.1GatewayHandler抽象角色,定義鏈市呼叫

public abstract class GatewayHandler {
    protected GatewayHandler nextGatewayHandler;

    /**
     * @return true 表示繼續執行 false表示不繼續執行..
     */
    public abstract void service();

    public void setHandler(GatewayHandler gatewayHandler) {
        this.nextGatewayHandler = gatewayHandler;
    }
    protected void nextService(){
         if(nextGatewayHandler!=null){
             nextGatewayHandler.service();;
         }
    }
}

1.2 具體Handler實現類,具體有三個實現類

@Component
public class CurrentLimitHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("001-閘道器限流判斷....");
        nextService();
    }
}

 

@Component
public class BlacklistHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("002-黑名單攔截判斷....");
        nextService();
    }
}



@Component
public class ConversationHandler extends GatewayHandler {
    @Override
    public void service() {
        System.out.println("003-使用者會話攔截判斷....");
        nextService();
    }
}

1.3 FactoryHandler類,組裝鏈式呼叫的順序

public class FactoryHandler {
    public static GatewayHandler getGatewayHandler() {
        GatewayHandler gatewayHandlerA = new CurrentLimitHandler();
        GatewayHandler gatewayHandlerB = new BlacklistHandler();
        gatewayHandlerA.setHandler(gatewayHandlerB);
        GatewayHandler gatewayHandlerC = new ConversationHandler();
        gatewayHandlerB.setHandler(gatewayHandlerC);
        return gatewayHandlerA;
    }
}

2.基於資料庫實現鏈式呼叫

2.1 資料庫SQL

CREATE TABLE `gateway_handler` (

  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

  `handler_name` varchar(32) DEFAULT NULL COMMENT 'handler名稱',

  `handler_id` varchar(32) DEFAULT NULL COMMENT 'handler主鍵id',

  `prev_handler_id` varchar(32) DEFAULT NULL,

  `next_handler_id` varchar(32) DEFAULT NULL COMMENT '下一個handler',

  PRIMARY KEY (`ID`)

) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='閘道器過濾器';

 

------------------- init data ----------------------------

INSERT INTO `gateway_handler` VALUES ('10086', 'Api介面限流', 'currentLimitHandler', null, 'blacklistHandler');

INSERT INTO `gateway_handler` VALUES ('10087', '黑名單攔截', 'blacklistHandler', 'currentLimitHandler', 'conversationHandler');

INSERT INTO `gateway_handler` VALUES ('10088', '會話驗證', 'conversationHandler', 'blacklistHandler', null);

2.2GatewayHandlerService,組裝鏈式的呼叫

@Component
public class GatewayHandlerService {
    @Autowired
    private GatewayHandlerMapper gatewayHandlerMapper;
    private GatewayHandler firstGatewayHandler;

    public GatewayHandler getDbGatewayHandler() {
        if (firstGatewayHandler != null) {
            return firstGatewayHandler;
        }
        // 獲取第一個GatewayHandler資訊
        GatewayHandlerEntity firstGatewayHandlerEntity = gatewayHandlerMapper.getFirstGatewayHandler();
        if (firstGatewayHandlerEntity == null) {
            return null;
        }
        // 獲取第一個firstGatewayHandler spring容器中的id
        String handlerBeanId = firstGatewayHandlerEntity.getHandlerId();
        // 從spring容器中獲取對應的物件 firstGatewayHandler
        GatewayHandler firstGatewayHandler = SpringUtils.getBean(handlerBeanId, GatewayHandler.class);
        // 使用white迴圈 設定下一個節點 同時定義迴圈遍歷臨時物件
        GatewayHandler tempGatewayHandler = firstGatewayHandler;
        // 獲取下一個節點
        String nextHandlerBeanId = firstGatewayHandlerEntity.getNextHandlerId();
        while (!StringUtils.isEmpty(nextHandlerBeanId)) {
            GatewayHandlerEntity nextGatewayHandlerEntity = gatewayHandlerMapper.getByHandler(nextHandlerBeanId);
            if (nextGatewayHandlerEntity == null) {
                break;
            }
            // 從springboot容器獲取下一個handler 物件
            String tempNextHandlerBeanId = nextGatewayHandlerEntity.getHandlerId();
            GatewayHandler nextGatewayHandler = SpringUtils.getBean(tempNextHandlerBeanId, GatewayHandler.class);
            // 設定當前handler下一個handler物件
            tempGatewayHandler.setHandler(nextGatewayHandler);
            tempGatewayHandler = nextGatewayHandler;
            // 迴圈遍歷下一個節點
            nextHandlerBeanId = nextGatewayHandlerEntity.getNextHandlerId();
        }
        this.firstGatewayHandler = firstGatewayHandler;
        return firstGatewayHandler;
    }
}

2.3GatewayHandlerMapper,從資料庫GatewayHandler

public interface GatewayHandlerMapper {

   /**
    * 獲取第一個GatewayHandler
    * @return
    */
   @Select("SELECT  handler_name AS handlerName,handler_id AS handlerid ,prev_handler_id AS prevhandlerid ,
        next_handler_id AS nexthandlerid FROM gateway_handler WHERE prev_handler_id is null;") public GatewayHandlerEntity getFirstGatewayHandler(); @Select("SELECT handler_name AS handlerName,handler_id AS handlerid ,prev_handler_id AS prevhandlerid ,
    next_handler_id AS nexthandlerid FROM gateway_handler WHERE handler_id=#{handlerId}") public GatewayHandlerEntity getByHandler(String handlerId); } @Data public class GatewayHandlerEntity implements Serializable, Cloneable { /** 主鍵ID */ private Integer id; /** handler名稱 */ private String handlerName; /** handler主鍵id */ private String handlerId; /** 下一個handler */ private String nextHandlerId; }