日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

一、什么是工作流引擎

工作流引擎是驅(qū)動(dòng)工作流執(zhí)行的一套代碼。

至于什么是工作流、為什么要有工作流、工作流的應(yīng)用景,同學(xué)們可以看一看網(wǎng)上的資料,在此處不在展開。

二、為什么要重復(fù)造輪子

開源的工作流引擎很多,比如 activiti、flowable、Camunda 等,那么,為什么沒有選它們呢?基于以下幾點(diǎn)考慮:

 

  • 最重要的,滿足不了業(yè)務(wù)需求,一些特殊的場景無法實(shí)現(xiàn)。
  • 有些需求實(shí)現(xiàn)起來比較繞,更有甚者,需要直接修改引擎數(shù)據(jù)庫,這對(duì)于引擎的穩(wěn)定運(yùn)行帶來了巨大的隱患,也對(duì)以后引擎的版本升級(jí)制造了一些困難。
  • 資料、代碼量、API繁多,學(xué)習(xí)成本較高,維護(hù)性較差。
  • 經(jīng)過分析與評(píng)估,我們的業(yè)務(wù)場景需要的BPMN元素較少,開發(fā)實(shí)現(xiàn)的代價(jià)不大。

 

因此,重復(fù)造了輪子,其實(shí),還有一個(gè)更深層次的戰(zhàn)略上的考慮,即:作為科技公司,我們一定要有我們自己的核心底層技術(shù)!這樣,才能不受制于人(參考最近的芯片問題)。

三、怎么造的輪子

對(duì)于一次學(xué)習(xí)型分享來講,過程比結(jié)果更重要,那些只說結(jié)果,不細(xì)說過程甚至不說的分享,我認(rèn)為是秀肌肉,而不是真正意義上的分享。因此,接下來,本文將重點(diǎn)描述造輪子的主要過程。

一個(gè)成熟的工作流引擎的構(gòu)建是很復(fù)雜的,如何應(yīng)對(duì)這種復(fù)雜性呢?一般來講,有以下三種方法:

 

  • 確定性交付:弄清楚需求是什么,驗(yàn)收標(biāo)準(zhǔn)是什么,最好能夠?qū)懗鰷y(cè)試用例,這一步是為了明確目標(biāo)。
  • 迭代式開發(fā):先從小的問題集的解決開始,逐步過渡到解決大的問題集上來,羅馬不是一天建成的,人也不是一天就能成熟的,是需要個(gè)過程的。
  • 分而治之:把大的問題拆成小的問題,小問題的解決會(huì)推動(dòng)大問題的解決(這個(gè)思想適用場景比較多,同學(xué)們可以用心體會(huì)和理解哈)。

 

如果按照上述方法,一步一步的詳細(xì)展開,那么可能需要一本書。為了縮減篇幅而又不失干貨,本文會(huì)描述重點(diǎn)幾個(gè)迭代,進(jìn)而闡述輕量級(jí)工作流引擎的設(shè)計(jì)與主要實(shí)現(xiàn)。

那么,輕量級(jí)又是指什么呢?這里,主要是指以下幾點(diǎn)

 

  • 少依賴:代碼的JAVA實(shí)現(xiàn)上,除了jdk8以外,不依賴與其他第三方j(luò)ar包,從而可以更好的減少依賴帶來的問題。
  • 內(nèi)核化:設(shè)計(jì)上,采用了微內(nèi)核架構(gòu)模式,內(nèi)核小巧,實(shí)用,同時(shí)提供了一定的擴(kuò)展性。從而可以更好地理解與應(yīng)用本引擎。
  • 輕規(guī)范:并沒有完全實(shí)現(xiàn)BPMN規(guī)范,也沒有完全按照BPMN規(guī)范進(jìn)行設(shè)計(jì),而只是參考了該規(guī)范,且只實(shí)現(xiàn)以一小部分必須實(shí)現(xiàn)的元素。從而降低了學(xué)習(xí)成本,可以按照需求自由發(fā)揮。
  • 工具化:代碼上,只是一個(gè)工具(UTIL),不是一個(gè)應(yīng)用程序。從而你可以簡單的運(yùn)行它,擴(kuò)展你自己的數(shù)據(jù)層、節(jié)點(diǎn)層,更加方便的集成到其他應(yīng)用中去。

 

