【進階玩法】策略+責任鏈+組合實現合同簽章

2023-07-17 12:02:00

前置內容

  1. 掌握策略模式
  2. 掌握責任鏈模式
  3. 掌握類繼承、介面的實現
  4. 掌握引數的傳遞與設定
  5. GitHub地址

ps:【文章由來】公司專案中所用的合同簽章處理流程,本人基於責任鏈上使用策略模式進行優化。

簽章的處理流程

  1. 合同文字初始化
  2. 合同文字生成
  3. 簽章擋板是否開啟
  4. 合同簽章傳送mq
  5. 合同簽章流水更新
  6. 合同上傳檔案伺服器
  7. 簽章渠道選擇
  8. 簽章渠道的實際呼叫

執行的流程如下:

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

流程實現

GitHub地址

專案結構

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> aClass = interceptor.getClass();
        log.info("當前節點:{}",aClass.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個了,後續如果新增節點或者減少節點都需要對該類進行手動的處理;比如:減少一個節點的流程。

  1. 刪除節點實現的注入
  2. 刪除list中的bean實現類

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

  1. 新增簽章註解
  2. 新增簽章節點列舉
  3. 新增簽章設定類

簽章註解實現

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.getClass(), 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後進入責任鏈流程。
到此,整個策略+責任鏈+組合的優化方式結束了。


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