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

前置內容

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


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章