前言

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

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

继续啰嗦下:

还是那句话,学习是自己的事情,但是跟着我学未尝不可。

不局限于参与讨论,但是我个人推崇参与讨论。

因为在叙述一个idea,一个design ,或者是 一个 question 的时候 ,

可能你在叙述的过程中,你就发现了自己的 漏洞;

可能在与各位兄弟(姐妹)在思想碰撞的时候,无形中就成长了的。

???

开搞。

正文

先看一个简图 :

再看一下大白话 :

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

里面流通的 是 水 (Context)

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

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

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

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

管与阀的设计

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

阀 做设计。

简单举例 ,

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

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

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

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

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

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

且慢,还没说完。

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

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

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

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

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

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

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

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

  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>

②创建一个 管道内容(父类),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();}
}

③ 第一个业务流程所需的 管道 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;//其他业务参数......@Overridepublic String getModelName() {return "赤藓糖醇MODEL";}
}

④ 创建 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);
}

⑤ 创建 第一节管子    砂砾浸出器  (管道处理器)    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());@Overridepublic 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;}
}

⑥ 创建 第二节管子    微生物消杀器(管道处理器)    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());@Overridepublic 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;}
}

⑦ 创建 第三节管子    温度加热器  (管道处理器)    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());@Overridepublic 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;}
}

⑧创建 第三节管子    配料反应器  (管道处理器)    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());@Overridepublic 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;}
}

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

现在是 有了 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 finalMap<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;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {appContext = applicationContext;}
}

简述作用 :

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*/@Resourceprivate 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;}}

最后就是 玩一下,简单示例写个调用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());@ResourcePipelineExecutor 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());}}

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

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

成功的效果:

演变/变动:

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

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

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

好的该篇就到这。

Springboot 使用管道设计模式 , 实践案例玩一玩相关推荐

  1. Kubernetes(K8s)容器设计模式实践案例 – 分散收集模式

    <Kubernetes与云原生应用>专栏是InfoQ向轻元科技首席架构师王昕约稿的系列 文章.本专栏包含8篇内容,将会从介绍和分析Kubernetes系统以及云原生应用 入手,逐步推出基于 ...

  2. 【视频课】8大Pytorch CV实践案例,超30小时视频助你攻略CV三大基础任务(分类分割检测)

    计算机视觉中大大小小可以包括至少30个以上的方向,在基于深度学习的计算机视觉研究方向中,图像分类,图像分割,目标检测无疑是最基础最底层的任务,掌握好之后可以很快的迁移到其他方向,比如目标识别,目标跟踪 ...

  3. 1个月连载30个设计模式真实案例,挑战年薪60W不是梦

    本文所有内容均节选自<设计模式就该这样学> 本文自2021年10月29日起持续连载,请大家持续关注.... 序言 Design Patterns: Elements of Reusable ...

  4. 【视频课】言有三每天答疑,38课深度学习+超60小时分类检测分割数据算法+超15个Pytorch框架使用与实践案例助你攻略CV...

    计算机视觉中大大小小可以包括至少30个以上的方向,在基于深度学习的计算机视觉研究方向中,图像分类,图像分割,目标检测无疑是最基础最底层的任务,掌握好之后可以很快的迁移到其他方向,比如目标识别,目标跟踪 ...

  5. 【视频课】零基础免费38课时深度学习+超60小时CV核心算法+15大Pytorch CV实践案例助你攻略CV...

    计算机视觉中大大小小可以包括至少30个以上的方向,在基于深度学习的计算机视觉研究方向中,图像分类,图像分割,目标检测无疑是最基础最底层的任务,掌握好之后可以很快的迁移到其他方向,比如目标识别,目标跟踪 ...

  6. javascript设计模式实践之模板方法--具有百叶窗切换图片效果的JQuery插件(二)...

    在上一篇<javascript设计模式实践之迭代器--具有百叶窗切换图片效果的JQuery插件(一)>里,通过采用迭代器模式完成了各初始化函数的定义和调用. 接下来就要完成各个切换效果的编 ...

  7. 神策数据荣获“2017金融科技·大数据优秀案例之最佳实践案例奖”

    当前,金融市场活跃度不断提升,业务模式不断创新,金融领域数据量呈爆炸式增长,蓬勃发展的大数据产业给金融业的发展带来了新机遇,也提出了新的挑战. 6 月 29 日, 「数据猿·超声波」之金融科技­商业价 ...

  8. 超干货 | 一线从业者实践案例大分享:3个tips实现用户增长

    12.21冬至日,网易云信.网易七鱼联合职人社举办线下闭门交流会,一起探讨To B企业如何做增长.活动筛选了来自不同行业的21位嘉宾,要求每人准备5分钟的分享,聊聊自己在过去一年的增长经验. 继最具启 ...

  9. 2021研发效能实践案例征集大赛

    简介:由阿里云云效主办的2021研发效能实践案例征集大赛正式启动,专门为企业CTO.研发(TL.一线研发人员).运维.产品人准备的实践案例展示平台,期待各位报名参与: 我们知道效能提升,就是要应用系统 ...

最新文章

  1. 【收藏清单】AI学习资料汇总——你想要的AI资源,这里都有
  2. Istio如何使用相同的端口访问网格外服务
  3. 比亚迪f3android系统,比亚迪F3发动机防盗系统设定
  4. 移动平台的meta标签-----神奇的功效
  5. python爬虫获取方法_小白学python爬虫:2.获得数据
  6. flume1.8 开发指南学习感悟
  7. bzoj1059: [ZJOI2007]矩阵游戏
  8. android 好玩的ui,让安卓更好玩 这些Launchers值得拥有
  9. 条件锁pthread_cond_t 的应用
  10. 11年写的一篇文章----智能终端安全现状及前景展望
  11. java有常函数_Java开发笔记(十一)常见的数学函数
  12. dotween unity 延时_使用DoTween在Unity中制作队列(Sequence)动画
  13. C# Windows 服务
  14. java 实现微信公众号开发服务器认证
  15. GRE Over IPSec技术
  16. 为你的Typecho文章页面添加微信公众号二维码-星泽V社
  17. Matlab在win10运行不出图片,win10系统网页图片加载不出来的六种原因及解决方法...
  18. vin码识别(车架号识别)SDK的应用
  19. 重启数据库服务器后数据库无法连接的解决方法
  20. iPhone 4的Romurs

热门文章

  1. 计算机剪切功能是哪个组合键,剪切快捷键是哪个 电脑剪切快捷键大全
  2. php替换正文中的汉字,php如何实现替换汉字
  3. Revit数据导入SuperMap iDesktop方法详解
  4. mysql 5.7 安装配置教程(windows 64位)
  5. 嵌入式学习DAY10 --- 封装子函数,GDB调试,gcc编译流程
  6. 牛客Wannafly挑战赛9
  7. 探索原味 BFF 模式
  8. iOS 16.2 的7个惊人变化
  9. 200套工作室设计行业响应式Html5模板HTML5+CSS3设计网站模板简洁设计师作品展示响应式模板整洁扁平宽屏CSS3网站模板html5网页静态模板Bootstrap扁平化网站源码css3手机se
  10. Creo 3D转2D 尺寸问题