好,廢話說完了,開始第一個(gè)迭代......

四、Hello ProcessEngine

按照國際慣例,第一個(gè)迭代用來實(shí)現(xiàn) hello world 。

1、需求

作為一個(gè)流程管理員,我希望流程引擎可以運(yùn)行如下圖所示的流程,以便我能夠配置流程來打印不同的字符串。


 

2、分析

 

  • 第一個(gè)流程,可以打印Hello ProcessEngine,第二個(gè)流程可以打印ProcessEngine Hello,這兩個(gè)流程的區(qū)別是只有順序不同,藍(lán)色的節(jié)點(diǎn)與紅色的節(jié)點(diǎn)的本身功能沒有發(fā)生變化
  • 藍(lán)色的節(jié)點(diǎn)與紅色的節(jié)點(diǎn)都是節(jié)點(diǎn),它們的功能是不一樣的,即:紅色的節(jié)點(diǎn)打印Hello,藍(lán)色的節(jié)點(diǎn)打印ProcessEngine
  • 開始與結(jié)束節(jié)點(diǎn)是兩個(gè)特殊的節(jié)點(diǎn),一個(gè)開始流程,一個(gè)結(jié)束流程
  • 節(jié)點(diǎn)與節(jié)點(diǎn)之間是通過線來連接的,一個(gè)節(jié)點(diǎn)執(zhí)行完畢后,是通過箭頭來確定下一個(gè)要執(zhí)行的節(jié)點(diǎn)
  • 需要一種表示流程的方式,或是XML、或是JSON、或是其他,而不是圖片

 

3、設(shè)計(jì)

(1)流程的表示

相較于JSON,XML的語義更豐富,可以表達(dá)更多的信息,因此這里使用XML來對(duì)流程進(jìn)行表示,如下所示

flow_1flow_1flow_2flow_2flow_3flow_3

 

  • process表示一個(gè)流程
  • startEvent表示開始節(jié)點(diǎn),endEvent表示結(jié)束節(jié)點(diǎn)
  • printHello表示打印hello節(jié)點(diǎn),就是需求中的藍(lán)色節(jié)點(diǎn)
  • processEngine表示打印processEngine節(jié)點(diǎn),就是需求中的紅色節(jié)點(diǎn)
  • sequenceFlow表示連線,從sourceRef開始,指向targetRef,例如:flow_3,表示一條從printProcessEngine_1到endEvent_1的連線。

 

(2)節(jié)點(diǎn)的表示

 

  • outgoing表示出邊,即節(jié)點(diǎn)執(zhí)行完畢后,應(yīng)該從那個(gè)邊出去。
  • incoming表示入邊,即從哪個(gè)邊進(jìn)入到本節(jié)點(diǎn)。
  • 一個(gè)節(jié)點(diǎn)只有outgoing而沒有incoming,如:startEvent,也可以 只有入邊而沒有出邊,如:endEvent,也可以既有入邊也有出邊,如:printHello、processEngine。

 

(3)流程引擎的邏輯

基于上述XML,流程引擎的運(yùn)行邏輯如下

 

  1. 找到開始節(jié)點(diǎn)(startEvent)
  2. 找到startEvent的outgoing邊(sequenceFlow)
  3. 找到該邊(sequenceFlow)指向的節(jié)點(diǎn)(targetRef)
  4. 執(zhí)行節(jié)點(diǎn)自身的邏輯
  5. 找到該節(jié)點(diǎn)的outgoing邊(sequenceFlow)
  6. 重復(fù)3-5,直到遇到結(jié)束節(jié)點(diǎn)(endEvent),流程結(jié)束

 

