前言

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

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

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

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

问题

对于简单的流程

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. Effective HPA:预测未来的弹性伸缩产品

    作者 胡启明,腾讯云专家工程师,专注 Kubernetes.降本增效等云原生领域,Crane 核心开发工程师,现负责成本优化开源项目 Crane 开源治理和弹性能力落地工作. 余宇飞,腾讯云专家工程师 ...

  2. C3P0反序列化链学习

    C3P0 c3p0第一次听闻是用于fastjson的回显上,大佬们总结三种方法,后面两种主要就是用于fastjson和jackjson的回显利用(注入内存马) http base jndi hex序列 ...

  3. mask-image实现聚光灯效果

    大家好,我是半夏,一个刚刚开始写文的沙雕程序员.如果喜欢我的文章,可以关注 点赞 加我微信:frontendpicker,一起学习交流前端,成为更优秀的工程师-关注公众号:搞前端的半夏,了解更多前端知 ...

  4. 【科普】为什么ip地址通常以192.168开头?

    开源Linux 回复"读书",挑选书籍资料~ 我们做运维的,与ip地址接触最多,无论是运维的哪方面,都需要跟ip地址打交道,通常我们也会经常听到公网.内网?那什么是公网ip地址呢? ...

  5. 题解0012:剪花布条(KMP)

    信奥一本通1465 KPM例题 题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1465 题目描述:给出花布条和小饰条(字符串),求花布条中能剪 ...

  6. 万字长文,带你轻松学习 Spark

    大家好,我是大D. 今天给大家分享一篇 Spark 核心知识点的梳理,对知识点的讲解秉承着能用图解的就不照本宣科地陈述,力求精简.通俗易懂.希望能为新手的入门学习扫清障碍,从基础概念入手.再到原理深入 ...

  7. 源码解读etcd heartbeat,election timeout之间的拉锯

    转一个我在知乎上回答的有关raft election timeout/ heartbeat interval 的回答吧. 答:准确来讲: election是timeout,而heartbeat 是in ...

  8. Druid数据库连接池使用体验

    写在前面 在实际工作中我们我们使用较多的则是Spring默认的HikariDataSource数据库连接池,但是它无法提供可视化监控SQL这一能力,而这在很多场景下往往又是我们需要的功能,因此今天来学 ...

  9. 网易数帆 Envoy Gateway 实践之旅:坚守 6 年,峥嵘渐显

    服务网格成熟度不断提升,云原生环境下流量处理愈发重要, Envoy Gateway 项目于近日宣布开源,"旨在大幅降低将 Envoy 作为 API 网关的使用门槛",引发了业界关注 ...

  10. Java 接口返回值集合防止空指针

    接口 返回值为一个集合 public interface UserSearchService{ List<User> listUser(); } 接口实现 public List<U ...