JAVA設計模式之責任鏈設計模式

責任鏈模式:責任鏈模式(Chain of Responsibility Pattern)爲請求建立了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬於行爲型模式。mysql

在這種模式中,一般每一個接收者都包含對另外一個接收者的引用。若是一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。spring

主要解決:職責鏈上的處理者負責處理請求,客戶只須要將請求發送到職責鏈上便可,無須關心請求的處理細節和請求的傳遞,因此職責鏈將請求的發送者和請求的處理者解耦了。sql

什麼時候使用:在處理消息的時候以過濾不少道。數據庫

如何解決:攔截的類都實現統一接口。設計模式

關鍵代碼:Handler 裏面聚合它本身,在 HandlerRequest 裏判斷是否合適,若是沒達到條件則向下傳遞,向誰傳遞以前 set 進去。api

應用實例: 一、紅樓夢中的"擊鼓傳花"。 二、JS 中的事件冒泡。 三、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。springboot

優勢: 一、下降耦合度。它將請求的發送者和接收者解耦。 二、簡化了對象。使得對象不須要知道鏈的結構。 三、加強給對象指派職責的靈活性。經過改變鏈內的成員或者調動它們的次序,容許動態地新增或者刪除責任。 四、增長新的請求處理類很方便。數據結構

缺點: 一、不能保證請求必定被接收。 二、系統性能將受到必定影響,並且在進行代碼調試時不太方便,可能會形成循環調用。 三、可能不容易觀察運行時的特徵,有礙於除錯。mybatis

廢話很少說,直接上手:
項目背景:網關做爲微服務項目入口,攔截客戶端全部的請求實現權限控制,如判斷API接口限流->黑名單攔截->用戶會話->參數過濾 app

環境準備:JDK八、springboot2.0.x、idea工具、mysql數據庫...
總體流程能夠縮概爲以下圖所示:
圖片.png

一、定義抽象的handler接口

/**
 * 網關攔截器
 * @author zhoumin
 * @create 2020-03-14 13:20
 */
public abstract class GatewayHandler {

    /**
     * 使用抽象類定義共同的行爲方法
     */
    public abstract void service();
}

二、定義具體實現事項

/**
 * @author zhoumin
 * @create 2020-03-14 13:25
 */
public class ApiLimitHandler extends GatewayHandler {
    //這裏須要指定下一個handler
    private BlacklistHandler blacklistHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("一、api接口限流.........");
        //下一個handler
        blacklistHandler.service();
    }

    //指定下一個handler
    public void setNextGatewayHandler(BlacklistHandler blacklistHandler) {
        this.blacklistHandler = blacklistHandler;
    }
}
/**
 * 黑名單攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:26
 */
public class BlacklistHandler extends GatewayHandler {
    //這裏須要指定下一個handler
    private ConversationHandler conversationHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("二、黑名單攔截.........");
        //下一個handler
        conversationHandler.service();
    }

    //指定下一個handler
    public void setNextGatewayHandler(ConversationHandler conversationHandler) {
        this.conversationHandler = conversationHandler;
    }
}
/**
 * 用戶會話攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:27
 */
public class ConversationHandler extends GatewayHandler {
    //這裏須要指定下一個handler
    private ParamHandler paramHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("三、用戶會話攔截.........");
        //下一個handler
        paramHandler.service();
    }

    //指定下一個handler
    public void setNextGatewayHandler(ParamHandler paramHandler) {
        this.paramHandler = paramHandler;
    }
}
/**
 * 參數攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:30
 */
public class ParamHandler extends GatewayHandler {
    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("四、用戶參數過濾.........");
    }
}

注意:須要在每一個具體handler裏面執行下一個須要執行的handler,直到最後一個

這個時候發現,只要獲取第一個handler後,並執行,那麼整個鏈路就能順利完成,那麼怎麼獲取第一個handler呢?答案是:使用工廠模型

/**
 * 工廠建立對象
 *
 * @author zhoumin
 * @create 2020-03-14 13:49
 */
public class FactoryHandler {