4、實(shí)現(xiàn)

首先要進(jìn)行數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),即:要把問題域中的信息映射到計(jì)算機(jī)中的數(shù)據(jù)。

可以看到,一個(gè)流程(PeProcess)由多個(gè)節(jié)點(diǎn)(PeNode)與邊(PeEdge)組成,節(jié)點(diǎn)有出邊(out)、入邊(in),邊有流入節(jié)點(diǎn)(from)、流出節(jié)點(diǎn)(to)。

具體的定義如下:

public class PeProcess {public String id;public PeNode start;public PeProcess(String id, PeNode start) {this.id = id;this.start = start;public class PeEdge {private String id;public PeNode from;public PeNode to;public PeEdge(String id) {this.id = id;public class PeNode {private String id;public String type;public PeEdge in;public PeEdge out;public PeNode(String id) {this.id=id;

PS : 為了表述主要思想,在代碼上比較“奔放自由”,生產(chǎn)中不可直接復(fù)制粘貼!

接下來,構(gòu)建流程圖,代碼如下:

public class XmlPeProcessBuilder {private String xmlStr;private final Map id2PeNode = new HashMap<>();private final Map id2PeEdge = new HashMap<>();public XmlPeProcessBuilder(String xmlStr) {this.xmlStr = xmlStr;public PeProcess build() throws Exception {//strToNode : 把一段xml轉(zhuǎn)換為org.w3c.dom.NodeNode definations = XmlUtil.strToNode(xmlStr);//childByName : 找到definations子節(jié)點(diǎn)中nodeName為process的那個(gè)NodeNode 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 skipif (node.getNodeType() == Node.TEXT_NODE) continue;if ("sequenceFlow".equals(node.getNodeName()))buildPeEdge(node);elsebuildPeNode(node);Map.Entry 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節(jié)點(diǎn)上屬性為id的值PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));peEdge.from = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "sourceRef"), id -> new PeNode(id));peEdge.to = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "targetRef"), id -> new PeNode(id));private void buildPeNode(Node node) {PeNode peNode = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeNode(id));peNode.type = node.getNodeName();Node inPeEdgeNode = XmlUtil.childByName(node, "incoming");if (inPeEdgeNode != null)//text : 得到inPeEdgeNode的nodeValuepeNode.in = id2PeEdge.computeIfAbsent(XmlUtil.text(inPeEdgeNode), id -> new PeEdge(id));Node outPeEdgeNode = XmlUtil.childByName(node, "outgoing");if (outPeEdgeNode != null)peNode.out = id2PeEdge.computeIfAbsent(XmlUtil.text(outPeEdgeNode), id -> new PeEdge(id));

接下來,實(shí)現(xiàn)流程引擎主邏輯,代碼如下:

public class ProcessEngine {private String xmlStr;public ProcessEngine(String xmlStr) {this.xmlStr = xmlStr;public void run() throws Exception {PeProcess peProcess = new XmlPeProcessBuilder(xmlStr).build();PeNode node = peProcess.start;while (!node.type.equals("endEvent")) {if ("printHello".equals(node.type))System.out.print("Hello ");if ("printProcessEngine".equals(node.type))System.out.print("ProcessEngine ");node = node.out.to;

就這?工作流引擎就這?同學(xué)們可千萬不要這樣簡單理解啊,畢竟這還只是hello world而已,各種代碼量就已經(jīng)不少了。

另外,這里面還有很多可以改進(jìn)的空間,比如異常控制、泛化、設(shè)計(jì)模式等,但畢竟只是一個(gè)hello world而已,其目的是方便同學(xué)理解,讓同學(xué)入門。

那么,接下來呢,就要稍微貼近一些具體的實(shí)際應(yīng)用場景了,我們繼續(xù)第二個(gè)迭代。

五、簡單審批

一般來講工作流引擎屬于底層技術(shù),在它之上可以構(gòu)建審批流、業(yè)務(wù)流、數(shù)據(jù)流等類型的應(yīng)用,那么接下啦就以實(shí)際中的簡單審批場景為例,繼續(xù)深入工作流引擎的設(shè)計(jì),好,我們開始。

1、需求

作為一個(gè)流程管理員,我希望流程引擎可以運(yùn)行如下圖所示的流程,以便我能夠配置流程來實(shí)現(xiàn)簡單的審批流。


 

例如:小張?zhí)峤涣艘粋€(gè)申請(qǐng)單,然后經(jīng)過經(jīng)理審批,審批結(jié)束后,不管通過還是不通過,都會(huì)經(jīng)過第三步把結(jié)果發(fā)送給小張。

2、分析

 

