1.需求
市面上常见的工作流组件一般都是前端通过拖拉拽配置流程图,后端流程引擎解析流程配置,这里我们手写一个简单的流程引擎,先实现串行流程,例如下:
小明提交了一个申请单,然后经过经理审批,审批结束后,不管通过还是不通过,都会经过第三步把结果发送给小明
2.难点分析
- 每个节点审批时间是不确定的,工作流引擎主动式调取下一个节点的逻辑并不适合当前场景
- 节点类型不是固定的后续会增多,工作流引擎与节点类型判断的逻辑不能写死
- 审批时需要传递一些基本信息,如审批人、审批时间等,这些信息如何传递
3.设计
- 采用注册机制,把节点类型及其自有逻辑注册进工作流引擎,以便能够扩展更多节点,使得工作流引擎与节点解耦
- 工作流引擎增加被动式驱动逻辑,使得能够通过外部来使工作流引擎执行下一个节点
- 增加上下文语义,作为全局变量来使用,使得数据能够流经各个节点
4.实现
流程的表示
流程配置好后一般会生成xml或者json格式的文件,这里我们使用xml表示流程
<definitions>
<process id="process_2" name="简单审批例子">
<startEvent id="startEvent_1">
<outgoing>flow_1</outgoing>
</startEvent>
<sequenceFlow id="flow_1" sourceRef="startEvent_1" targetRef="approvalApply_1" />
<approvalApply id="approvalApply_1" name="提交申请单">
<incoming>flow_1</incoming>
<outgoing>flow_2</outgoing>
</approvalApply>
<sequenceFlow id="flow_2" sourceRef="approvalApply_1" targetRef="approval_1" />
<approval id="approval_1" name="审批">
<incoming>flow_2</incoming>
<outgoing>flow_3</outgoing>
</approval>
<sequenceFlow id="flow_3" sourceRef="approval_1" targetRef="notify_1"/>
<notify id="notify_1" name="结果邮件通知">
<incoming>flow_3</incoming>
<outgoing>flow_4</outgoing>
</notify>
<sequenceFlow id="flow_4" sourceRef="notify_1" targetRef="endEvent_1"/>
<endEvent id="endEvent_1">
<incoming>flow_4</incoming>
</endEvent>
</process>
</definitions>
- process表示一个流程
- startEvent表示开始节点,endEvent表示结束节点
- approvalApply、approval、notify分别表示提交申请单、审批、邮件通知节点
- sequenceFlow表示连线,从sourceRef开始,指向targetRef
节点的表示
outgoing表示出边,即节点执行完毕后,应该从那个边出去。
incoming表示入边,即从哪个边进入到本节点。
一个节点只有outgoing而没有incoming,如:startEvent;也可以 只有入边而没有出边,如:endEvent;也可以既有入边也有出边,如:approvalApply、approval、notify。
流程引擎的逻辑
基于上述XML,流程引擎的运行逻辑如下
- 找到process
- 找到开始节点(startEvent)
- 找到startEvent的outgoing边(sequenceFlow)
- 找到该边(sequenceFlow)指向的节点(targetRef=approvalApply_1)
- 执行approvalApply_1节点自身的逻辑
- 找到该节点的outgoing边(sequenceFlow)重复3-5,直到遇到结束节点(endEvent),流程结束
上代码
定义流程
public class PeProcess {
private String id;
public PeNode start;
public PeProcess(String id, PeNode start) {
this.id = id;
this.start = start;
}
public PeNode peNodeWithID(String peNodeID) {
PeNode node = this.start.out.to;
this.start = this.start.out.to;
return node;
}
}
定义节点
public class PeNode {
public String id;
public String type;
public PeEdge in;
public PeEdge out;
public PeNode(String id) {
this.id = id;
}
}
定义边
public class PeEdge {
private String id;
public PeNode from;
public PeNode to;
public PeEdge(String id) {
this.id = id;
}
}
接下来,构建流程图,代码如下:
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.HashMap;
import java.util.Map;
public class XmlPeProcessBuilder {
private String xmlStr;
private final Map<String, PeNode> id2PeNode = new HashMap<>();
private final Map<String, PeEdge> id2PeEdge = new HashMap<>();
public XmlPeProcessBuilder(String xmlStr) {
this.xmlStr = xmlStr;
}
public PeProcess build() throws Exception {
//strToNode : 把一段xml转换为org.w3c.dom.Node
Node definations = XmlUtil.strToNode(xmlStr);
//childByName : 找到definations子节点中nodeName为process的那个Node
Node process = XmlUtil.childByName(definations, "process");
NodeList childNodes = process.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node node = childNodes.item(j);
//#text node should be skip
if (node.getNodeType() == Node.TEXT_NODE) continue;
if ("sequenceFlow".equals(node.getNodeName()))
buildPeEdge(node);
else
buildPeNode(node);
}
Map.Entry<String, PeNode> startEventEntry = id2PeNode.entrySet().stream().filter(entry -> "startEvent".equals(entry.getValue().type)).findFirst().get();
return new PeProcess(startEventEntry.getKey(), startEventEntry.getValue());
}
private void buildPeEdge(Node node) {
//attributeValue : 找到node节点上属性为id的值
PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));
pe