創(chuàng)建一個流程引擎
先說一下我的環(huán)境
Windows 7
JDK 8
IDEA
第一個工作流我們就完成一個請假的流程:
1. 員工發(fā)出請假申請
2. 經(jīng)理同意或拒絕該申請
3. 發(fā)送電子郵件給員工
使用 IDEA 創(chuàng)建 Maven 項目
添加依賴項
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
由于我用的是 mysql 數(shù)據(jù)庫, 所以添加的是 mysql 的啟動.
當(dāng)你添加完了依賴后你的項目看起來是這個樣子的.(2017年12月13日 最新版本為 6.2.1)
Flowable在內(nèi)部使用SLF4J作為日志框架, 所以我們還需要加入對應(yīng)的坐標(biāo)
dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
Log4j需要一個屬性文件進(jìn)行配置, 使用以下內(nèi)容將log4j.properties文件添加到src / main / resources
文件夾中
log4j.rootLogger = DEBUG丢间,CA
log4j.appender.CA = org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout = org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern =%d {hh:mm:ss缩举,SSS} [%t]%-5p%c%x - %m%n
創(chuàng)建一個新的Java類并添加main
方法:
public class App
{
public static void main( String[] args )
{
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/basics_database?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true")
.setJdbcUsername("root")
.setJdbcPassword("root")
.setJdbcDriver("com.mysql.jdbc.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
}
}
如果我們要創(chuàng)建一個 ProcessEngine
流程引擎, 那么我們就需要先創(chuàng)建一個 ProcessEngineConfiguration
實例. 它允許你配置和調(diào)整設(shè)置的流程引擎.
通常, ProcessEngineConfiguration
是使用配置XML文件創(chuàng)建的,
但是, 我們也可以通過Java代碼來創(chuàng)建它.
對于 ProcessEngineConfiguration
實例的創(chuàng)建我們要重點(diǎn)說一下 setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
代碼. 當(dāng)我們設(shè)置為 true
的時候, 如果我們數(shù)據(jù)庫中的表不存在會被自動創(chuàng)建.
當(dāng)我們流程引擎配置完成之后, 我們就使用 cfg.buildProcessEngine()
方法來實例化一個流程引擎.
注意:
ProcessEngine
實例是一個線程安全的對象, 通常只需在應(yīng)用程序中實例化一次.
啟動應(yīng)用程序
應(yīng)用程序啟動后, 我們需要給他一定的初始化時間, 當(dāng)程序正確結(jié)束后, 我們看一下數(shù)據(jù)庫中是不是多了很多表呢.
部署流程定義
我們將建立的流程是一個非常簡單的請假流程. Flowable引擎期望過程在BPMN 2.0格式中定義, 這是業(yè)界廣泛接受的XML標(biāo)準(zhǔn).
在Flowable術(shù)語中, 我們將這作為一個流程定義來說明. 流程定義定義了請假流程所涉及的不同步驟, 而一個流程實例與特定員工的請假相匹配.
我們假設(shè)這個過程是通過提供一些信息來開始的, 例如員工姓名.
當(dāng)然, 這可以作為這個過程中的第一步. 但是, 通過將其作為輸入數(shù)據(jù), 只有在發(fā)出真正的請求時才會創(chuàng)建實例.
在另一種情況下, 用戶可以在提交之前改變主意并取消, 但是流程實例已經(jīng)被創(chuàng)建. 在某些情況下, 這可能是有價值的信息(例如, 流程已啟動多少次, 但尚未完成), 具體取決于業(yè)務(wù)目標(biāo).
- 左邊的圓叫做啟動事件. 這是流程實例的起點(diǎn).
- 第一個矩形是一個用戶任務(wù). 這是人類用戶必須執(zhí)行的過程中的一個步驟. 在這種情況下, 經(jīng)理需要批準(zhǔn)或拒絕請求.
- 根據(jù)經(jīng)理的決定, Exclusive Gateway(帶X字的菱形)將流程實例路由到批準(zhǔn)路徑或拒絕路徑.
- 如果同意, 我們必須在某個外部系統(tǒng)中注冊該請求, 然后再為用戶通知用戶任務(wù), 通知他們你的請假同意.
- 如果被拒絕, 則會向員工發(fā)送電子郵件, 通知他們這一點(diǎn).
對應(yīng)于上圖的BPMN 2.0 XML如下所示. 請注意, 這只是過程的一部分. 如果您使用的是圖形建模工具, 則底層的XML文件還包含描述圖形信息的可視化部分, 例如流程定義的各個元素的坐標(biāo)(所有圖形信息包含在XML 中的BPMNDiagram標(biāo)記中, 這是定義標(biāo)記的子元素).
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://sourceforge.net/bpmn/definitions/_1513158409301" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="_1513158409301" name="" targetNamespace="http://sourceforge.net/bpmn/definitions/_1513158409301" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="PROCESS_1" isClosed="false" isExecutable="true" processType="None">
<extensionElements>
<yaoqiang:description/>
<yaoqiang:pageFormat height="841.8897637795276" imageableHeight="831.8897637795276" imageableWidth="588.1102362204724" imageableX="5.0" imageableY="5.0" orientation="0" width="598.1102362204724"/>
<yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1"/>
</extensionElements>
<startEvent id="_2" isInterrupting="true" name="開始" parallelMultiple="false">
<outgoing>_10</outgoing>
<outputSet/>
</startEvent>
<userTask completionQuantity="1" id="_3" implementation="##unspecified" isForCompensation="false" name="同意或拒絕" startQuantity="1">
<incoming>_10</incoming>
<outgoing>_11</outgoing>
</userTask>
<exclusiveGateway gatewayDirection="Diverging" id="_4">
<incoming>_11</incoming>
<outgoing>_12</outgoing>
<outgoing>_13</outgoing>
</exclusiveGateway>
<serviceTask completionQuantity="1" id="_5" implementation="##WebService" isForCompensation="false" name="在外部系統(tǒng)中輸入假期" startQuantity="1">
<incoming>_12</incoming>
<outgoing>_14</outgoing>
</serviceTask>
<serviceTask completionQuantity="1" id="_6" implementation="##WebService" isForCompensation="false" name="發(fā)送拒絕郵件" startQuantity="1">
<incoming>_13</incoming>
<outgoing>_16</outgoing>
</serviceTask>
<userTask completionQuantity="1" id="_7" implementation="##unspecified" isForCompensation="false" name="假期批準(zhǔn)" startQuantity="1">
<incoming>_14</incoming>
<outgoing>_15</outgoing>
</userTask>
<sequenceFlow id="_10" sourceRef="_2" targetRef="_3"/>
<endEvent id="_9" name="結(jié)束">
<incoming>_16</incoming>
<inputSet/>
</endEvent>
<sequenceFlow id="_11" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_12" name="同意" sourceRef="_4" targetRef="_5">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_13" name="拒絕" sourceRef="_4" targetRef="_6">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_14" sourceRef="_5" targetRef="_7"/>
<sequenceFlow id="_15" sourceRef="_7" targetRef="_8"/>
<sequenceFlow id="_16" sourceRef="_6" targetRef="_9"/>
<endEvent id="_8" name="結(jié)束">
<incoming>_15</incoming>
<inputSet/>
</endEvent>
</process>
</definitions>
Exclusive Gateway(帶X字的菱形)的流顯然是特殊的: 都具有以表達(dá)式的形式定義的條件.
當(dāng)流程實例執(zhí)行到達(dá)Exclusive Gateway時, 將評估條件獲取并解析為true, 只有第一個表達(dá)式滿足.
當(dāng)然, 如果需要不同的路由行為, 其他類型的Exclusive Gateway也是可能的.
這里以表達(dá)式形式寫出的條件是$ {approved}
, 它是$ {approved == true}
的簡寫形式.
approved變量稱為流程變量. 流程變量是一個持久的數(shù)據(jù)位, 與流程實例一起存儲, 可以在流程實例的生命周期中使用.
在這種情況下, 這意味著我們將不得不在流程實例中的某個點(diǎn)設(shè)置此流程變量.
將它部署到引擎中
現(xiàn)在我們有了BPMN 2.0 XML文件的流程, 接下來我們需要將它部署到引擎中. 部署流程定義意味著:
- 流程引擎會將XML文件存儲在數(shù)據(jù)庫中, 因此可以在需要時進(jìn)行檢索.
- 流程定義被解析為一個內(nèi)部可執(zhí)行的對象模型, 以便流程實例可以從中啟動.
要將流程定義部署到Flowable引擎, 需要使用RepositoryService
, 可以從ProcessEngine
對象中獲取.
使用RepositoryService
, 通過傳遞XML文件的位置并調(diào)用deploy()
方法來實際執(zhí)行它, 創(chuàng)建一個新的部署:
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
現(xiàn)在, 我們可以通過API查詢流程定義來確認(rèn)流程定義是否已經(jīng)被引擎所了解. 這是通過RepositoryService
創(chuàng)建一個新的ProcessDefinitionQuery
對象來完成的雌桑。
啟動一個流程實例
我們現(xiàn)在將流程定義部署到流程引擎, 因此可以使用此流程定義作為藍(lán)圖來啟動流程實例.
要啟動流程實例, 我們需要提供一些初始流程變量. 通常情況下, 當(dāng)某個進(jìn)程被自動觸發(fā)時, 您將通過呈現(xiàn)給用戶的表單或通過REST API獲取這些表單.
在這個例子中, 我們將保持簡單并使用 java.util.Scanner
類在命令行上簡單地輸入一些數(shù)據(jù):
Scanner scanner= new Scanner(System.in);
System.out.println("你是誰?");
String employee = scanner.nextLine();
System.out.println("你要多少假期?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());
System.out.println("你為什么需要他們?");
String description = scanner.nextLine();
接下來, 我們可以通過RuntimeService
啟動一個流程實例. 收集的數(shù)據(jù)以java.util.Map
實例的形式傳遞, 其中的關(guān)鍵字是稍后用于檢索變量的標(biāo)識符.
流程實例使用鍵啟動. 此鍵匹配在BPMN 2.0 XML文件中設(shè)置的id屬性, 在此情況下為PROCESS_1
展父。
<process id="PROCESS_1" isClosed="false" isExecutable="true" processType="None">
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("PROCESS_1", variables);
當(dāng)流程實例啟動時, 會創(chuàng)建一個execution并放入啟動事件. 這個execution遵循用戶任務(wù)的順序流程以供管理者批準(zhǔn), 并執(zhí)行用戶任務(wù)行為. 此行為將在數(shù)據(jù)庫中創(chuàng)建一個任務(wù), 以后可以使用查詢找到該任務(wù).
查詢和完成任務(wù)
在更現(xiàn)實的應(yīng)用程序中, 將會有一個用戶界面, 員工和經(jīng)理可以登錄并查看他們的任務(wù)列表.
通過這些, 他們可以檢查存儲為流程變量的流程實例數(shù)據(jù), 并決定他們想要處理的任務(wù). 在這個例子中, 我們將通過執(zhí)行通常位于驅(qū)動UI的服務(wù)調(diào)用后面的API調(diào)用來模擬任務(wù)列表.
要注意我們還沒有為userTask進(jìn)行分配. 在這個例子中, 我們將第一個userTask分配給經(jīng)理組. 將第二個 userTask 分配給請假申請的原始請求者.
為此, 請將 candidateGroups
屬性添加到第一個任務(wù):
<userTask flowable:candidateGroups="managers" completionQuantity="1" id="_3" implementation="##unspecified" isForCompensation="false" name="同意或拒絕" startQuantity="1"></userTask>
而第二個任務(wù), 如下所示. 請注意, 我們沒有像上面的flowable:candidateGroups="managers"
那樣使用靜態(tài)值, 而是基于流程實例啟動時所傳遞的流程變量的動態(tài)賦值:
<userTask flowable:assignee="${employee}" completionQuantity="1" id="_7" implementation="##unspecified" isForCompensation="false" name="假期批準(zhǔn)" startQuantity="1"></userTask>
為了得到實際的任務(wù)列表, 我們通過TaskService
創(chuàng)建一個TaskQuery
, 并且配置查詢只返回managers
的任務(wù):
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
System.out.println("You have " + tasks.size() + " tasks:");
for (int i=0; i<tasks.size(); i++) {
System.out.println((i+1) + ") " + tasks.get(i).getName());
}
使用任務(wù)標(biāo)識符, 我們現(xiàn)在可以獲得特定的流程實例變量,
并在屏幕上顯示實際的請求:
System.out.println("你想完成哪個任務(wù)?");
int taskIndex = Integer.valueOf(scanner.nextLine());
Task task = tasks.get(taskIndex - 1);
Map<String, Object> processVariables = taskService.getVariables(task.getId());
System.out.println(processVariables.get("employee") + " wants " +
processVariables.get("nrOfHolidays") + " of holidays. 你贊成這個嗎?");
如果上面都做完了, 我們可以執(zhí)行以下程序, 就可以看到我們的任務(wù)列表.
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
該任務(wù)現(xiàn)在已經(jīng)完成, 并且基于同意的流程變量來選擇離開專用網(wǎng)關(guān)的兩個路徑之一.
編寫JavaDelegate
還有最后一塊難題還沒有完成: 我們還沒有實現(xiàn)自動邏輯,
當(dāng)請求被同意時, 這些自動邏輯就會被執(zhí)行. 在BPMN 2.0 XML中, 這是一個 serviceTask, 它看起來像:
<serviceTask flowable:class="Flowable.cc.CallExternalSystemDelegate" ></serviceTask>
實際上, 這個邏輯可以是任何東西, 從用HTTP REST調(diào)用一個服務(wù)到執(zhí)行一些傳統(tǒng)的代碼調(diào)用到一個組織幾十年來一直使用的系統(tǒng). 我們不會在這里實現(xiàn)實際的邏輯, 只是記錄處理.
使該類實現(xiàn) org.flowable.engine.delegate.JavaDelegate
接口并實現(xiàn) execute
方法:
public class CallExternalSystemDelegate implements JavaDelegate {
public void execute(DelegateExecution delegateExecution) {
}
}