  • 總體上來講,這個(gè)流程還是線性順序類的,基本上可以沿用上次迭代的部分設(shè)計(jì)
  • 審批節(jié)點(diǎn)的耗時(shí)可能會(huì)比較長,甚至?xí)_(dá)到幾天時(shí)間,工作流引擎主動(dòng)式的調(diào)取下一個(gè)節(jié)點(diǎn)的邏輯并不適合此場景
  • 隨著節(jié)點(diǎn)類型的增多,工作流引擎里寫死的那部分節(jié)點(diǎn)類型自由邏輯也不合適
  • 審批時(shí)需要申請(qǐng)單信息、審批人,結(jié)果郵件通知還需要審批結(jié)果等信息,這些信息如何傳遞也是一個(gè)要考慮的問題

 

3、設(shè)計(jì)

 

  • 采用注冊(cè)機(jī)制,把節(jié)點(diǎn)類型及其自有邏輯注冊(cè)進(jìn)工作流引擎,以便能夠擴(kuò)展更多節(jié)點(diǎn),使得工作流引擎與節(jié)點(diǎn)解耦
  • 工作流引擎增加被動(dòng)式驅(qū)動(dòng)邏輯,使得能夠通過外部來使工作流引擎執(zhí)行下一個(gè)節(jié)點(diǎn)
  • 增加上下文語義,作為全局變量來使用,使得數(shù)據(jù)能夠流經(jīng)各個(gè)節(jié)點(diǎn)

 

4、實(shí)現(xiàn)

新的XML定義如下:

flow_1flow_1flow_2flow_2flow_3flow_3flow_4flow_4

首先要有一個(gè)上下文對(duì)象類,用于傳遞變量的,定義如下:

public class PeContext {private Map info = new ConcurrentHashMap<>();public Object getValue(String key) {return info.get(key);public void putValue(String key, Object value) {info.put(key, value);

每個(gè)節(jié)點(diǎn)的處理邏輯是不一樣的,此處應(yīng)該進(jìn)行一定的抽象,為了強(qiáng)調(diào)流程中節(jié)點(diǎn)的作用是邏輯處理,引入了一種新的類型--算子(Operator),定義如下:

public interface IOperator {//引擎可以據(jù)此來找到本算子String getType();//引擎調(diào)度本算子void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext);

對(duì)于引擎來講,當(dāng)遇到一個(gè)節(jié)點(diǎn)時(shí),需要調(diào)度之,但怎么調(diào)度呢?首先需要各個(gè)節(jié)點(diǎn)算子注冊(cè)(registNodeProcessor())進(jìn)來,這樣才能找到要調(diào)度的那個(gè)算子。

其次,引擎怎么知道節(jié)點(diǎn)算子自有邏輯處理完了呢?一般來講,引擎是不知道的,只能是由算子告訴引擎,所以引擎要提供一個(gè)功能(nodeFinished()),這個(gè)功能由算子調(diào)用。

最后,把算子任務(wù)的調(diào)度和引擎的驅(qū)動(dòng)解耦開來,放入不同的線程中。

修改后的ProcessEngine代碼如下:

public class ProcessEngine {private String xmlStr;//存儲(chǔ)算子private Map type2Operator = new ConcurrentHashMap<>();private PeProcess peProcess = null;private PeContext peContext = null;//任務(wù)數(shù)據(jù)暫存public final BlockingQueue arrayBlockingQueue = new LinkedBlockingQueue();//任務(wù)調(diào)度線程public final Thread dispatchThread = new Thread(() -> {while (true) {try {PeNode node = arrayBlockingQueue.take();type2Operator.get(node.type).doTask(this, node, peContext);} catch (Exception e) {public ProcessEngine(String xmlStr) {this.xmlStr = xmlStr;//算子注冊(cè)到引擎中,便于引擎調(diào)用之public void registNodeProcessor(IOperator operator) {type2Operator.put(operator.getType(), operator);public void start() throws Exception {peProcess = new XmlPeProcessBuilder(xmlStr).build();peContext = new PeContext();dispatchThread.setDaemon(true);dispatchThread.start();executeNode(peProcess.start.out.to);private void executeNode(PeNode node) {if (!node.type.equals("endEvent"))arrayBlockingQueue.add(node);elseSystem.out.println("process finished!");public void nodeFinished(String peNodeID) {PeNode node = peProcess.peNodeWithID(peNodeID);executeNode(node.out.to);

接下來,簡單(簡陋)實(shí)現(xiàn)本示例所需的三個(gè)算子,代碼如下:

* 提交申請(qǐng)單public class OperatorOfApprovalApply implements IOperator {@Overridepublic String getType() {return "approvalApply";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {peContext.putValue("form", "formInfo");peContext.putValue("applicant", "小張");processEngine.nodeFinished(node.id);* 審批public class OperatorOfApproval implements IOperator {@Overridepublic String getType() {return "approval";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {peContext.putValue("approver", "經(jīng)理");peContext.putValue("message", "審批通過");processEngine.nodeFinished(node.id);* 結(jié)果郵件通知public class OperatorOfNotify implements IOperator {@Overridepublic String getType() {return "notify";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {System.out.println(String.format("%s 提交的申請(qǐng)單 %s 被 %s 審批,結(jié)果為 %s",peContext.getValue("applicant"),peContext.getValue("form"),peContext.getValue("approver"),peContext.getValue("message")));processEngine.nodeFinished(node.id);

運(yùn)行一下,看看結(jié)果如何,代碼如下:

public class ProcessEng.NETest {@Testpublic void testRun() throws Exception {//讀取文件內(nèi)容到字符串String modelStr = Tools.readResoucesFile("model/two/hello.xml");ProcessEngine processEngine = new ProcessEngine(modelStr);processEngine.registNodeProcessor(new OperatorOfApproval());processEngine.registNodeProcessor(new OperatorOfApprovalApply());processEngine.registNodeProcessor(new OperatorOfNotify());processEngine.start();Thread.sleep(1000 * 1);

小張 提交的申請(qǐng)單 formInfo 被 經(jīng)理 審批,結(jié)果為 審批通過process finished!

到此,輕量級(jí)工作流引擎的核心邏輯介紹的差不多了,然而,只支持順序結(jié)構(gòu)是太單薄的,我們知道,程序流程的三種基本結(jié)構(gòu)為順序、分支、循環(huán),有了這三種結(jié)構(gòu),基本上就可以表示絕大多數(shù)流程邏輯。循環(huán)可以看做一種組合結(jié)構(gòu),即:循環(huán)可以由順序與分支推導(dǎo)出來,我們已經(jīng)實(shí)現(xiàn)了順序,那么接下來只要實(shí)現(xiàn)分支即可,而分支有很多類型,如:二選一、N選一、N選M(1<=M<=N),其中N選一可以由二選一的組合推導(dǎo)出來,N選M也可以由二選一的組合推導(dǎo)出來,只是比較啰嗦,不那么直觀,所以,我們只要實(shí)現(xiàn)二選一分支,即可滿足絕大多數(shù)流程邏輯場景,好,第三個(gè)迭代開始。

六、一般審批

作為一個(gè)流程管理員,我希望流程引擎可以運(yùn)行如下圖所示的流程,以便我能夠配置流程來實(shí)現(xiàn)一般的審批流。


 

例如:小張?zhí)峤涣艘粋€(gè)申請(qǐng)單,然后經(jīng)過經(jīng)理審批,審批結(jié)束后,如果通過,發(fā)郵件通知,不通過,則打回重寫填寫申請(qǐng)單,直到通過為止。

1、分析

 

  • 需要引入一種分支節(jié)點(diǎn),可以進(jìn)行簡單的二選一流轉(zhuǎn)
  • 節(jié)點(diǎn)的入邊、出邊不只一條
  • 需要一種邏輯表達(dá)式語義,可以配置分支節(jié)點(diǎn)

 

2、設(shè)計(jì)

 

  • 節(jié)點(diǎn)要支持多入邊、多出邊
  • 節(jié)點(diǎn)算子來決定從哪個(gè)出邊出
  • 使用一種簡單的規(guī)則引擎,支持簡單的邏輯表達(dá)式的解析
  • 簡單分支節(jié)點(diǎn)的XML定義

 

3、實(shí)現(xiàn)

新的XML定義如下:

flow_1flow_1flow_5flow_2flow_2flow_3flow_4approvalResultflow_3flow_4flow_5flow_4flow_6flow_6

其中,加入了simpleGateway這個(gè)簡單分支節(jié)點(diǎn),用于表示簡單的二選一分支,當(dāng)expr中的表達(dá)式為真時(shí),走trueOutGoing中的出邊,否則走另一個(gè)出邊。

節(jié)點(diǎn)支持多入邊、多出邊,修改后的PeNode如下:

public class PeNode {public String id;public String type;public List in = new ArrayList<>();public List out = new ArrayList<>();public Node xmlNode;public PeNode(String id) {this.id = id;public PeEdge onlyOneOut() {return out.get(0);public PeEdge outWithID(String nextPeEdgeID) {return out.stream().filter(e -> e.id.equals(nextPeEdgeID)).findFirst().get();public PeEdge outWithOutID(String nextPeEdgeID) {return out.stream().filter(e -> !e.id.equals(nextPeEdgeID)).findFirst().get();

以前只有一個(gè)出邊時(shí),是由當(dāng)前節(jié)點(diǎn)來決定下一節(jié)點(diǎn)的,現(xiàn)在多出邊了,該由邊來決定下一個(gè)節(jié)點(diǎn)是什么,修改后的流程引擎代碼如下:

public class ProcessEngine {private String xmlStr;//存儲(chǔ)算子private Map type2Operator = new ConcurrentHashMap<>();private PeProcess peProcess = null;private PeContext peContext = null;//任務(wù)數(shù)據(jù)暫存public final BlockingQueue arrayBlockingQueue = new LinkedBlockingQueue();//任務(wù)調(diào)度線程public final Thread dispatchThread = new Thread(() -> {while (true) {try {PeNode node = arrayBlockingQueue.take();type2Operator.get(node.type).doTask(this, node, peContext);} catch (Exception e) {e.printStackTrace();public ProcessEngine(String xmlStr) {this.xmlStr = xmlStr;//算子注冊(cè)到引擎中,便于引擎調(diào)用之public void registNodeProcessor(IOperator operator) {type2Operator.put(operator.getType(), operator);public void start() throws Exception {peProcess = new XmlPeProcessBuilder(xmlStr).build();peContext = new PeContext();dispatchThread.setDaemon(true);dispatchThread.start();executeNode(peProcess.start.onlyOneOut().to);private void executeNode(PeNode node) {if (!node.type.equals("endEvent"))arrayBlockingQueue.add(node);elseSystem.out.println("process finished!");public void nodeFinished(PeEdge nextPeEdgeID) {executeNode(nextPeEdgeID.to);

新加入的simpleGateway節(jié)點(diǎn)算子如下:

* 簡單是非判斷public class OperatorOfSimpleGateway implements IOperator {@Overridepublic String getType() {return "simpleGateway";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("js");engine.put("approvalResult", peContext.getValue("approvalResult"));String expression = XmlUtil.childTextByName(node.xmlNode, "expr");String trueOutGoingEdgeID = XmlUtil.childTextByName(node.xmlNode, "trueOutGoing");PeEdge outPeEdge = null;try {outPeEdge = (Boolean) engine.eval(expression) ?node.outWithID(trueOutGoingEdgeID) : node.outWithOutID(trueOutGoingEdgeID);} catch (ScriptException e) {e.printStackTrace();processEngine.nodeFinished(outPeEdge);

其中簡單使用了js腳本作為表達(dá)式,當(dāng)然其中的弊端這里就不展開了。

為了方便同學(xué)們CC+CV,其他發(fā)生相應(yīng)變化的代碼如下:

* 審批public class OperatorOfApproval implements IOperator {@Overridepublic String getType() {return "approval";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {peContext.putValue("approver", "經(jīng)理");Integer price = (Integer) peContext.getValue("price");//價(jià)格<=200審批才通過,即:approvalResult=trueboolean approvalResult = price <= 200;peContext.putValue("approvalResult", approvalResult);System.out.println("approvalResult :" + approvalResult + ",price : " + price);processEngine.nodeFinished(node.onlyOneOut());* 提交申請(qǐng)單public class OperatorOfApprovalApply implements IOperator {public static int price = 500;@Overridepublic String getType() {return "approvalApply";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {//price每次減100peContext.putValue("price", price -= 100);peContext.putValue("applicant", "小張");processEngine.nodeFinished(node.onlyOneOut());* 結(jié)果郵件通知public class OperatorOfNotify implements IOperator {@Overridepublic String getType() {return "notify";@Overridepublic void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {System.out.println(String.format("%s 提交的申請(qǐng)單 %s 被 %s 審批,結(jié)果為 %s",peContext.getValue("applicant"),peContext.getValue("price"),peContext.getValue("approver"),peContext.getValue("approvalResult")));processEngine.nodeFinished(node.onlyOneOut());public class XmlPeProcessBuilder {private String xmlStr;private final Map id2PeNode = new HashMap<>();private final Map id2PeEdge = new HashMap<>();public XmlPeProcessBuilder(String xmlStr) {this.xmlStr = xmlStr;public PeProcess build() throws Exception {//strToNode : 把一段xml轉(zhuǎn)換為org.w3c.dom.NodeNode definations = XmlUtil.strToNode(xmlStr);//childByName : 找到definations子節(jié)點(diǎn)中nodeName為process的那個(gè)NodeNode 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 skipif (node.getNodeType() == Node.TEXT_NODE) continue;if ("sequenceFlow".equals(node.getNodeName()))buildPeEdge(node);elsebuildPeNode(node);Map.Entry 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節(jié)點(diǎn)上屬性為id的值PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));peEdge.from = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "sourceRef"), id -> new PeNode(id));peEdge.to = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "targetRef"), id -> new PeNode(id));private void buildPeNode(Node node) {PeNode peNode = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeNode(id));peNode.type = node.getNodeName();peNode.xmlNode = node;List inPeEdgeNodes = XmlUtil.childsByName(node, "incoming");inPeEdgeNodes.stream().forEach(n -> peNode.in.add(id2PeEdge.computeIfAbsent(XmlUtil.text(n), id -> new PeEdge(id))));List outPeEdgeNodes = XmlUtil.childsByName(node, "outgoing");outPeEdgeNodes.stream().forEach(n -> peNode.out.add(id2PeEdge.computeIfAbsent(XmlUtil.text(n), id -> new PeEdge(id))));

運(yùn)行一下,看看結(jié)果如何,代碼如下:

public class ProcessEngineTest {@Testpublic void testRun() throws Exception {//讀取文件內(nèi)容到字符串String modelStr = Tools.readResoucesFile("model/third/hello.xml");ProcessEngine processEngine = new ProcessEngine(modelStr);processEngine.registNodeProcessor(new OperatorOfApproval());processEngine.registNodeProcessor(new OperatorOfApprovalApply());processEngine.registNodeProcessor(new OperatorOfNotify());processEngine.registNodeProcessor(new OperatorOfSimpleGateway());processEngine.start();Thread.sleep(1000 * 1);

approvalResult :false,price : 400approvalResult :false,price : 300approvalResult :true,price : 200小張 提交的申請(qǐng)單 200 被 經(jīng)理 審批,結(jié)果為 trueprocess finished!

至此,本需求實(shí)現(xiàn)完畢,除了直接實(shí)現(xiàn)了分支語義外,我們看到,這里還間接實(shí)現(xiàn)了循環(huán)語義。

作為一個(gè)輕量級(jí)的工作流引擎,到此就基本講完了,接下來,我們做一下總結(jié)與展望。

七、總結(jié)與展望

經(jīng)過以上三個(gè)迭代,我們可以得到一個(gè)相對(duì)穩(wěn)定的工作流引擎的結(jié)構(gòu),如下圖所示:


 

通過此圖我們可知,這里有一個(gè)相對(duì)穩(wěn)定的引擎層,同時(shí)為了提供擴(kuò)展性,提供了一個(gè)節(jié)點(diǎn)算子層,所有的節(jié)點(diǎn)算子的新增都在此處中。

此外,進(jìn)行了一定程度的控制反轉(zhuǎn),即:由算子決定下一步走哪里,而不是引擎。這樣,極大地提高了引擎的靈活性,更好的進(jìn)行了封裝。

最后,使用了上下文,提供了一種全局變量的機(jī)制,便于節(jié)點(diǎn)之間的數(shù)據(jù)流動(dòng)。

當(dāng)然,以上的三個(gè)迭代距離實(shí)際的線上應(yīng)用場景相距甚遠(yuǎn),還需實(shí)現(xiàn)與展望以下幾點(diǎn)才可,如下:

 

  • 一些異常情況的考慮與設(shè)計(jì)
  • 應(yīng)把節(jié)點(diǎn)抽象成一個(gè)函數(shù),要有入?yún)ⅰ⒊鰠ⅲ瑪?shù)據(jù)類型等
  • 關(guān)鍵的地方加入埋點(diǎn),用以控制引擎或吐出事件
  • 圖的語義合法性檢查,xsd、自定義檢查技術(shù)等
  • 圖的dag算法檢測(cè)
  • 流程的流程歷史記錄,及回滾到任意節(jié)點(diǎn)
  • 流程圖的動(dòng)態(tài)修改,即:可以在流程開始后,對(duì)流程圖進(jìn)行修改
  • 并發(fā)修改情況下的考慮
  • 效率上的考慮
  • 防止重啟后流轉(zhuǎn)信息丟失,需要持久化機(jī)制的加入
  • 流程的取消、重置、變量傳入等
  • 更合適的規(guī)則引擎及多種規(guī)則引擎的實(shí)現(xiàn)、配置
  • 前端的畫布、前后端流程數(shù)據(jù)結(jié)構(gòu)定義及轉(zhuǎn)換

 

作者:劉洋

分享到:
標(biāo)簽:工作流 引擎
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績?cè)u(píng)定