設計模式5 責任鏈模式 Chain of Responsibility Pattern
阿新 • • 發佈:2021-06-14
什麼是責任鏈模式
客戶端發出一個請求,鏈上的物件都有機會來處理這一請求,而客戶端不需要知道誰是具體的處理物件。這樣就實現了請求者和接受者之間的解耦,並且在客戶端可以實現動態的組合職責鏈。使程式設計更有靈活性。
定義:使多個物件都有機會處理請求,從而避免了請求的傳送者和接受者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。其過程實際上是一個遞迴呼叫。
要點主要是:
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; }