Building a Flexible Contract Signing Workflow with Chain of Responsibility
This article explains how to design a contract‑signing workflow in Java using the Chain of Responsibility and Strategy patterns, covering the processing steps, project structure, annotation‑driven bean injection, and complete code examples for each component.
Signature Processing Flow
Contract text initialization
Contract text generation
Check whether the signature shield is open
Send contract signature to MQ
Update contract signature flow
Upload contract file to server
Select signature channel
Actual call of the signature channel
Execution flow is as follows:
The whole structure is similar to a recursive call. Each node depends on the previous node’s input and the next node’s output, allowing custom operations in the middle, which makes it very flexible.
Process Implementation
GitHub repository: https://github.com/xbhog/DesignPatternsStudy
Project Structure
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.javaProject Class Diagram
Chain of Responsibility + Composite Pattern Code Implementation
Project Structure
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.javaObject definitions in the chain
<span class="hljs-keyword">@Data</span>
<span class="hljs-keyword">@NoArgsConstructor</span>
<span class="hljs-keyword">@AllArgsConstructor</span>
<span class="hljs-keyword">@Builder</span>
public class ContractRequest {
private String name;
private String age;
private String status;
}
<span class="hljs-keyword">@Data</span>
<span class="hljs-keyword">@NoArgsConstructor</span>
<span class="hljs-keyword">@AllArgsConstructor</span>
<span class="hljs-keyword">@Builder</span>
public class ContractResponse {
private String status;
private String mas;
}These request and response classes make it easy to handle each node’s input and output within the chain.
Chain processing flow
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-meta">@Component</span>
public class ContractSignProcessor<T extends ContractRequest> implements Processor<T, ContractResponse> {
@Resource(name = "contractCompactInitImpl")
private Interceptor<T, ContractResponse> contractCompactInitImpl;
// other interceptor fields omitted for brevity
@Override
public ContractResponse process(T paramter) {
List<Interceptor<T, ContractResponse>> interceptorList = new ArrayList<>();
interceptorList.add(contractCompactInitImpl);
// add other interceptors …
log.info("签章开始");
return new ContractCall(paramter, interceptorList).exectue();
}
}The main entry injects all node implementations (e.g., contractCompactInitImpl) and assembles them into a chain. Before initializing nodes, the request is wrapped and processed.
public class ContractCall<T extends ContractRequest> implements Call<T, ContractResponse> {
private final T originalRequest;
private final List<Interceptor<T, ContractResponse>> interceptorList;
// constructor and methods omitted for brevity
@Override
public ContractResponse exectue() {
ContractChain<T> chain = new ContractChain<>(0, originalRequest, interceptorList);
return chain.proceed(originalRequest);
}
} 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;
// constructor omitted
@Override
public ContractResponse proceed(T request) {
if (index >= interceptors.size()) {
throw new IllegalArgumentException("index越界");
}
Chain<T, ContractResponse> nextChain = new ContractChain<>(index + 1, request, interceptors);
Interceptor<T, ContractResponse> interceptor = interceptors.get(index);
log.info("当前节点:{}", interceptor.getClass().getSimpleName());
ContractResponse response = interceptor.process(nextChain);
if (Objects.isNull(response)) {
throw new NullPointerException("intercetor" + interceptor + "return null");
}
return response;
}
}Test Verification
@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);
}
}Running the test triggers the full signing chain, producing log entries such as "签章开始", "当前节点:ContractSignCompactInitImpl", and finally prints ContractResponse(status=success, mas=处理成功).
Strategy + Chain + Composite Code Implementation
To avoid manual changes when adding or removing nodes, the author introduces a strategy‑based bean injection. The SignConfig class scans all beans annotated with @ContractSign and stores them in a map keyed by the enum code.
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 sign = AnnotationUtils.findAnnotation(interceptor.getClass(), ContractSign.class);
if (sign != null) {
CONTRACT_SIGN_MAP.put(sign.SIGN_CHANNEL().getCode(), interceptor);
}
});
}
}The annotation, enum, and config are defined as follows.
Signature Annotation Implementation
package com.example.springboottest.chainresponsibility.annotations;
import com.example.springboottest.chainresponsibility.Enum.ContractSignEnum;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContractSign {
ContractSignEnum.SignChannel SIGN_CHANNEL();
}The annotation targets TYPE and retains at runtime, allowing the enum to specify the channel.
Enum Implementation
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; }
// getters omitted
}
}Signature Config Class
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 sign = AnnotationUtils.findAnnotation(interceptor.getClass(), ContractSign.class);
if (sign != null) {
CONTRACT_SIGN_MAP.put(sign.SIGN_CHANNEL().getCode(), interceptor);
}
});
}
}During application startup, AnnotationUtils scans all beans annotated with @ContractSign and populates the map for later ordering.
Signature Annotation Usage
@Slf4j
@ContractSign(SIGN_CHANNEL = ContractSignEnum.SignChannel.SIGN_INIT)
@Component
public class ContractSignCompactInitImpl<T extends ContractRequest> implements Interceptor<T, ContractResponse> {
@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;
}
}The annotation binds the enum value SIGN_INIT to this implementation.
Modified ContractSignProcessor
public class ContractSignProcessor<T extends ContractRequest> extends SignConfig implements Processor<T, ContractResponse> {
@Override
public ContractResponse process(T paramter) {
List<Interceptor<T, ContractResponse>> interceptorList = new ArrayList<>();
for (Integer key : CONTRACT_SIGN_MAP.keySet()) {
interceptorList.add(CONTRACT_SIGN_MAP.get(key));
}
log.info("签章开始");
return new ContractCall(paramter, interceptorList).exectue();
}
}By inheriting SignConfig, the processor obtains the ordered map, builds the interceptor list, and executes the chain without hard‑coded bean references.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
