當前位置: 妍妍網 > 碼農

進階玩法:策略+責任鏈+組合實作合約簽章

2024-06-09碼農

來源:juejin.cn/post/7256307512244650040

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書

新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計43w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,

  • 1 前置內容

  • 2 簽章的處理流程

  • 3 流程實作

  • 計畫結構

  • 計畫類圖

  • 責任鏈+組合模式程式碼實作

  • 策略+責任鏈+組合程式碼實作

  • 1 前置內容

  • 掌握策略模式

  • 掌握責任鏈模式

  • 掌握類繼承、介面的實作

  • 掌握參數的傳遞與設定

  • GitHub地址

  • ps:【文章由來】 公司計畫中所用的合約簽章處理流程,本人基於責任鏈上使用策略模式進行最佳化。

    2 簽章的處理流程

  • 合約文本初始化

  • 合約文本生成

  • 簽章擋板是否開啟

  • 合約簽章發送mq

  • 合約簽章流水更新

  • 合約上傳檔伺服器

  • 簽章渠道選擇

  • 簽章渠道的實際呼叫

  • 執行的流程如下:

    圖片

    整個結構類似於遞迴呼叫。每個節點中依賴上一個節點的輸入以及下一個節點的輸出,在中間過程可以實作每個節點的自訂操作,比較靈活。

    3 流程實作

    GitHub地址

    https://github.com/xbhog/DesignPatternsStudy

    計畫結構

    DesignPatterns
    └── src
    └── main
    └── java
    └── com.xbhog.chainresponsibility
    ├── annotations
    │ └── ContractSign
    ├── channel
    │ ├── ContractSignChannelImpl.java
    │ └── ContractSignChannel
    ├── Config
    │ └── SignConfig
    ├── Enum
    │ └── ContractSignEnum
    ├── impl
    │ ├── ContractSignCompactInitImpl.java
    │ ├── ContractSignGenerateImpl.java
    │ ├── ContractSignMockImpl.java
    │ ├── ContractSignMqImpl.java
    │ ├── ContractSignSaveUploadImpl.java
    │ ├── ContractSignSerialImpl.java
    │ └── ContractSignTradeImpl.java
    ├── inter
    │ ├── Call
    │ ├── Chain
    │ ├── Interceptor
    │ └── Processor
    ├── pojo
    │ ├── ContractRequest.java
    │ └── ContractResponse.java
    ├── ContractCall
    ├── ContractChain
    └── ContractSignProcessor.java

    計畫類圖

    圖片

    責任鏈+組合模式程式碼實作

    工程結構

    DesignPatterns
    └── src
    └── main
    └── java
    └── com.xbhog.chainresponsibility
    ├── channel
    │ ├── ContractSignChannelImpl.java
    │ └── ContractSignChannel
    ├── impl
    │ ├── ContractSignCompactInitImpl.java
    │ ├── ContractSignGenerateImpl.java
    │ ├── ContractSignMockImpl.java
    │ ├── ContractSignMqImpl.java
    │ ├── ContractSignSaveUploadImpl.java
    │ ├── ContractSignSerialImpl.java
    │ └── ContractSignTradeImpl.java
    ├── inter
    │ ├── Call
    │ ├── Chain
    │ ├── Interceptor
    │ └── Processor
    ├── pojo
    │ ├── ContractRequest.java
    │ └── ContractResponse.java
    ├── ContractCall
    ├── ContractChain
    └── ContractSignProcessor.java

    責任鏈中的物件定義

    //請求
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class ContractRequest {
    private String name;
    private String age;
    private String status;
    }
    //響應
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class ContractResponse {
    private String status;
    private String mas;
    }


    定義流程中的請求及響應類,方便處理每個責任鏈的請求、返回資訊。

    責任鏈處理流程

    /**
     * @author xbhog
     * @describe: 責任鏈+組合實作合約簽章
     * @date 2023/7/11
     */
    @Slf4j
    @Component
    public class ContractSignProcessor <T extends ContractRequest> implements Processor<T, ContractResponse> {
    @Resource(name = "contractSignCompactInitImpl")
    private Interceptor<T,ContractResponse> contractCompactInitImpl;
    ......

    public ContractSignProcessor() {
    }
    @Override
    public ContractResponse process(T paramter) {
    //獲取所有的監聽器
    List<Interceptor<T,ContractResponse>> interceptorList = new ArrayList<>();
    interceptorList.add(contractCompactInitImpl);
    ......
    //開始簽章
    log.info("簽章開始");
    return new ContractCall(paramter,interceptorList).exectue();
    }
    }

    合約簽章方法的主流程呼叫介面(入口) ,該類中註入所有的節點實作類(如 contractCompactInitImpl ),透過編排實作責任鏈流程。

    在初始化節點之前,進行節點的封裝以及數據請求的處理。例: contractCompactInitImpl -合約數據初始化節點

    /**
     * @author xbhog
     * @describe: 合約數據請求、節點的例項化及方法執行
     * @date 2023/7/11
     */
    public class ContractCall<T extends ContractRequest> implements Call<T, ContractResponse> {
    private final T originalRequest;
    private final List<Interceptor<T,ContractRequest>> interceptorList;
    public ContractCall(T originalRequest, List<Interceptor<T, ContractRequest>> interceptorList) {
    this.originalRequest = originalRequest;
    this.interceptorList = interceptorList;
    }
    @Override
    public T request() {
    return this.originalRequest;
    }
    @Override
    public ContractResponse exectue() {
    //例項化流程節點
    ContractChain<T> chain = new ContractChain(0,this.originalRequest,this.interceptorList);
    return chain.proceed(this.originalRequest);
    }
    }

    獲取節點中的請求參數,例項化當前責任鏈節點( contractCompactInitImpl ),在執行節點中的proceed方法來獲取當前節點的參數以及獲取節點的資訊。

    /**
     * @author xbhog
     * @describe: 合約節點
     * @date 2023/7/11
     */
    @Slf4j
    public class ContractChain<T extends ContractRequest> implements Chain<T, ContractResponse> {
    private final Integer index;
    private final T request;
    private final List<Interceptor<T,ContractResponse>> interceptors;
    public ContractChain(Integer index, T request, List<Interceptor<T, ContractResponse>> interceptors) {
    this.index = index;
    this.request = request;
    this.interceptors = interceptors;
    }
    @Override
    public T request() {
    return this.request;
    }
    @Override
    public ContractResponse proceed(T request) {
    //控制節點流程
    if(this.index >= this.interceptors.size()){
    throw new IllegalArgumentException("index越界");
    }
    //下一個節點參數設定
    Chain<T,ContractResponse> nextChain = new ContractChain(this.index + 1, request, this.interceptors);
    //獲取節點資訊
    Interceptor<T, ContractResponse> interceptor = this.interceptors.get(this.index);
    class<? extends Interceptor> a class = interceptor.get class();
    log.info("當前節點:{}",a class.getSimpleName());
    ContractResponse response = interceptor.process(nextChain);
    if(Objects.isNull(response)){
    throw new NullPointerException("intercetor"+interceptor+"return null");
    }
    return response;
    }
    }



    到此合約簽章的架構流程已經確定,後續只要填充Interceptor具體的實作類即可。 在程式碼中 ContractResponse response = interceptor.process(nextChain); 來執行合約初始化節點的具體操作。

    /**
     * @author xbhog
     * @describe: 合約文本初始化
     * @date 2023/7/12
     */
    @Slf4j
    @Component
    public class ContractSignCompactInitImpl<T extends ContractRequest> implements Interceptor<T, ContractResponse> {
    public ContractSignCompactInitImpl() {
    }
    @Override
    public ContractResponse process(Chain<T,ContractResponse> chain) {
    log.info("=============執行合約文本初始化攔截器開始");
    //獲取處理的請求參數
    T request = chain.request();
    request.setStatus("1");
    log.info("=============執行合約文本初始化攔截器結束");
    //進入下一個責任鏈節點
    ContractResponse response = chain.proceed(request);
    if(Objects.isNull(response)){
    log.error("返回值的為空");
    response = ContractResponse.builder().status("fail").mas("處理失敗").build();
    }
    //其他處理
    return response;
    }
    }

    測試驗證

    @SpringBootTest
    class SPringBootTestApplicationTests {
    @Autowired
    @Qualifier("contractSignProcessor")
    private Processor<ContractRequest,ContractResponse> contractSignProcessor;
    @Test
    void contextLoads() {
    ContractRequest contractRequest = new ContractRequest();
    contractRequest.setName("xbhog");
    contractRequest.setAge("12");
    ContractResponse process = contractSignProcessor.process(contractRequest);
    System.out.println(process);
    }
    }

    在這裏只需要呼叫合約簽章入口的方法即可進入合約簽章的流程。

    2023-07-16 13:25:13.063 INFO 26892 --- [ main] c.e.s.c.ContractSignProcessor : 簽章開始
    2023-07-16 13:25:13.067 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignCompactInitImpl
    2023-07-16 13:25:13.068 INFO 26892 --- [ main] c.e.s.c.i.ContractSignCompactInitImpl : =============執行合約文本初始化攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.i.ContractSignCompactInitImpl : =============執行合約文本初始化攔截器結束
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignGenerateImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignGenerateImpl : =============執行合約文本生成攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignGenerateImpl : =============執行合約文本生成攔截器結束
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignMockImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignMockImpl : =============執行簽章擋板攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignMockImpl : =============執行簽章擋板攔截器結束
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignMqImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignMqImpl : =============執行合約簽章完成發送mq攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignSerialImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSerialImpl : =============執行合約簽章流水處理攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignSaveUploadImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSaveUploadImpl : =============執行合約簽章完成上傳伺服器攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.chainresponsibility.ContractChain : 當前節點:ContractSignTradeImpl
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignTradeImpl : =============執行簽章渠道實際呼叫攔截器開始
    2023-07-16 13:25:13.069 INFO 26892 --- [ main] c.e.s.c.channel.ContractSignChannelImpl : 簽章處理開始
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSaveUploadImpl : 開始上傳伺服器
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSaveUploadImpl : .............
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSaveUploadImpl : 上傳伺服器完成
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSaveUploadImpl : =============執行合約簽章完成上傳伺服器攔截器結束
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignSerialImpl : =============執行合約簽章流水處理攔截器結束
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignMqImpl : 發送MQ給下遊處理數據
    2023-07-16 13:25:13.070 INFO 26892 --- [ main] c.e.s.c.impl.ContractSignMqImpl : =============執行合約簽章完成發送mq攔截器結束
    ContractResponse(status=success, mas=處理成功)

    策略+責任鏈+組合程式碼實作

    以下是完整的合約簽章入口實作類:

    /**
     * @author xbhog
     * @describe: 責任鏈+組合實作合約簽章
     * @date 2023/7/11
     */
    @Slf4j
    @Component
    public class ContractSignProcessor <T extends ContractRequest> implements Processor<T, ContractResponse> {
    @Resource(name = "contractSignCompactInitImpl")
    private Interceptor<T,ContractResponse> contractCompactInitImpl;
    @Resource(name = "contractSignGenerateImpl")
    private Interceptor<T,ContractResponse> contractGenerateImpl;
    @Resource(name = "contractSignMockImpl")
    private Interceptor<T,ContractResponse> contractSignMockImpl;
    @Resource(name = "contractSignMqImpl")
    private Interceptor<T,ContractResponse> contractSignMqImpl;
    @Resource(name = "contractSignSaveUploadImpl")
    private Interceptor<T,ContractResponse> contractSignSaveUploadImpl;
    @Resource(name = "contractSignSerialImpl")
    private Interceptor<T,ContractResponse> contractSignSerialImpl;
    @Resource(name = "contractSignTradeImpl")
    private Interceptor<T,ContractResponse> ContractSignTradeImpl;

    public ContractSignProcessor() {
    }
    @Override
    public ContractResponse process(T paramter) {
    //獲取所有的監聽器
    List<Interceptor<T,ContractResponse>> interceptorList = new ArrayList<>();
    interceptorList.add(contractCompactInitImpl);
    interceptorList.add(contractGenerateImpl);
    interceptorList.add(contractSignMockImpl);
    interceptorList.add(contractSignMqImpl);
    interceptorList.add(contractSignSerialImpl);
    interceptorList.add(contractSignSaveUploadImpl);
    interceptorList.add(ContractSignTradeImpl);
    //開始簽章
    log.info("簽章開始");
    return new ContractCall(paramter,interceptorList).exectue();
    }
    }







    可以看到,目前的合約簽章的處理流程需要的節點數已經7個了,後續如果新增節點或者減少節點都需要對該類進行手動的處理;比如:減少一個節點的流程。

  • 刪除節點實作的註入

  • 刪除list中的bean實作類

  • 為方便後續的拓展(懶是社會進步的加速器,不是),在責任鏈,組合的基礎上透過策略模式來修改bean的註入方式。 完整的計畫結構和計畫類圖就是作者文章開始放的,可返回檢視。 在第一部份的基礎上增加的功能點如下

  • 新增簽章註解

  • 新增簽章節點列舉

  • 新增簽章配置類

  • 簽章註解實作

    package com.example.springboottest.chainresponsibility.annotations;
    import com.example.springboottest.chainresponsibility.Enum.ContractSignEnum;
    import java.lang.annotation.*;
    /**
     * @author xbhog
     * @describe:
     * @date 2023/7/15
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ContractSign {
    ContractSignEnum.SignChannel SIGN_CHANNEL();
    }


    設定註解修飾物件的範圍,主要是對bean的一個註入,所以型別選擇type,

  • TYPE : 用於描述類、介面(包括註解型別) 或enum聲明

  • 設定註解的執行周期(有效範圍),一般是執行時有效,

  • RUNTIME :在執行時有效 (大部份註解的選擇)

  • 設定該註解的數據型別,

  • ENUM :列舉型別,方便統一處理

  • 列舉實作

    package com.xbhog.chainresponsibility.Enum;
    /**
     * @author xbhog
     * @describe:
     * @date 2023/7/15
     */
    public class ContractSignEnum {
    public enum SignChannel {
    SIGN_INIT(1, "合約文本初始化"),
    SIGN_GENERATE(2, "合約文本生成"),
    SIGN_MOCK(3, "簽章擋板"),
    SIGN_MQ(4, "合約簽章完成發送MQ"),
    SIGN_TABLE(5, "合約簽章表處理"),
    SIGN_UPLOAD(6, "合約簽章完成上傳伺服器"),
    SIGN_TRADE(7, "簽章渠道實際呼叫");
    private Integer code;
    private String info;
    SignChannel(Integer code, String info) {
    this.code = code;
    this.info = info;
    }
    ......
    }
    }


    對合約簽章中的流程節點進行統一的配置。

    簽章配置類

    在計畫啟動的時候,透過註解工具類 AnnotationUtils 掃描所有被 ContractSign 註解修飾的類,將這些類透過Map進行儲存,方便後續的呼叫。

    public class SignConfig {
    @Resource
    protected List<Interceptor> contractSignList;
    protected static final Map<Integer,Interceptor> CONTRACT_SIGN_MAP = new ConcurrentHashMap<>();
    @PostConstruct
    public void init(){
    contractSignList.forEach(interceptor -> {
    //尋找這個介面的實作類上有沒有ContractSign註解
    ContractSign sign = AnnotationUtils.findAnnotation(interceptor.get class(), ContractSign. class);
    if(!Objects.isNull(sign)){
    CONTRACT_SIGN_MAP.put(sign.SIGN_CHANNEL().getCode(),interceptor);
    }
    });
    }
    }

    到此,簡化了Bean的註入方式。

    簽章註解使用

    以合約文本初始化 ContractSignCompactInitImpl 來說。

    /**
     * @author xbhog
     * @describe: 合約文本初始化
     * @date 2023/7/12
     */
    @Slf4j
    @ContractSign(SIGN_CHANNEL = ContractSignEnum.SignChannel.SIGN_INIT)
    @Component
    public class ContractSignCompactInitImpl<T extends ContractRequest> implements Interceptor<T, ContractResponse> {
    public ContractSignCompactInitImpl() {
    }
    @Override
    public ContractResponse process(Chain<T,ContractResponse> chain) {
    log.info("=============執行合約文本初始化攔截器開始");
    //獲取處理的請求參數
    T request = chain.request();
    request.setStatus("1");
    log.info("=============執行合約文本初始化攔截器結束");
    //進入下一個責任鏈節點
    ContractResponse response = chain.proceed(request);
    if(Objects.isNull(response)){
    log.error("返回值的為空");
    response = ContractResponse.builder().status("fail").mas("處理失敗").build();
    }
    //其他處理
    return response;
    }
    }

    在該實作類上繫結了列舉 @ContractSign(SIGN_CHANNEL = ContractSignEnum.SignChannel.SIGN_INIT) . 在合約簽章入口類( ContractSignProcessor )中的變更如下:

    @Slf4j
    @Component
    public class ContractSignProcessor <T extends ContractRequest> extends SignConfig implements Processor<T, ContractResponse> {
    public ContractSignProcessor() {
    }
    @Override
    public ContractResponse process(T paramter) {
    //獲取所有的監聽器
    List<Interceptor<T,ContractResponse>> interceptorList = new ArrayList<>();
    //獲取排序後的結果,保證責任鏈的順序,hashmap中key如果是數位的話,透過hashcode編碼後是有序的
    for(Integer key : CONTRACT_SIGN_MAP.keySet()){
    interceptorList.add(CONTRACT_SIGN_MAP.get(key));
    }
    //開始簽章
    log.info("簽章開始");
    return new ContractCall(paramter,interceptorList).exectue();
    }
    }

    透過繼承合約簽章配置類(SignConfig),來獲取Map,遍歷Map添加到list後進入責任鏈流程。 到此,整個策略+責任鏈+組合的最佳化方式結束了。

    問題: 責任鏈中的順序是怎麽保證的? 相信認真看完的你能在文章或者程式碼中找到答案。

    👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書

    新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計43w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。

    PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