ps:【文章由來】公司專案中所用的合同簽章處理流程,本人基於責任鏈上使用策略模式進行優化。
執行的流程如下:
整個結構類似於遞迴呼叫。每個節點中依賴上一個節點的輸入以及下一個節點的輸出,在中間過程可以實現每個節點的自定義操作,比較靈活。
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個了,後續如果新增節點或者減少節點都需要對該類進行手動的處理;比如:減少一個節點的流程。
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
,
設定註解的執行週期(有效範圍),一般是執行時有效,
設定該註解的資料型別,
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
後進入責任鏈流程。
到此,整個策略+責任鏈+組合的優化方式結束了。
問題:
責任鏈中的順序是怎麼保證的?
相信認真看完的你能在文章或者程式碼中找到答案。