当前位置: 欣欣网 > 码农

进阶玩法:策略+责任链+组合实现合同签章

2024-06-09码农

来源:juejin.cn/post/7256307512244650040

👉 欢迎 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 每月赠书

新项目: 仿小红书 (微服务架构)正在更新中... , 全栈前后端分离博客项目 2.0 版本完结啦, 演示链接 http://116.62.199.48/ 。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。 目前已更新了261小节,累计43w+字,讲解图:1806张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,

  • 1 前置内容

  • 2 签章的处理流程

  • 3 流程实现

  • 项目结构

  • 项目类图

  • 责任链+组合模式代码实现

  • 策略+责任链+组合代码实现

  • 1 前置内容

  • 掌握策略模式

  • 掌握责任链模式

  • 掌握类继承、接口的实现

  • 掌握参数的传递与设置

  • GitHub地址

  • ps:【文章由来】 公司项目中所用的合同签章处理流程,本人基于责任链上使用策略模式进行优化。

    2 签章的处理流程

  • 合同文本初始化

  • 合同文本生成

  • 签章挡板是否开启

  • 合同签章发送mq

  • 合同签章流水更新

  • 合同上传文件服务器

  • 签章渠道选择

  • 签章渠道的实际调用

  • 执行的流程如下:

    图片

    整个结构类似于递归调用。每个节点中依赖上一个节点的输入以及下一个节点的输出,在中间过程可以实现每个节点的自定义操作,比较灵活。

    3 流程实现

    GitHub地址

    https://github.com/xbhog/DesignPatternsStudy

    项目结构

    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> a class = interceptor.get class();
    log.info("当前节点:{}",a class.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,

  • 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.get class(), 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后进入责任链流程。 到此,整个策略+责任链+组合的优化方式结束了。

    问题: 责任链中的顺序是怎么保证的? 相信认真看完的你能在文章或者代码中找到答案。

    👉 欢迎 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 每月赠书

    新项目: 仿小红书 (微服务架构)正在更新中... , 全栈前后端分离博客项目 2.0 版本完结啦, 演示链接 http://116.62.199.48/ 。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。 目前已更新了261小节,累计43w+字,讲解图:1806张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

    获取方式:点「在看」,关注公众号并回复 Java 领取,更多内容陆续奉上。

    PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

    「在看」支持小哈呀,谢谢啦