前言
前面几篇文章分别介绍了前端使用mxGraph拖拽实现自定义流程,然后前端导出为xml文件,后端读取并解析xml,那么这篇文章介绍一下,如何使用java来实现一个轻量级的工作流引擎,使用工作流引擎来执行前端拖拽生成的流程。目前网上有很多开源可靠的工作流引擎,但是普遍偏重量级,有些还需要和idea结合才能使用拖拽实现工作流。
实现思路
1、后端解析xml文件,将xml文件转化为java对象。 2、定义后端节点处理器,每一个xml节点对应一个后端处理节点。 3、使用多线程技术来执行,更具拖拽形成的流程图,相应的实现串行或者并行。 4、定义回调接口实现节点间消息的传递和通知。 5、定义枚举类,将自定义的节点处理类和xml中的节点关联
代码编写
1、抽象node接口
public interface Node {
public void run(NodeCallBack callBack);
public String getName();
}
2、编写一个抽象节点继承Node接口
public abstract class AbstractNode implements Node {
public MxElement mxElement;
private Runnable runnable;
/**
* 自定义业务参数传递
*/
public Map<String, Object> map = new ConcurrentHashMap<String, Object>();
public AbstractNode(MxElement mxElement) {
this.mxElement = mxElement;
}
// 支持动态扩展
public void run(NodeCallBack callBack) {
map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_SUCCESS); // 默认运行状态为成功。
try {
verify();
runNode(callBack);
map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_SUCCESS);
//MessagePushSocketUtils.sendMsg(MessagePushSocketUtils.AUTOUI_MESSAGE, mxCell.getId() + "|" + NodeRunState.NODE_TYPE_SUCCESS, 0);
} catch (Exception e) {
e.printStackTrace();
//MessagePushSocketUtils.sendMsg(MessagePushSocketUtils.AUTOUI_MESSAGE, mxCell.getId() + "|" + NodeRunState.NODE_TYPE_FAIL, 0);
map.put(NodeRunState.RUN_TYPE, NodeRunState.RUN_FAIL);
}
callBack.callBack(map);
// 每个节点运行完成后默认休眠一段事件
sleep();
}
public abstract void runNode(NodeCallBack callBack) throws Exception;
/**
* 检验参数
*/
public abstract void verify() throws Exception;
public void sleep() {
sleep(10000L);
}
public void sleep(Long millis) {
ThreadUtils.doSleep(millis);
}
}
3、定义回调接口
public interface NodeCallBack {
public void callBack(Map<String, Object> paramsMap);
}
4、定义多线程处理
public class AutoUiExcutor {
private static final String NODE_NAME = "nodeName";
/**
* 全局锁, 用于控制多个上游节点公用一个下游节点的情况,保证下游节点只运行一次
*/
private CountDownLatch latch;
public void start(MxCell mxCell,String folwId) {
this.run(mxCell, folwId);
}
public void run(MxCell mxCell, String folwId) {
Node node = getNodeInstance(mxCell);
LogUtils.debug(node.getName() + "即将运行" + Thread.currentThread().getName());
node.run(new NodeCallBack() {
@Override
public void callBack(Map<String,Object> paramsMap) {
// 执行下一个时先等待一下,防止初始化工作没有完成
// 1标识成功,2标识执行失败
if (paramsMap.get(NodeRunState.RUN_TYPE).equals(NodeRunState.RUN_SUCCESS)) {
runNext(mxCell.hasNext() ? mxCell.getNextCell() : new ArrayList<MxCell>(), paramsMap, folwId);
}
}
});
}
public void runNext(List<MxCell> mxCells, Map<String,Object> paramsMap, String folwId) {
// 串行
int size = mxCells.size();
if (size > 0 && size == 1) {
MxCell mxCell = mxCells.get(0);
mxCell.setParams(paramsMap); // 设置自定义参数
if (!hasManyUpstream(mxCell, folwId)) {
run(mxCell,folwId);
}
return;
}
// 多个下游节点需要并行运行
for (MxCell mxCell : mxCells) {
mxCell.setParams(paramsMap); // 设置自定义参数
// 如果节点具有多个上游,要等待所有上游执行完成,再执行,且只执行一次
if (!hasManyUpstream(mxCell, folwId)) {
AutoUiThreadPool.getThreadPool(folwId).execute(() -> {
run(mxCell, folwId);
});
}
}
}
/**
* 处理节点具有多个上游节点的情况。
* @param mxCell
* @param folwId
* @return
*/
private boolean hasManyUpstream(MxCell mxCell, String folwId) {
if (mxCell.hasManyUpstreamMode()) {
List<Integer> upstreamNode = mxCell.getUpstreamNode();
LogUtils.debug(mxCell.getName() + "具有多个上游节点,节点个数为:" + upstreamNode.size());
if (latch == null || latch.getCount() == 0) {
latch = new CountDownLatch(upstreamNode.size());
}
latch.countDown();
long count = latch.getCount();
if (count == 0) {
run(mxCell, folwId);
// 运行过之后,就要把锁消耗完
while(latch.getCount() > 0) {
latch.countDown();
}
}
return true;
}
return false;
}
private Node getNodeInstance(MxCell mxCell) {
String name = mxCell.getMxObjectData().get(NODE_NAME);
Class<? extends Node> nodeClassByNodeName = NodeEnum.getNodeClassByNodeName(name);
Node node = null;
try {
node = nodeClassByNodeName.getConstructor(MxCell.class).newInstance(mxCell);
} catch (InstantiationException | InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
return node;
}
}
5、定义枚举
定义枚举的目的是为了将节点处理类和xml中的节点关联
public enum NodeTypeEnum {
BUTTON("button", ButtonNode.class),
START("start", StartNode.class),
CLICK("click", ClickNode.class),
DBCLICK("dbClick", DbClickNode.class),
IF("if", JudgeNode.class),
TEXT("text", TextNode.class),
ALERT("alert", AlertHandleNode.class),
SWITCH("switch", SwitchNode.class),
PRINT("print", PrintNode.class),
OVER("over", OverNode.class),
DELAYED("delayed", DelayedNode.class);
private final String nodeName;
private final Class<? extends Node> nodeClass;
NodeTypeEnum(String nodeName, Class<? extends Node> nodeClass) {
this.nodeName = nodeName;
this.nodeClass = nodeClass;
}
public String getNodeName() {
return nodeName;
}
public Class<? extends Node> getNodeClass() {
return nodeClass;
}
public static Class<? extends Node> getNodeClassByNodeName(String nodeName) {
NodeTypeEnum nodeTypeEnum = getEnumByNodeName(nodeName);
return nodeTypeEnum.getNodeClass();
}
public static Class<? extends Node> getNodeClassByNode(Node node) {
String nodeName = node.getName();
return getNodeClassByNodeName(nodeName);
}
public static NodeTypeEnum getEnumByNodeName(String nodeName) {
for (NodeTypeEnum anEnum : values()) {
if (anEnum.getNodeName().equals(nodeName)) {
return anEnum;
}
}
return null;
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第7天,点击查看活动详情