安利一个轻量级流程引擎compileflow

写在文章开头

今日推荐一个比较轻量级的工作流引擎——即阿里的compileflow,这款流程引擎算是笔者接触过流程引擎中相对轻量级、且性能和集成扩展表现都比较良好的框架,本文就会从几种常见的使用场景以及源码分析的角度介绍这款工具。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

为什么需要 compileflow

经评审后得出明确复杂系统的实现思路,由此绘制出流程图,但真正落地时因为每个开发不同的习惯总会导致实现会有所偏差,使得系统流程串联时会出现各种各样的问题,通过流程引擎界定业务边界后并约定开发规范,以及业务逻辑的可视化,大大降低了业务设计和开发的成本。

相比主流的几种流程引擎,compileflow有着如下几个优势:

  1. 高性能:流程文件会直接转换成Java代码并编译执行,简洁且高效。
  2. 集成方便:引入compileflow无需繁琐的配置,只需引入类库后在开发的维度完成配置即可。
  3. 完善的插件:compileflowIDEA提供的插件,在进行流程设计时可基于快速完成流程图的设计,并基于流程图的节点边界引入我们的业务代码。

compileflow最佳实践案例

前置准备

在正式使用compileflow之前,我们需要在IDEA中安装一个compileflow的插件,方便我们后续的绘图工作,对应的地址如下,按需下载对应版本安装重启即可:

compileflow-designer-upgrade

以笔者为例,个人开发工具是IDEA 2018版本,所以下载的版本就是compileflow-idea-designer-1.0.14.for.2018.up.zip

在这里插入图片描述

开平方计算

接下来笔者回基于3个例子介绍一下compileflow几种比较常见的使用,先来一个比较基础的平方根计算,流程比较简单,传入一个参数后,给出对应的开平方结果。

基于插件,我们在resources目录下创建一个名为sqrt.bpm的文件,通过开始、结束、自动设置3个标签完成了一张简单的开平方流程图绘制:

在这里插入图片描述

按照我们的设计,我们要求在求平方根这个自动节点传入一个参数num,让这个num走到我们的平方计算逻辑,基于这个约定,我们完成平方计算器的代码:

@Slf4j
public class SqrtCalculator {public double sqrt(double num) {double result = Math.sqrt(num);return result;}
}

有个这个Java类之后,我们就可以将这个类和图片中的自动节点绑定,如下图所示,我们双击节点后配置类的包路径以及出入参的映射即可:

在这里插入图片描述

自此我们的流程图就绘制并开发完成了,在Spring中,我们只需通过afterPropertiesSet这个扩展点,将该图编译转换成字节码,后续使用时就会通过反射创建并缓存起来等待用户的调用和运行:

@Component
@Configuration
public class BpmInitializer implements InitializingBean, ApplicationContextAware {@Overridepublic void afterPropertiesSet() throws Exception {ProcessEngine processEngine = ProcessEngineFactory.getProcessEngine();//编译开平方根流程图生成字节码processEngine.preCompile("sqrt");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringApplicationContextProvider.applicationContext = applicationContext;}
}

为了方便测试我们写了一段HTTP的调用:

@RequestMapping("/sqrt")public double sqrt(@RequestParam Double num) {//code在bpm文件中定义String code = "sqrt";//执行流程的入参Map<String, Object> context = new HashMap<>();context.put("num", num);try {//从ProcessEngineFactory获取流程引擎ProcessEngine processEngine = ProcessEngineFactory.getProcessEngine();//拿到我们的开平方的代码并传入参数获取结果Map<String, Object> result = processEngine.execute(code, context);log.info("sqrt result:{}", result.get("result"));} catch (Exception e) {log.error("执行报错,报错原因:{}", e.getMessage(), e);}return 0;}

我们传入参数4对应的返回结果如下:

 INFO 14676 --- [io-18080-exec-4] c.s.controller.CalculatorController      : sqrt result:2.0

两数相加

我们再拓展一个例子,要求传入两个参数num1num2并获取这两个参数的结果result,同理我们快速绘制出这样一张流程图:

在这里插入图片描述

基于该图的约定,我们给出两数相加的代码:

@Slf4j
public class SumCalculator {public int sum(int num1, int num2) {int result = num1 + num2;log.info("{}+{}={}", num1, num2, result);return result;}
}

最后我们将代码和图片中的节点绑定:

在这里插入图片描述

同理在配置中完成该流程图的预编译:

 processEngine.preCompile("sum");

后续的调用也就和上文一样,这里我们也给出对应的代码:

@RequestMapping("/sum")public int sqrt(@RequestParam Integer num1,Integer num2) {//code在bpm文件中定义String code = "sum";//执行流程的入参Map<String, Object> context = new HashMap<>();context.put("num1", num1);context.put("num2", num2);try {ProcessEngine processEngine = ProcessEngineFactory.getProcessEngine();Map<String, Object> result = processEngine.execute(code, context);log.info("sum result:{}", result.get("result"));} catch (Exception e) {log.error("执行报错,报错原因:{}", e.getMessage(), e);}return 0;}

团队点餐

最后我们再来一个复杂的流程图,我们会给定人数进行点餐,最后根据人数则算点餐金额,如果餐费大于100则减去10元,反之按照原价完成付款结束整个流程。
对此我们给出对应的流程图,可以看到我们在循环节点中添加了吃饭的自动节点,该节点会遍历入参并调用吃饭这个自动节点。完成吃饭的流程后通过判断节点计算金额,如果大于100走左边的脚本节点,反之走右边,通过脚本节点得到计算结果后,执行付款节点即可:

在这里插入图片描述

由此可知,该流程图需要我们从java代码的角度提供3个方法:

  1. 循环遍历时要调用的eat方法。
  2. 判断节点计算价格的方法。
  3. 付款节点的付款方法。

因为compileflow支持在Spring中直接注入使用,所以基于上述的行为要求,我们创建一个EatBean

@Component("eatBean")
@Slf4j
public class EatBean {public void eat(Object name) {log.info("{} 吃鸡腿饭", String.valueOf(name));}public int total(int pSize) {int total = pSize * 15;log.info("吃鸡腿饭人数:{},总价:{}",pSize,total);return total;}public void pay(int realPrice) {log.info("最终付款:{}", realPrice);}
}

接下来我们再来看看每个节点的配置,先来看看循环节点,它定义了集合变量的变量名和类型等信息,后续我们调用该流程引擎时,传入的集合就需要是Object类型的List变量pList

在这里插入图片描述

循环遍历的每一个节点都会调用吃饭节点,所以我们的吃饭节点就和eat方法绑定:

在这里插入图片描述

同理判断分支绑定pListsize,将返回值totalreturn的变量绑定:

在这里插入图片描述

判断分支基于计算方法得到结果后,就会走到对应的脚本节点,我们以左边减去100的为例,可以看到它会基于判断节点返回的total通过ql表达式将其减去10作为最终结果realPrice

在这里插入图片描述

最后付款节点基于脚本节点得到的realPrice绑定pay方法付款:

在这里插入图片描述

最终我们给出测试代码:

@RequestMapping("/eat")public int eat() {//code在bpm文件中定义String code = "eat";//执行流程的入参Map<String, Object> context = new HashMap<>();List pList=new ArrayList();pList.add("user1");pList.add("user2");pList.add("user3");pList.add("user4");context.put("pList", pList);try {//从流程引擎中获取eat的代码并执行ProcessEngine processEngine = ProcessEngineFactory.getProcessEngine();Map<String, Object> result = processEngine.execute(code, context);//输出最终付款结果log.info("eat result:{}", result.get("realPrice"));} catch (Exception e) {log.error("执行报错,报错原因:{}", e.getMessage(), e);}return 0;}

4个人一人15,所以餐费不扣减最终付款60:

024-05-14 08:57:35.982  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : user1 吃鸡腿饭
2024-05-14 08:57:35.982  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : user2 吃鸡腿饭
2024-05-14 08:57:35.982  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : user3 吃鸡腿饭
2024-05-14 08:57:35.982  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : user4 吃鸡腿饭
2024-05-14 08:57:35.982  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : 吃鸡腿饭人数:4,总价:60
2024-05-14 08:57:35.998  INFO 15416 --- [io-18080-exec-2] com.sharkChili.type.EatBean              : 最终付款:60
2024-05-14 08:57:35.998  INFO 15416 --- [io-18080-exec-2] com.sharkChili.controller.EatController  : eat result:60

基于源码详解compileflow工作流程

我们直接以两数相加以为例,查看processEngine的预编译preCompile方法,其内部会基于我们的流程图将其转换为class文件并将这些内容缓存起来。

 ProcessEngine processEngine = ProcessEngineFactory.getProcessEngine();processEngine.preCompile("sum");

可以看到其内部最终会走到AbstractProcessEnginepreCompile方法,它会判断以当前code作为key查看缓存runtimeCache中是否有这个流程图的编译后的字节码,如果有则直接返回,反之通过当前流程图各种变量等信息生成Java代码并将其编译编译为字节码文件并将其缓存到compiledClassCache中,最后再通过runtimecompile方法用类加载器将我们编译后的字节码文件的类加载到内存中等待使用:

   @Overridepublic void preCompile(ClassLoader classLoader, String... codes) {if (ArrayUtils.isEmpty(codes)) {throw new CompileFlowException("No process to compile");}for (String code : codes) {//获取运行时流程引擎,如果runtimeCache中存在则直接得到runtime ,反之基于code的信息得到流程图将其转换为Java代码并编译成字节码缓存到compiledClassCache中,再将其存入runtimeCache中并返回AbstractProcessRuntime runtime = getProcessRuntime(code);//传入类加载器,基于上文编译后的字节码文件将类加载到内存中runtime.compile(classLoader);}}

执行时我们会通过processEngine.execute(code, context);方法进行调用,其内部逻辑就是通过我们上文的runtimeCache找到运行时运行时信息,基于该信息的code也就我们两数相加的名字sum调用runtime.start方法,其内部会从编译缓存中找到我们的字节码文件,完成反射创建并调用,最后将结果返回:

public Map<String, Object> execute(String code, Map<String, Object> context) {//基于code获取sum的运行时信息TbbpmProcessRuntime runtime = (TbbpmProcessRuntime)this.getProcessRuntime(code);//通过上一步得到的runtime 调用start方法,其内部会拿着runtime的code从编译缓存compiledClassCache中拿到字节码文件完成SumFlow的反射创建并调用return runtime.start(context);}

我们查看runtimestart方法就可以看到从编译缓存中拿到对应的字节码完成反射创建和调用的操作:

public Map<String, Object> start(Map<String, Object> context) {//......//从compiledClassCache中拿到code对应的字节码完成反射创建集合参数context完成调用return this.executeProcessInstance(context);}private Map<String, Object> executeProcessInstance(Map<String, Object> context) {try {//从compiledClassCache拿到字节码完成反射创建的示例ProcessInstance instance = getProcessInstance();//基于这个示例完成调用并返回结果return instance.execute(context);} catch (CompileFlowException e) {throw e;} catch (Exception e) {throw new CompileFlowException("Failed to execute process, code is " + code, e);}}

小结

本文通过3个案例并结合源码分析的形式剖析了轻量级流程引擎compileflow的工作机制,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

协议详解:https://github.com/alibaba/compileflow/wiki/协议详解#2-全局变量

compileflow官方文档:https://github.com/alibaba/compileflow

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/698269.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

邓闲小——生存、生活、生命|真北写作

人生有三个层次∶生存、生活、生命。 生存就是做必须做的事。生存的模式是邓&#xff0c;是交易&#xff0c;是买卖。别人需要的东西&#xff0c;你生产出来&#xff0c;卖给他。哪怕这个东西没啥用&#xff0c;也可以卖&#xff0c;情绪也可以卖。你需要的东西&#xff0c;你花…

社交媒体数据恢复:派派

派派是一款非常流行的社交软件&#xff0c;但是如果你在使用派派的过程中&#xff0c;不小心删除了一些重要的聊天记录或者其他数据&#xff0c;那么该怎么办呢&#xff1f;下面是一些简单的步骤&#xff0c;可以帮助你进行数据恢复。 1. 首先打开派派&#xff0c;并进入需要恢…

【论文复刻】堆叠柱状图+饼图

复刻了一下这篇论文里的fig2c&#xff1a;Impacts of COVID-19 and fiscal stimuli on global emissions and the Paris Agreement | Nature Climate Change 效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;随机赋值 2. 图像绘制&#xff1a;绘制堆叠柱状…

无列名注入

在进行sql注入时&#xff0c;一般都是使用 information_schema 库来获取表名与列名&#xff0c;因此有一种场景是传入参数时会将 information_schema 过滤 在这种情况下&#xff0c;由于 information_schema 无法使用&#xff0c;我们无法获取表名与列名。 表名获取方式 Inn…

RabbitMQ--死信队列

目录 一、死信队列介绍 1.死信 2.死信的来源 2.1 TTL 2.2 死信的来源 3.死信队列 4.死信队列的用途 二、死信队列的实现 1.导入依赖 pom.xml 2.application.properties 3.配置类 4.生产者 5.业务消费者&#xff08;正常消费者&#xff09; 6.死信队列消费者 一、…

Linux服务升级:OpenResty 升级1.25.3.1版本

目录 一、实验 1.环境 2.Windows 安装 Termius 3.Linux 部署 OpenResty 4.Linux 使用 OpenResty 实现内容展示&#xff08;content_by_lua&#xff09; 5.Linux 使用 OpenResty 实现重定向 &#xff08;rewrite_by_lua&#xff09; 6.Linux 使用 OpenResty 实现请求体&…

设置 kafka offset 消费者位移

文章目录 1.重设kafka消费者位移2.示例2.1 通过 offset 位置2.2 通过时间2.3 设置到最早 1.重设kafka消费者位移 维度策略含义位移Earliest把位移调整到当前最早位移处位移Latest把位移调整到当前最新位移处位移Current把位移调整到当前最新提交位移处位移Specified-Offset把位…

AI地名故事:长洲村

长洲村&#xff0c;位于广州市黄埔区长洲街道&#xff0c;坐落在广州东部的长洲岛、洪圣沙、白兔沙、大吉沙。这个地方自古以来就是扼守广州海上大门的咽喉要塞&#xff0c;地理位置十分重要。 关于长洲村的由来&#xff0c;要追溯到南宋时期。南宋景定三年&#xff08;1262年…

【小积累】@Qualifier注解

今天在看rabbitMQ的时候需要绑定交换机和队列&#xff0c;交换机和队列都已经注入到spring容器中&#xff0c;写了一个配置类&#xff0c;使用了bean注解注入的。所以这时候绑定的时候需要使用容器中的交换机和队列&#xff0c;必须要使用Qualifier去确定是容器中的哪个bean对象…

十一、 进行个人信息保护认证的流程是怎样的?

2022 年 11 月 18 日&#xff0c;国家市场监督管理总局和国家网信办发布的《认证公告》以及附件《认证规则》&#xff0c;对开展个人信息保护认证的流程进行了细节说明&#xff0c;包括认证委托、技术验证、现场审核、认证结果评价和批准等环节。《认证公告》指出“从事个人信息…

Mamba:4 魔幻矩阵A

若在阅读过程中有些知识点存在盲区&#xff0c;可以回到如何优雅的谈论大模型重新阅读。另外斯坦福2024人工智能报告解读为通识性读物。若对于如果构建生成级别的AI架构则可以关注AI架构设计。技术宅麻烦死磕LLM背后的基础模型。 ​Mamba自从出道就一直被拿来和Transformer对比…

基于.NET的跨平台应用程序框架介绍

基于.NET的跨平台应用程序框架介绍 随着.NET 8的发布&#xff0c;我也将我的书.NET MAUI Cross-Platform Application Development更新到了第二版&#xff0c;已经在3月25日开始正式发行。第一版是基于.NET 6编写的&#xff0c;第二版更新到了 .NET 8。这是一本介绍.NET MAUI的…