前言

最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的。

一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说。

于是一直有一个想法,让程序直接读流程图,根据流程图的配置来决定程序运行的顺序。

一转眼三年过去了,目前这个想法已经逐步落地实现变成代码。

问题

对于简单的流程

a -> b -> c

可以很容易用代码来实现

// 执行a
a();
// 执行b
b();
// 执行c
c();

对于并行的流程

a -> b
a -> c

这个就要多线程框架来实现

// 执行a
a(); // a结束后执行b
new Thread(b).start();
// a结束后执行c
new Thread(c).start();

对于分支合并的流程

a -> b
a -> c
b -> d
c -> d
程序会变得更加复杂
// 执行a
a(); // a结束后执行b
new Thread(b).start();
// a结束后执行c
new Thread(c).start(); // 等待b,c结束
waitComplete(b,c); // 执行d
d();
这个是最常用的业务流程,在实际写程序的时候,一般会避开多线程框架,往往被简单写成:
a();
b();
c();
d();
去除了Fork-Join的麻烦,也没有改变业务执行顺序,但和流程图稍有出入。
 
于是想到能不能有个框架来控制a,b,c,d的运行顺序呢?
也就我们只需要编写a,b,c,d的单体,执行顺序变成可配置。
 

调查

于是想到各种工作流框架和job执行框架可以满足这个需求,但是太重了。
为了简单的需求,引入庞大的工作流或者job执行引擎,无疑是每个项目都不能接受的。
 
于是,决定手写一个轻量的,即可以控制程序执行流程,又可以通过图形界面编辑程序流程的框架。
 

实现

首先要有一个绘制流程图的界面。并且能够将流程图转化为json格式。
这里我选择了Vis.js的network。
可以编辑简单流程,如下

还可以实现流程图和json之间的互转。

我们把这些节点的基本信息拿到,就可以得到一张图,然后通过程序遍历这张图的每个节点,即可达到运行流程图的效果。

接下来就是流程图的节点与Java的方法绑定了。
我做了一个Annotation来绑定流程图节点,

public @interface Node {

    String id()     default "" ;
String label()    default "" ;
}

节点得到运行开始事件后,拿到要运行的节点ID和名称,查找对应的类的Annotation对应的方法,如找到则运行该方法。

public int execute(String flowId, String nodeId, String historyId, HistoryNodeEntity nodeEntity)      throws Exception{

  String nodeName = nodeEntity.getNodeName();
System.out.println(    "execute:" + nodeId);
System.out.println(    "node name:" + nodeEntity.getNodeName()); Method methods[] =     this .getClass().getMethods();
if (methods !=     null ) {
for (Method method:methods) {
Node node = method.getAnnotation(Node.    class );
if (node !=     null ) {
if (node.id().equals(nodeId) || node.label().equals(nodeName)) {
try {
method.invoke(    this );
return 0 ;
}     catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
throw e;
}
}
}
}
}
return 0 ;
}

使用方法

我们需要做一个继承自FlowRunner的类,里面的方法和flow的节点绑定,和一个flow的配置文件,放在相同的目录下。

MyFlow1.java

public class MyFlow1     extends FlowRunner {

    @Node (label=    "a" )
public void process_a() {
System.out.println(   "processing a" );
} @Node (label=   "b" )
public void process_b() {
System.out.println(   "processing b" );
} @Node (label=   "c" )
public void process_c() {
System.out.println(   "processing c" );
} @Node (label=   "d" )
public void process_c() {
System.out.println(   "processing d" );
}
}

MyFlow1.json

{
"flowId" :     "123" ,
"nodes" : [
{
"id" :     "1" ,
"label" :     "start"
},
{
"id" :     "2" ,
"label" :     "a"
},
{
"id" :     "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
"label" :     "b"
},
{
"id" :     "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
"label" :     "c"
}
],
"edges" : [
{
"id" :     "1" ,
"from" :     "1" ,
"to" :     "2" ,
"arrows" :     "to"
},
{
"id" :     "078ffa82-5eff-4d33-974b-53890f2c9a18" ,
"from" :     "1" ,
"to" :     "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
"arrows" :     "to"
},
{
"id" :     "90663193-7077-4aca-9011-55bc8745403f" ,
"from" :     "2" ,
"to" :     "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
"arrows" :     "to"
},
{
"id" :     "a6882e25-c07a-4abd-907e-e269d4eda0ec" ,
"from" :     "0b5ba9df-b6c7-4752-94e2-debb6104015c" ,
"to" :     "29bc32c7-acd8-4893-9410-e9895da38b2e" ,
"arrows" :     "to"
}
]
}

然后通过下面的代码来启动流程。

MyFlow1 myFlow1 =    new MyFlow1();
myFlow1.startFlow();

系统关闭时,通过下面的代码关闭流程管理器

FlowStarter.shutdown();

运行

正常结束日志如下

Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
execute:29bc32c7-acd8-4893-9410-e9895da38b2e
node name:d
processing d
Complete success.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#36AE7C"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#36AE7C"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}

流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

异常结束日志如下

Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
... 11 more
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
... 11 more
Complete error.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#EB5353"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#E8F9FD"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}

流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

源码:https://github.com/nobuglady/nobuglady-network

感谢阅读。欢迎Star。

