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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Building a Flexible Contract Signing Workflow with Chain of Responsibility

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:

Process diagram
Process diagram

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.java

Project Class Diagram

Class diagram
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.java

Object 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Chain of ResponsibilityDesign Patternsstrategy patternAnnotationsDependency Injection
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.