    //這裏static 爲了方便後面調試運行
    public static ApiLimitHandler getFirstGatewayHandler(){
        ApiLimitHandler apiLimitHandler = new ApiLimitHandler();
        BlacklistHandler blacklistHandler = new BlacklistHandler();
        apiLimitHandler.setNextGatewayHandler(blacklistHandler);
        ConversationHandler conversationHandler = new ConversationHandler();
        blacklistHandler.setNextGatewayHandler(conversationHandler);
        ParamHandler paramHandler = new ParamHandler();
        conversationHandler.setNextGatewayHandler(paramHandler);
        return apiLimitHandler;
    }
}

至此,完成了整個流程,如今能夠來簡單測試下

/**
 * @author zhoumin
 * @create 2020-03-14 14:49
 */
@RestController
public class HandlerController {

    @GetMapping("/clientHandler")
    public String clientHandler(){
        ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler();
        apiLimitHandler.service();
        return "success!!";
    }
}

運行後能夠看到控制檯打印數據:
圖片.png

斷點調試能夠清晰看到整個鏈路結構:
圖片.png

我是一個分割線emmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.....

美中不足,咱們發現代碼有不少冗餘,包括setNextGatewayHandler以及出如今每一個handler中的下個handler,那麼如何優化呢?------------->將公共方法抽取到父類中!

/**
 * 網關攔截器
 * @author zhoumin
 * @create 2020-03-14 13:20
 */
public abstract class GatewayHandler {
    protected GatewayHandler nextGatewayHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    public abstract void service();

    //設置下一個handler
    public void setNextGatewayHandler(GatewayHandler gatewayHandler){
        this.nextGatewayHandler = gatewayHandler;
    }

    //執行下一個handler
    protected void nextService(){
        if (nextGatewayHandler != null){
            nextGatewayHandler.service();
        }
    }
}

子類優化:

/**
 * @author zhoumin
 * @create 2020-03-14 13:25
 */
public class ApiLimitHandler extends GatewayHandler {
    //這裏須要指定下一個handler
//    private BlacklistHandler blacklistHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("一、api接口限流.........");
        //下一個handler
//        blacklistHandler.service();
        nextService();
    }

    //指定下一個handler
//    public void setNextGatewayHandler(BlacklistHandler blacklistHandler) {
//        this.blacklistHandler = blacklistHandler;
//    }
}
/**
 * 黑名單攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:26
 */
public class BlacklistHandler extends GatewayHandler {
    //這裏須要指定下一個handler
//    private ConversationHandler conversationHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("二、黑名單攔截.........");
        //下一個handler
//        conversationHandler.service();
        nextService();
    }

    //指定下一個handler
//    public void setNextGatewayHandler(ConversationHandler conversationHandler) {
//        this.conversationHandler = conversationHandler;
//    }
}
/**
 * 用戶會話攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:27
 */
public class ConversationHandler extends GatewayHandler {
    //這裏須要指定下一個handler
//    private ParamHandler paramHandler;

    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("三、用戶會話攔截.........");
        //下一個handler
//        paramHandler.service();
        nextService();
    }

    //指定下一個handler
//    public void setNextGatewayHandler(ParamHandler paramHandler) {
//        this.paramHandler = paramHandler;
//    }
}
/**
 * 參數攔截
 *
 * @author zhoumin
 * @create 2020-03-14 13:30
 */
public class ParamHandler extends GatewayHandler {
    /**
     * 使用抽象類定義共同的行爲方法
     */
    @Override
    public void service() {
        System.out.println("四、用戶參數過濾.........");
    }
}

運行結果跟上圖保持一致。

至此,結束了嗎?顯然沒有,繼續.....

能夠看到咱們在Factory中定義的關係強耦合,若是如今咱們須要改變順序或者是新增其餘handler,須要改動代碼,可擴展性較差。

結合上一章,啓示能夠把beanId關係維護到數據庫中,利用spring容器,根據beanId獲取到每一個具體的實例對象(注:須要將上面每個具體handler注入到spring容器中)。

現有數據結構和數據關係以下:
圖片.png

能夠很清晰看到,第一個handler的prev爲null,next爲咱們指定的handler;而最後一個handler的next爲null。

定義咱們的實體類以及mapper以下:

/**
 * @author zhoumin
 * @create 2020-03-14 15:35
 */
@Data
public class GatewayHandlerEntity {
    /** 主鍵ID */
    private Integer id;

    /** handler名稱 */
    private String handlerName;

    /** handler主鍵id */
    private String handlerId;

    /** 下一個handler */
    private String nextHandlerId;

}
/**
 * @author zhoumin
 * @create 2020-03-14 15:33
 */