业务可视化-让你的流程图"Run"起来的更多相关文章

  1. 业务可视化-让你的流程图"Run"起来(2.问题与改进)

    前言 首先,感谢大家对上一篇文章[业务可视化-让你的流程图"Run"起来]的支持. 分享一下近期我对这个项目的一些改进. 问题&改进 问题1: 流程运行开始后,异步执行,无 ...

  2. 业务可视化-让你的流程图"Run"起来(3.分支选择&跨语言分布式运行节点)

    前言 首先,感谢大家对上一篇文章[业务可视化-让你的流程图"Run"起来(2.问题与改进)]的支持. 分享一下近期我对这个项目的一些改进. 1. 增加了分支选择工程,可以根据节点的 ...

  3. 业务可视化-让你的流程图"Run"起来(4.实际业务场景测试)

    前言 首先,感谢大家对上一篇文章[业务可视化-让你的流程图"Run"起来(3.分支选择&跨语言分布式运行节点)]的支持. 下面我以实际业务场景为例,来介绍一下ladybug ...

  4. 业务可视化-让你的流程图"Run"起来(6.定时任务&Spring-Batch的集成)

    前言 首先,感谢大家对上一篇文章[业务可视化-让你的流程图"Run"起来(5.SpringBoot集成&微服务编排)]的支持. 分享一下近期我对这个项目的一些改进. 在项目 ...

  5. 业务流程可视化-让你的流程图"Run"起来(7.运行状态持久化&轻量工作流支持)

    前言 感谢大家阅读本项目系列文章和对项目的支持.分享一下我对这个项目的新的改进. 之前项目做到了流程设计可视化和流程运行结果可视化. 本期发布的版本中实现了中间的运行过程的实时可视化,和流程状态持久化 ...

  6. 可视化图表库--goJS

    GoJS是Northwoods Software的产品.Northwoods Software创立于1995年,专注于交互图控件和类库.旗下四款产品: GoJS:用于在HTML上创建交互图的纯java ...

  7. 线程中start和run方法的区别

    先说java中实现多线程常用的两种方式:   1:继承Thread类,并重写run()方法   2:实现Runnable接口,实现run方法实际上Thread类也是实现了Runnable接口 [Jav ...

  8. 基于LadybugFlow的微服务编排(1.SpringBoot集成)

    前言 前面的系列文章里,介绍了ladybugflow的业务可视化的设计以及常见场景的使用方法. 感谢大家对项目的关注. 本篇文章介绍一下基于ladybugflow的微服务编排场景及使用方法. 1. 业 ...

  9. 谈谈对BPM的理解

    BPM的产生缘由 近年来,随着计算机技术的发展和互联网时代的到来,我们已经进入了信息时代,也称为数字化时代,在这数字化的时代里,企业的经营管理都受到了极大的挑战.从上世纪90年代起至今,企业的信息化工 ...

随机推荐

  1. mmdetection获取最高map的epoch

    自动从训练结果中获取最高的mAP所对应的epoch. <code>import json import os ''' :param work_dir 训练结果目录 :return 最好的m ...

  2. 条件表达式和switch语句

    一,条件表达式相当于(相当于if-else) 1.条件表达式 ? : 由条件运算符组成条件表达式的一般情况为: 表达式1?表达式2:表达式3: 求值规则:如果表达式的值为真,则以表达式2的值作为条件的 ...

  3. fxksmdb.exe 是什么进程?

    今天打开电脑应用进程发现fxksmdb.exe.fxksmpl.exe.fxksmW.exe三个进程,经过查看文件路径发现原来是施乐打印机的驱动程序自带的应用,平时都没注意到这个,这下放心了. 我这里 ...

  4. 『现学现忘』Git基础 — 21、git diff命令

    目录 1.git diff 命令说明 2.比较工作区与暂存区中文件的差别 3.比较暂存区与本地库中文件的差别 4.总结git diff命令常见用法 5.总结 1.git diff 命令说明 在comm ...

  5. 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析)

    电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 目录 电机噪声之谐波分析(内附simulink中FFT分析的相关参数配置与解析) 写在前面 正文 电机噪声 谐波的产生 什么 ...

  6. Java获取特定区间随机数及产生不重复随机数

    问题 有这样一种需求,在这样一个数组中String[] arr = new String[]{"电商", "互联网", "小程序", &qu ...

  7. linux篇-CentOS7搭建NTP服务器

    1查看服务器.客户端操作系统版本 2查看服务器是否安装ntp 3如果没有安装 4安装完成后重新查看服务器是否安装ntp 5查看ntp服务器状态 6修改配置文件 注释 #server 0.centos. ...

  8. 121_Power Query之R.Execute的read.xlsx&ODBC

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.问题 pq在用 Excel.Workbook 读取一些Excel早期版本(.xls后缀)的文件时候,报错:DataFo ...

  9. 以点类 Point 及平面图形类 Plane 为基础设计三角形类 Triangle

    学习内容:以点类 Point 及平面图形类 Plane 为基础设计三角形类 Triangle 代码示例: import java.util.Scanner; class Point{ private ...

  10. 有趣的BUG之Stack Overflow

    今天遇到一个很有意思的bug,当程序开发完成后打包到服务器运行,总是会出现栈溢出异常,经过排查发现,问题出现在一个接口上,但这个接口逻辑并不复杂,除了几局逻辑代码外和打印语句之外也没有其他的了,但是只 ...