前言:
Jef-Flow是一个基于xml为数据载体的轻量级的流程处理引擎,该组件脱胎于PostGirl(gitee.com/chengzhi2/p…) 项目。
优缺点
目前主流的流程引擎,常见的有:jBPM、activiti、SWF等,这些流程引擎提供了很多丰富的功能,比如:基于角色的流程流转、表单设计器,有些甚至还集成了流程设计插件,相比较而言Jef-Flow功能比较单一,但是假设有如下场景:一个串行的业务调用流程,比如A接口执行完成后执行B接口,B接口执行完成后执行C接口,场景很简单,但是如果使用比如activiti流程引擎,自动生成的那堆表就够开发者去了解熟悉很长时间了。但是如果使用Jef-Flow,学习成本基本为零,使用时只需要导入一个jar包,添加一个注解,然后自定义实现流程节点即可完成一个业务流程的自动调度。
Jef-Flow优点:
1、数据配置简单
基于xml的做为数据的载体,不需要和数据库等其他数据源交互。后续还会支持json做为数据载体。。
2、天然支持MxGraph
对于一些低代码平台中的流程可视化,流程设计器是重要的支撑工具,目前市场上基于mxGraph的流程设计器有很多,所以Jef-FLow中内置了MxGraph.xml解析器,支持原生mxGraph.js导出的xml文件解析。
3、开发流程简练
开发者只需简单配置即可启动流程,无需额外的节点配置。
4、扩展性强
由于是基于xml的,xml可自定义任何内容,只要符合xml规范即可,可自定义xml或基于内置的实现去扩展解析类。
5、支持实例节点串行、并行、合并运行
采用多线程的方式实现节点的并行运行,采用多线程锁技术实现流程合并或等待。
Jef-Flow使用
1、导入jar包 2、如果是springBoot项目,直接在Application启动类上添加注解开启Jef-Flow功能。注解为@EnableJefFlow(basePackages = "xxx"),xxx表示节点所在的包路径 3、自定义流程实例,继承AbstractNode类,在runNode中写实例的具体任务。 4、对于自动的xml文件,需要自定义xml解析规则,Jef-flow提供公共接口和抽象类。开发者可根据实际情况来选择继承或自己实现接口来解析xml. 5、使用JefFlowAutoExcutor.run唤起开始实例,之后程序将自动调度。
使用范例:
1、基于MsGraph的流程。
<mxGraphModel>
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="2" value="初始化百度网页" style="nodeStyle;image=/img/start.f26db4f5.png" parent="1" vertex="1">
<mxGeometry x="220" y="80" width="130" height="35" as="geometry" />
<Object name="开始" icon="/img/start.f26db4f5.png" nodeName="start" fullPath="" context="https://www.baidu.com/" operation="" as="data" />
</mxCell>
<mxCell id="3" value="输入搜索内容" style="nodeStyle;image=/img/text.ab22920f.png" parent="1" vertex="1">
<mxGeometry x="220" y="170" width="130" height="35" as="geometry" />
<Object name="文本框" icon="/img/text.ab22920f.png" nodeName="text" fullPath="/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input" context="hello world" operation="" as="data" />
</mxCell>
<mxCell id="4" value="点击百度一下" style="nodeStyle;image=/img/button.144b855d.png" parent="1" vertex="1">
<mxGeometry x="220" y="250" width="130" height="35" as="geometry" />
<Object name="按钮" icon="/img/button.144b855d.png" nodeName="button" fullPath="/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input" context="" operation="3" as="data" />
</mxCell>
<mxCell id="5" parent="1" source="2" target="3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="6" parent="1" source="3" target="4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="7" value="结束" style="nodeStyle;image=/img/over.61db7b65.png" parent="1" vertex="1">
<mxGeometry x="220" y="490" width="130" height="35" as="geometry" />
<Object name="结束" icon="/img/over.61db7b65.png" nodeName="over" fullPath="" context="" operation="" as="data" />
</mxCell>
<mxCell id="8" value="延时器" style="nodeStyle;image=/img/delayed.288d27d6.png" vertex="1" parent="1">
<mxGeometry x="220" y="360" width="130" height="35" as="geometry" />
<Object name="延时器" icon="/img/delayed.288d27d6.png" nodeName="delayed" fullPath="" context="" operation="20000" as="data" />
</mxCell>
<mxCell id="9" edge="1" parent="1" source="4" target="8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="10" edge="1" parent="1" source="8" target="7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
开启功能
@SpringBootApplication
@EnableJefFlow(basePackages = "com.cz.node")
public class JefFlowTestApplication {
public static void main(String[] args) {
SpringApplication.run(JefFlowTestApplication.class, args);
}
}
创建流程实例,这里以start节点为例:
public class StartNode extends AbstractNode {
public StartNode(JefFlowCell mxCell) {
super(mxCell);
}
@Override
public void verify() {
System.out.println(getName() + "检验参数");
}
@Override
public void runNode(NodeCallBack callBack) throws Exception{
System.out.println("do soming...");
}
@Override
public String getName() {
MxCell mxCell = (MxCell) jefFlowCell;
return mxCell.getName();
}
}
开始执行流程任务:
public void test3(@RequestBody Map map) throws Exception {
String xml = (String) map.get("xml");
String flowId = "test";
MxGraphParser graphParser = new MxGraphParser(xml);
JefFlowRoot mxGraphModel = graphParser.parseMxGraph();
JefFlowCell startCell = mxGraphModel.getStartCell();
JefFlowAutoThreadPool.getThreadPool(flowId).execute(new Runnable() {
@Override
public void run() {
autoUiExcutor.run(startCell, flowId);
}
});
}
输出结果:
2、自定义xml流程
自定义xml格式
<myRoot>
<myCell id="1" name="第一个节点" node_name="first" next_id="2" is_start="true"></myCell>
<myCell id="2" name="第二个节点" node_name="second" next_id="3"></myCell>
<myCell id="3" name="第三个节点" node_name="third" next_id="4"></myCell>
<myCell id="4" name="第四个节点" node_name="fourth" next_id="5"></myCell>
<myCell id="5" name="第五个节点" node_name="fifth" next_id="6"></myCell>
<myCell id="6" name="第六个节点" node_name="sixth"></myCell>
</myRoot>
自定义xml解析器
用来解析xml一级节点
public class MyXmlRoot extends AbstractJefFlowRoot {
private MyCell startCell;
public MyXmlRoot(Document document) {
super(document);
}
@Override
public Boolean isEdge(Element element) {
return null;
}
@Override
public JefFlowCell getStartCell() {
return startCell;
}
@Override
public void handleElement(Element element) {
if ("myCell".equalsIgnoreCase(element.getName())) {
MyCell myCell = new MyCell(element,this);
if (myCell.isStart()) {
this.startCell = myCell;
}
addCell(myCell);
}
}
@Override
public void handleElement(JsonElement jsonElement) {
}
}
解析最小元素
public class MyCell extends AbstractJefFlowCell {
Map<String, Object> params = new HashMap<>();
private JefFlowRoot jefFlowRoot;
MyCell(Element element, JefFlowRoot jefFlowRoot) {
super(element);
this.jefFlowRoot = jefFlowRoot;
}
@Override
public Integer getId() {
return Integer.valueOf(String.valueOf(getData().get("id")));
}
@Override
public String getName() {
return (String) getData().get("name");
}
@Override
public String getFlowNodeName() {
return (String) getData().get("node_name");
}
@Override
public void setParams(Map<String, Object> map) {
this.params = map;
}
@Override
public Map<String, Object> getParams() {
return this.params;
}
@Override
public Boolean isStart() {
Object is_start = getData().get("is_start");
if (is_start != null && String.valueOf(is_start).equalsIgnoreCase("true") ) {
return true;
} else {
return false;
}
}
@Override
public boolean hasNext() {
boolean flag = false;
Object next_id = getData().get("next_id");
if (next_id != null) {
flag = true;
}
return flag;
}
public Integer getNextId() {
Object next_id = getData().get("next_id");
if (next_id != null) {
return Integer.valueOf(String.valueOf(next_id));
}
return null;
}
@Override
public List<JefFlowCell> getNextCell() {
Integer nextId = getNextId();
List<JefFlowCell> jefFlowCells = new ArrayList<>();
for (JefFlowCell jefFlowCell : jefFlowRoot.getCells()) {
MyCell myCell = (MyCell) jefFlowCell;
Integer id = myCell.getId();
if (nextId != null && nextId == id) {
jefFlowCells.add(jefFlowCell);
}
}
return jefFlowCells;
}
@Override
public List<Integer> getUpstreamNode() {
int currentId = getId();
List<Integer> ids = new ArrayList<>();
for (JefFlowCell jefFlowCell : jefFlowRoot.getCells()) {
MyCell myCell = (MyCell) jefFlowCell;
Integer nextId = myCell.getNextId();
if (nextId != null && nextId == currentId) {
ids.add(myCell.getId());
}
}
return ids;
}
@Override
public boolean hasManyUpstreamMode() {
List<Integer> upstreamNode = getUpstreamNode();
if (upstreamNode != null && upstreamNode.size() > 0) {
return true;
}
return false;
}
@Override
public Map<String, Object> getCustomizeData() {
// 获取自定义节点
return null;
}
}
实例节点写法和上面的一样,这里就不单独举例了。
开始执行流程
public void test4(@RequestBody Map map) throws Exception {
String xml = (String) map.get("xml");
String flowId = "test";
JefFlowParser graphParser = new JefFlowXmlParser(xml);
Document parse = graphParser.parse();
JefFlowRoot mxGraphModel = new MyXmlRoot(parse);
JefFlowCell startCell = mxGraphModel.getStartCell();
JefFlowAutoThreadPool.getThreadPool(flowId).execute(new Runnable() {
@Override
public void run() {
autoUiExcutor.run(startCell, flowId);
}
});
}
运行结果:
需要注意的点:
1、由于是内置的MxGraph操作,所以如果使用MxGraph导出的xml做为数据载体,则需要符合以下规范:
①、mxCell中自定义数据需保存到Object标签下,如果是开始节点,需明确指定开始节点且必须为nodeName="start"
②、mxCell中其他非开始节点,则可以自定义任何数据,但是Object标签下必须包含nodeName字段用来和Jef-Flow中后台实例节点映射。
2、自定义处理实例节点类需要以Node结尾,由于JefFlowAutoExcutor自动执行器是根据内置的注册表反射来获取实例的,所以启动的时候Jef-Flow会根据注解上的包路径来自动注册实例,实例的名称是截取nodeName.subString(0,length-4)。
真实案例:
PostGirl使用MxGraph作为流程设计器配合Jef-Flow实现的UI自动化工具。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第11天,点击查看活动详情