Springboot 使用管道设计模式 ,玩一玩

简介: 管道设计模式

前言

这段时间,学习群里大家讨论设计模式频率很高,可以看出来 日常搬砖 CRUD 已经让人从麻木到想脱离麻木,对代码有了些许追求。

1.jpeg

image.gif

当然也有还没放开的小伙(N重打码照顾兄弟),不敢参与讨论,但是私下还是非常愿意学的:


 

2.jpegimage.gif



开搞。

正文

先看一个简图 :

2.pngimage.gif

再看一下大白话 :

 

管道 (Pipeline ) , 一节一节 管子 (Handler)组成

里面流通的 是 水 (Context)  

而 每一节 管子, 直接连接的地方 ,叫 阀 (Boolean 阀值)(角阀、三角阀)


这个触发流程工艺, 我们交给一个 执行者负责 (Executor)

ps : 当然 你说你只有一节管子,当我没说,你现在就出去,不要再看这篇文章了。

补充简述 接下来实例内容:

 

管与阀的设计

从 第一节 管子Handler 流到 第二节 管子Handler ,能不能流过去, 我们可以在这俩 管子 之间的

阀 做设计。

简单举例 ,

例如 必须在第一节管子 里面, 把水的沙子清除掉, 我这个阀能检测沙子占比, 不通过是不会让水流到第二节管子的。

所以第一节管子 Handler, 起个名字 叫 过滤沙子管Handler 。

然后同样, 第二节 管子, 是个加热管,升温到 100摄氏度, 因为能够流到第三节管子的阀 要求温度到100摄氏度 ,起个名字 叫 升温管Handler 。

最后一节管子的业务,那就 加点 赤藓糖醇吧 ,就叫 加点糖管Handler 吧。

3.pngimage.gif

有了这些管子, 顺序一旦被 阀 组合固定后,  就成了 固定顺序、固定步骤的 ‘责任链’。

看到这里,这管道模式的设计,似乎不咋滴啊 ?

且慢,还没说完。

哪天,我们想调整某节管子的业务功能


例如  过滤沙子 改成 过滤沙子 + 微生物, 我们只需要对某一节 管子的 业务功能做处理即可    :


 

1b08a0a87e334c9d9723044d8f1afe80.pngimage.gif

又比如说, 经过讨论, 认为 一块把沙子和 微生物 过滤 ,不行,需要分开,多一节管子 处理微生物,  不慌, 直接加一节管子 就行 (具体后面实战会教大家怎么玩,轻轻松松加管子):

 

eb3d98d48c264d098f3e864e606f34d5.pngimage.gif

当然,这节 过滤微生物管子Handler  ,新增在哪,都是随随便便的 :

 

5fffe612f8d947bdb4c052f52f5af704.pngimage.gif

又假如有一天, 接到新的业务了 (产品又提需求了) ,

说之前的这个 加赤藓糖醇 水  就持续保留 。

然后我们需要整 一种水 , 这个玩意啊, 前面也是 需要过滤沙子 ,也需要升温 ,不过最后不加糖,改成加盐!

一条新的工艺管道 , 执行者 还是 Executor , 但是这个新的工艺管道 组建起来,非常简单 。

