Jef-Flow轻量级流程引擎的使用

190 阅读4分钟

前言:

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);
        }
    });
}

输出结果:

image.png

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);
        }
    });
}

运行结果:

image.png

需要注意的点:
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自动化工具。

image.png

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第11天,点击查看活动详情