@Mapper
public interface GatewayHandlerMapper {
    /**
     * 獲取第一個handler
     * @return
     */
    GatewayHandlerEntity getFirstGatewayHandler();

    /**
     * 根據beanId獲取當前handler
     * @return
     */
    GatewayHandlerEntity getByHandler(String handlerId);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zm.test.mapper.GatewayHandlerMapper">
    <select id="getFirstGatewayHandler" resultType="com.zm.test.entity.GatewayHandlerEntity">
        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
    </select>

    <select id="getByHandler" resultType="com.zm.test.entity.GatewayHandlerEntity">
        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}
    </select>
</mapper>

對應的service能夠處理爲:

/**
 * @author zhoumin
 * @create 2020-03-14 15:38
 */
@Component
public class GatewayHandlerService {
    @Resource
    private GatewayHandlerMapper gatewayHandlerMapper;

    private GatewayHandler firstGatewayHandler;

    public GatewayHandler getDbFirstGatewayHandler() {
        //判斷是否加載過
        if (this.firstGatewayHandler != null) {
            return this.firstGatewayHandler;
        }
        // 1.查詢數據庫第一個handler配置
        GatewayHandlerEntity firstGatewayHandlerEntity = gatewayHandlerMapper.getFirstGatewayHandler();
        if (firstGatewayHandlerEntity == null) {
            return null;
        }
        String firstBeanHandlerId = firstGatewayHandlerEntity.getHandlerId();
        if (StringUtils.isEmpty(firstBeanHandlerId)) {
            return null;
        }
        // 2.根據beanId,從SpringBoot容器獲取第一個handler對象
        GatewayHandler firstGatewayHandler = SpringUtils.getBean(firstBeanHandlerId, GatewayHandler.class);
        if (firstGatewayHandler == null) {
            return null;
        }
        // 3. 獲取下一個handlerBeanId
        String nextBeanHandlerId = firstGatewayHandlerEntity.getNextHandlerId();
        // 定義臨時循環遍歷指針
        GatewayHandler tempNextGatewayHandler = firstGatewayHandler;
        while (StringUtils.isNotEmpty(nextBeanHandlerId)) {
            // 4.根據beanId,從SpringBoot容器獲取下一個handler對象
            GatewayHandler nextGatewayHandler = SpringUtils.getBean(nextBeanHandlerId, GatewayHandler.class);
            if (nextGatewayHandler == null) {
                break;
            }
            // 5.從數據庫查詢該hanlder信息,從而獲取下一個beanId
            GatewayHandlerEntity nextGatewayHandlerEntity = gatewayHandlerMapper.getByHandler(nextBeanHandlerId);
            if (nextGatewayHandlerEntity == null) {
                break;
            }
            // 6.設置下一個white循環遍歷hanlderid
            nextBeanHandlerId = nextGatewayHandlerEntity.getNextHandlerId();
            tempNextGatewayHandler.setNextGatewayHandler(nextGatewayHandler);
            tempNextGatewayHandler = nextGatewayHandler;
        }

        //設置只在啓動時加載查詢一次
        this.firstGatewayHandler = firstGatewayHandler;
        return firstGatewayHandler;
    }
}

相應,作一次測試:

/**
 * @author zhoumin
 * @create 2020-03-14 14:49
 */
@RestController
public class HandlerController {
    @Autowired
    private GatewayHandlerService gatewayHandlerService;

    @GetMapping("/clientHandler")
    public String clientHandler(){
        ApiLimitHandler apiLimitHandler = FactoryHandler.getFirstGatewayHandler();
        apiLimitHandler.service();
        return "success!!";
    }

    @GetMapping("/clientHandler2")
    public String clientHandler2(){
        GatewayHandler gatewayHandler = gatewayHandlerService.getDbFirstGatewayHandler();
        gatewayHandler.service();
        return "success!!";
    }
}

運行後,數據以下:
圖片.png

若是想調節順序,或者是新增節點,只須要修改數據庫便可,相應,咱們也能夠把這塊放到咱們本身的管理後臺管理。

在經常使用代碼中,過濾器Filter就是這一模型很好的應用實例,具體能夠看下:
圖片.png

doFilter即相似咱們這裏的nextService()方法。

圖片.png

完~

相關文章
相關標籤/搜索