d8559fa4dadb43c2a440ddaa202796f8.pngimage.gif

    1. 职责单一分治
    2. 随意组合
    3. 拓展简易
    4. 新业务易克隆新生
    5. 角阀式固序,环环相扣

    开始敲代码 。

    ①pom文件引入依赖:

    <dependencies>
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>3.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>

    image.gif

    ②创建一个 管道内容(父类),PipelineContext.java:

    import lombok.Data;
    import java.time.LocalDateTime;
    /**
     * @Author: JCccc
     * @Date: 2022-09-06 11:24
     * @Description: 传递到管道的上下文
     */
    @Data
    public class PipelineContext {
        /**
         * 模型ID 管道内容
         */
        private Long modelId;
        /**
         * 故障信息
         */
        private String failureMsg;
        /**
         * 开始时间
         */
        private LocalDateTime beginTime;
        /**
         * 结束
         */
        private LocalDateTime endTime;
        /**
         * 获取模型名
         * @return
         */
        public String getModelName() {
            return this.getClass().getSimpleName();
        }
    }

    image.gif

    ③ 第一个业务流程所需的 管道 Context , 赤藓糖醇工艺Context ,EditorialContext.java:

     

    import com.jc.pipelinedemo.context.PipelineContext;
    import lombok.Data;
    import java.util.Map;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:26
     * @Description: 加赤藓糖醇的工艺 Context
     */
    @Data
    public class EditorialContext extends PipelineContext {
        /**
         * 溯源ID 一次业务流程一个ID
         */
        private String traceId;
        /**
         * 操作人 ID
         */
        private long operatorId;
        /**
         * 业务输入参
         */
        private Map<String, Object> inputParams;
        //其他业务参数......
        @Override
        public String getModelName() {
            return "赤藓糖醇MODEL";
        }
    }

    image.gif

    25d4d54d3f0c48ee85ad683934ad8a91.pngimage.gif

    ④ 创建 context处理器 ,也就是每一节管子的处理能力(interface)  ContextHandler.java :

    /**
     * @Author: JCccc
     * @Date: 2022-08-17 11:26
     * @Description: 管道中的上下文处理器
     */
    public interface ContextHandler<T extends PipelineContext> {
        /**
         * 处理输入的上下文数据
         *
         * @param context 处理时的上下文数据
         * @return 返回 (阀值) true 则表示由下一个 ContextHandler 继续处理 ; 返回 false 则表示处理结束.
         */
        boolean handle(T context);
    }

    image.gif

    ⑤ 创建 第一节管子    砂砾浸出器  (管道处理器)    ContextGritLeacher.java :

    import com.jc.pipelinedemo.context.mycontext.EditorialContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.StringUtils;
    import java.util.Map;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:28
     * @Description: 砂砾浸出器  (管道处理器)
     */
    @Component
    public class ContextGritLeacher implements ContextHandler<EditorialContext> {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public boolean handle(EditorialContext context) {
            Map<String, Object> formInput = context.getInputParams();
            if ((CollectionUtils.isEmpty(formInput))) {
                context.setFailureMsg("业务输入数据不能为空");
                return false;
            }
            //模拟 砂砾处理 业务逻辑
            String source = (String) formInput.get("source");
            if (StringUtils.isEmpty(source)) {
                context.setFailureMsg("材料必须存在来源地");
                return false;
            }
            //业务逻辑省略
            return true;
        }
    }

    image.gif

    ⑥ 创建 第二节管子    微生物消杀器(管道处理器)    ContextGermDisinfector.java :

    import com.jc.pipelinedemo.context.mycontext.EditorialContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.StringUtils;
    import java.util.Map;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:28
     * @Description: 微生物消杀器  (管道处理器)
     */
    @Component
    public class ContextGermDisinfector implements ContextHandler<EditorialContext> {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public boolean handle(EditorialContext context) {
            Map<String, Object> formInput = context.getInputParams();
            if ((CollectionUtils.isEmpty(formInput))) {
                context.setFailureMsg("业务输入数据不能为空");
                return false;
            }
            //模拟 微生物处理 业务逻辑
            String disinfectantCode = (String) formInput.get("disinfectantCode");
            if (StringUtils.isEmpty(disinfectantCode)) {
                context.setFailureMsg("材料必须包含消毒挤编码");
                return false;
            }
            //业务逻辑省略
            return true;
        }
    }

    image.gif

    ⑦ 创建 第三节管子    温度加热器  (管道处理器)    ContextTempHeater.java :

    import com.jc.pipelinedemo.context.mycontext.EditorialContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.StringUtils;
    import java.util.Map;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:28
     * @Description: 温度加热器  (管道处理器)
     */
    @Component
    public class ContextTempHeater implements ContextHandler<EditorialContext> {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public boolean handle(EditorialContext context) {
            Map<String, Object> formInput = context.getInputParams();
            if ((CollectionUtils.isEmpty(formInput))) {
                context.setFailureMsg("业务输入数据不能为空");
                return false;
            }
            //模拟 业务逻辑
            String tempRequire = (String) formInput.get("tempRequire");
            if (StringUtils.isEmpty(tempRequire)) {
                context.setFailureMsg("材料必须包含温度要求");
                return false;
            }
            //业务逻辑省略
            return true;
        }
    }

    image.gif

    ⑧创建 第三节管子    配料反应器  (管道处理器)    ContextMixReactor.java :

    import com.jc.pipelinedemo.context.mycontext.EditorialContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.StringUtils;
    import java.util.Map;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:28
     * @Description: 配料反应器  (管道处理器)
     */
    @Component
    public class ContextMixReactor implements ContextHandler<EditorialContext> {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Override
        public boolean handle(EditorialContext context) {
            Map<String, Object> formInput = context.getInputParams();
            if ((CollectionUtils.isEmpty(formInput))) {
                context.setFailureMsg("业务输入数据不能为空");
                return false;
            }
            //模拟 配料添加 业务逻辑
            String mixtureScale = (String) formInput.get("mixtureScale");
            if (StringUtils.isEmpty(mixtureScale)) {
                context.setFailureMsg("材料必须包含配料比例");
                return false;
            }
            //业务逻辑省略
            return true;
        }
    }

    image.gif

    handler 处理器,也就是对应管道的每一节管子 :

    57d7afa5a2fe40b0a5cc4396871e41a3.pngimage.gif

    现在是 有了 Context 和 Context 处理器, 还差 角阀 来把 这些管子 contextHandler 组合固序 。

    ⑨ 创建 PipelineRouteConfig.java :

    import com.jc.pipelinedemo.context.PipelineContext;
    import com.jc.pipelinedemo.context.mycontext.EditorialContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import com.jc.pipelinedemo.handler.myhandler.editorial.ContextGermDisinfector;
    import com.jc.pipelinedemo.handler.myhandler.editorial.ContextGritLeacher;
    import com.jc.pipelinedemo.handler.myhandler.editorial.ContextMixReactor;
    import com.jc.pipelinedemo.handler.myhandler.editorial.ContextTempHeater;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 11:28
     * @Description: 管道每一节管子的路由顺序配置
     */
    @Configuration
    public class PipelineRouteConfig implements ApplicationContextAware {
        private static final
        Map<Class<? extends PipelineContext>,
                List<Class<? extends ContextHandler<? extends PipelineContext>>>> PIPELINE_ROUTE_MAP = new HashMap<>(4);
        static {
            PIPELINE_ROUTE_MAP.put(EditorialContext.class,
                    Arrays.asList(
                            ContextGritLeacher.class,
                            ContextGermDisinfector.class,
                            ContextTempHeater.class,
                            ContextMixReactor.class
                    ));
        }
        /**
         * 在 Spring 启动时,根据路由表生成对应的管道映射关系,
         * PipelineExecutor 从这里获取处理器列表
         */
        @Bean("pipelineRouteMap")
        public Map<Class<? extends PipelineContext>, List<? extends ContextHandler<? extends PipelineContext>>> getHandlerPipelineMap() {
            return PIPELINE_ROUTE_MAP.entrySet()
                    .stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, this::toPipeline));
        }
        /**
         * 根据给定的管道中 ContextHandler 的类型的列表,构建管道
         */
        private List<? extends ContextHandler<? extends PipelineContext>> toPipeline(
                Map.Entry<Class<? extends PipelineContext>, List<Class<? extends ContextHandler<? extends PipelineContext>>>> entry) {
            return entry.getValue()
                    .stream()
                    .map(appContext::getBean)
                    .collect(Collectors.toList());
        }
        private ApplicationContext appContext;
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            appContext = applicationContext;
        }
    }

    image.gif

    简述作用 :

    spring启动, 把 Context作为key ,然后把相关的 管道每一节都按照我们想要的循序固定好丢到list里面,作为value , 放进  PIPELINE_ROUTE_MAP 里面去。


     

    为什么要这样做的,其实就是 相当于把责任链的 责任序 做成配置化。

    目前是通过static 代码块 实现, 其实可以改成从 数据库读取 简单设置一个sort, 然后这样可以更加动态地去变换顺序。

    ⑩ 最后就是我们的 管道负责者,执行器 , PipelineExecutor.java :

     

    import com.jc.pipelinedemo.context.PipelineContext;
    import com.jc.pipelinedemo.handler.ContextHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import javax.annotation.Resource;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 13:39
     * @Description: 管道执行器
     */
    @Component
    public class PipelineExecutor {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        /**
         * PipelineRouteConfig 中的 pipelineRouteMap
         */
        @Resource
        private Map<Class<? extends PipelineContext>,
                    List<? extends ContextHandler<? super PipelineContext>>> pipelineRouteMap;
        /**
         * 同步处理输入的上下文数据<br/>
         * 如果处理时上下文数据流通到最后一个处理器且最后一个处理器返回 true,则返回 true,否则返回 false
         *
         * @param context 输入的上下文数据
         * @return 处理过程中管道是否畅通,畅通返回 true,不畅通返回 false
         */
        public boolean acceptSync(PipelineContext context) {
            Objects.requireNonNull(context, "上下文数据不能为 null");
            // 拿到context数据类型
            Class<? extends PipelineContext> dataType = context.getClass();
            // 获取数据处理管道list (每一节)
            List<? extends ContextHandler<? super PipelineContext>> pipeline = pipelineRouteMap.get(dataType);
            if (CollectionUtils.isEmpty(pipeline)) {
                logger.error("{} 的管道为空", dataType.getSimpleName());
                return false;
            }
            // 管道是否畅通
            boolean lastSuccess = true;
            for (ContextHandler<? super PipelineContext> handler : pipeline) {
                try {
                    lastSuccess = handler.handle(context);
                } catch (Throwable ex) {
                    lastSuccess = false;
                    logger.error("[{}] 处理异常,handler={}", context.getModelName(), handler.getClass().getSimpleName(), ex);
                }
                // 终止处理
                if (!lastSuccess) { break; }
            }
            return lastSuccess;
        }
    }

    image.gif

    最后就是 玩一下,简单示例写个调用service:

     

    import javax.annotation.Resource;
    /**
     * @Author: JCccc
     * @Date: 2022-10-13 13:43
     * @Description:
     */
    @Service
    public class DealService{
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Resource
        PipelineExecutor pipelineExecutor;
        public ResultResponse<Long> dealSync(InstanceBuildRequest request) {
            PipelineContext data = convertPipelineContext(request);
            if (Objects.isNull(data)) {
                return ResultResponse.returnFail("数据异常");
            }
            boolean success = pipelineExecutor.acceptSync(data);
            if (success) {
                return ResultResponse.returnSuccess(data.getModelId());
            }
            logger.error("管道处理失败:{}", data.getFailureMsg());
            return ResultResponse.returnFail(data.getFailureMsg());
        }
    }

    image.gif

    其实核心就是通过executor调用一下 :

    ac017312e68e48c7bd4195029b7a06bf.pngimage.gif

    看看执行效果 ,如果某个管道条件不符合,处理不了,直接终止:


    eab87ca36636424382c44a8ecf729ff6.pngimage.gif

    79dc51f564604275a2ccd1a0506d694c.pngimage.gif

    成功的效果:


    37a0a1113bdd4bc09cf9071e5cfef535.pngimage.gif

    演变/变动:


    哪天需要新增某节管子 ,例如  参数预处理器 ContextPreParamProcessor

    f3b44df46ab14951823f7e693eca87b2.pngimage.gif

    然后在配置路由类里面,安排上对应的管子即可:

    3b51f1955d4544f9b4066a43a69890d7.pngimage.gif

    假如完全来了一个新的业务流程 , 那么直接在这里 简简单单配置起来相关的管道链即可 (当然如果有些管子的共用的,也是可以自由组合起来):

    2dc36a5b355b4bda8c139ad0bcc5569d.pngimage.gif


    好的该篇就到这,开心学,做行动的巨人!

    相关文章
    |
    3月前
    |
    设计模式 Java 关系型数据库
    【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
    本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
    464 37
    |
    设计模式 算法 Java
    Springboot 使用设计模式- 策略模式
    Springboot 使用设计模式- 策略模式
    993 0
    Springboot 使用设计模式- 策略模式
    |
    4月前
    |
    设计模式 缓存 Java
    深入Spring Boot启动过程:揭秘设计模式与代码优化秘籍
    深入Spring Boot启动过程:揭秘设计模式与代码优化秘籍
    |
    数据采集 设计模式 自然语言处理
    设计模式最佳套路2 —— 愉快地使用管道模式
    管道模式(Pipeline Pattern) 是责任链模式(Chain of Responsibility Pattern)的常用变体之一。在管道模式中,管道扮演着流水线的角色,将数据传递到一个加工处理序列中,数据在每个步骤中被加工处理后,传递到下一个步骤进行加工处理,直到全部步骤处理完毕。 PS:纯的责任链模式在链上只会有一个处理器用于处理数据,而管道模式上多个处理器都会处理数据。
    12197 0
    设计模式最佳套路2 —— 愉快地使用管道模式
    |
    设计模式 XML 缓存
    Spring Boot源码中设计模式应用浅析
    背景:大家好,我是冰点。最近有网友反馈,他在面试过程中被面试官问到,设计模式,他按自己背设计模式的八股文回答了,之后面试官又追问了一句,那你知道 你们项目所用的spring boot都使用了哪些设计模式呢,这些设计模式是怎么应用的呢?。我这位网友,说自己直接懵逼,瞬间感觉之前背的设计模式八股文,一文不值哈哈。那今天我们分析一下Spring Boot 源码中的设计模式应用。工欲善其事必先利其器。加油。
    195 0
    |
    消息中间件 数据库 云计算
    云计算设计模式(十五)——管道和过滤器模式
    云计算设计模式(十五)——管道和过滤器模式 分解,执行复杂处理成一系列可重复使用分立元件的一个任务。这种模式可以允许执行的处理进行部署和独立缩放任务元素提高性能,可扩展性和可重用性。
    1140 0
    |
    1月前
    |
    设计模式 安全 Java
    Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
    Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
    |
    3月前
    |
    设计模式 数据库连接 PHP
    PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
    本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
    |
    1月前
    |
    设计模式 开发者 Python
    Python编程中的设计模式:工厂方法模式###
    本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###