《partner4java 講述jBPM4》之第二步:描述工作流程 & 處理工作流程

通過上一章的工作流開發展示,我們可以得出結論(“白話”):

1、“環境”:

     首先jBPM4給開發者提供了一套環境,如:java框架、數據庫。

     數據庫無可厚非是我們流程的各數據保存的地方(因爲保存數據總要有一定的規則,所以數據表是已定義好的);java框架爲jBPM4提供的一套開發架構,用於分析流程定義、管理流程等。

2、“描述工作流程”:

     從架構師的思路出發,我們的流程千變萬化,那麼如何告知我們的架構具體某個業務流程應該按什麼樣的流程執行呢?

     就當下流行的描述“手法”-- xml ,但是這個xml應該按照一定的規則去定義吧(且解析總需要有規則)?於是出現了“jPDL”,被稱爲jBPM最寶貴的資產。但是,當某種技術比較流行時,一些大佬就坐不住了,他們就會推出一些規範,於是乎出現了“BPEL”。但是很抱歉“BPEL”就jBPM4來講並沒有完全支持,所以我們後面的重點在“jPDL”。

3、“流程運轉”:

    當流程使用XML定義之後,無可厚非要轉換成Java對象並保存進數據庫(也就是發佈),這裏我們藉助了Hibernate。

    轉換爲“對象後”,我們就應該“驅趕”流程定義對象按照描述的流程進行流轉。“驅趕”之前我們要賦予流程定義“生命”,換句話說就是以一些特性或者“某人”的名義啓動,每次啓動都爲一個單獨的流程實例

    當啓動一個流程實例之後,就需要“驅趕”,也就是向下執行

    工作流比較大的特點就應該是每一步和具體人員相關,或轉言之每一步都應該由指定的人或組處理,那麼相對於指定的人這一步工作爲任務

(大體相關類可見上一篇最後圖)


一:環境

數據庫,腳本就包含在我們的下載包中:jbpm-4.4\install\src\db

核心數據表:
jbpm4_deployment 流程定義的部署記錄
jbpm4_deployprop 已部署的流程定義的具體屬性
jbpm4_lob 流程定義的相關資源,包括jPDL XML、圖片、用戶代碼Java類等,以二進制格式統一存儲在此表中
jbpm4_job 異步活動或定時執行的Job記錄
jbpm4_variable 流程實例的變量
jbpm4_execution 流程實例及執行對象
jbpm4_swimlane 任務泳道。這屬於流程定義的數據
jbpm4_participation 任務參與者(任務的相關用戶,區別於任務的分配人)。這屬於流程實例的數據
jbpm4_task 流程實例的任務記錄

歷史數據表:
jbpm4_hist_procinst 保存歷史的流程實例記錄
jbpm4_hist_actinst 保存歷史的活動實例記錄
jbpm4_hist_task 保存歷史的任務實例記錄
jbpm4_hist_var 保存歷史的流程變量數據
jbpm4_hist_detail 保存流程實例、活動實例、任務實例運行過程中歷史明細數據,例如起止時間、平均處理時間、任務註釋等,爲效率分析等流程數據挖掘服務提供基礎數據支持。

身份認證數據表:
jbpm4_id_user 保存用戶記錄
jbpm4_id_membership 保存用戶和用戶組之間的關聯關係
jbpm4_id_group 保存用戶組記錄

引擎屬性數據表:
jbpm4_property 初始化設定的種子數據

配置文件:

一般情況下,你可以先拷入jbpm-4.4\examples\src下的5個文件到類路徑。

jbpm.cfg.xml:

在官方發佈包jbpm.jar的根路徑中包含了一些默認提供的配置文件。用戶可以選擇包含或排除某些功能,通過向jbpm.cfg.xml配置文件中導入選定的配置文件:

<jbpm-configuration>
  <!-- 流程引擎的主配置文件 -->
  <import resource="jbpm.default.cfg.xml" />
  <!-- 身份認證相關的配置文件 -->
  <import resource="jbpm.identity.cfg.xml" />
  <import resource="jbpm.jboss.idm.cfg.xml" />
  <!-- 基於JBoss應用服務器實現分佈式遠程調用的配置文件 -->
  <import resource="jbpm.jbossremote.cfg.xml" />
  <!-- Job執行器配置文件。用於配置異步活動和定時器Job的執行策略 -->
  <import resource="jbpm.jobexecutor.cfg.xml" />
  <!-- 任務聲明週期狀態定義的配置文件。默認的有開啓(open)狀態、掛起(suspended)狀態、取消(cancelled)狀態、完成(completed)狀態 -->
  <import resource="jbpm.task.lifecycle.cfg.xml" />
  <!-- Hibernate事務、JTA事務和Spring事務的配置文件 -->
  <import resource="jbpm.tx.hibernate.cfg.xml" />
  <import resource="jbpm.tx.jta.cfg.xml" />
  <import resource="jbpm.tx.spring.cfg.xml" />
  <!-- 流程變量數據類型映射的配置文件 -->
  <import resource="jbpm.variable.types.xml" />
  <!-- 基於jBPM4的IoC架構,將引擎組件綁定在運行環境中的配置文件(通過依賴注入) -->
  <import resource="jbpm.wire.bindings.xml" />
  <import resource="jbpm.jpdl.bindings.xml" />
  <!-- 工作日曆的配置文件 -->
  <import resource="jbpm.businesscalendar.cfg.xml" />
  <import resource="jbpm.jpdl.cfg.xml" />
  <import resource="jbpm.bpmn.cfg.xml" />

</jbpm-configuration>

jbpm.hibernate.cfg.xml:

hibernate配置文件,參照hibernate相關文章

jbpm.mail.properties、jbpm.mail.templates.xml:

郵件相關(如我們每一步操作,進行郵件提醒),在後面詳細介紹


二、“流程運轉”

(我們先學習一下流程運轉,因爲考慮到流程運轉非常簡單就幾個服務類,而且後面的流程定義測試需要用到,如果對上一章的helloworld進行了學習,學習流程運轉服務類應該會比較容易)

我們定義的流程,需要被實例化(或加載),因此我們要創建流程實例;
當流程實例在執行中時,我們要控制和監控流程,以確保業務流程執行在監控之中;
當流程實例執行完畢,jBPM4會將其歸檔到“歷史流程”中去,從而提高運行中流程實例的執行效率,而我們需要從歷史流程中進行數據分析以優化和重組業務。

以上這些功能的開發,都需要依賴jBPM4提供的Service API去幫助我們實現。包括:
·管理流程部署
·管理流程實例
·管理流程任務
·管理流程歷史

·其他

1、概覽 流程引擎API:

流程引擎對象 -- org.jbpm.api.ProcessEngine 是jBPM4所有Service API之源。
(ProcessEngine是由Configuration類構建的,根據配置產生)

ProcessEngine是線程安全的,所以我們可以全局共享一個。

//這種格式會默認從classpath根目錄下加載默認文件jbpm.cfg.xml
ProcessEngine processEngine = Configuration.getProcessEngine();

//從classpath下查找一個指定的文件
ProcessEngine processEngine = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();
除了setResource文件名稱外,還提供了其他方式的資源載入。

// 流程資源服務的接口。提供對流程定義的部署、查詢、刪除等操作
RepositoryService repositoryService = processEngine.getRepositoryService();

// 流程執行服務的接口。提供啓動流程實例、“執行”推進、設置流程變量等操作
ExecutionService executionService = processEngine.getExecutionService();

// 流程管理控制服務的接口。只提供異步工作相關執行和查詢操作。
ManagementService managementService = processEngine.getManagementService();

// 人工任務服務的接口。提供對任務的創建、提交、查詢、保存、刪除等操作
TaskService taskService = processEngine.getTaskService();

// 流程歷史服務的接口。提供對流程歷史庫中歷史流程實例、歷史活動實例等記錄的查詢操作。還提供諸如某個流程定義中所有活動的平均執行時間、某個流程定義中某個轉移經過的次數等數據分析服務。
HistoryService historyService = processEngine.getHistoryService();

// 身份認證服務的接口。提供對流程用戶、用戶組以及組成員關係的相關服務。
IdentityService identityService = processEngine.getIdentityService();

(這六種Service的獲取,ProcessEngine還提供了兩種通用獲取方式<T> T get(Class<T> paramClass)、Object get(String paramString))

2、流程定義管理:

RepositoryService提供了管理jBPM4發佈資源的所有接口。
添加:

// repositoryService獲取NewDeployment,通過NewDeployment進行addResourceFromXXX操作(可以以多種方式獲取資源),然後發佈。
// 發佈的文件爲我們定義的.jpdl.xml文件
String deployId = super.repositoryService.createDeployment().addResourceFromClasspath("leave.jpdl.xml")
		.deploy();
在部署過程中,流程引擎會把一個ID分配給流程定義,ID的格式爲{key}-{version}流程鍵和版本。
如果流程定義沒有指定key(鍵),key則會在流程名稱的基礎上自動生成。生成的key會把所有不是字母和數字的字符替換成下劃線,例如空格。
同一個流程名稱只能關聯一個key,反之亦然。

刪除:
可以通過RepositoryService提供的API刪除一個已經部署的流程定義,如果使用了數據庫,會從數據庫中徹底刪除。
super.repositoryService.deleteDeployment(deploymentId);
但是如果要刪除的流程定義還存在未完成流程實例的話,執行deleteDeployment方法會拋出異常。

如果希望級聯刪除一個已經發布的流程定義以及其所有產生的流程實例的話,可以:
repositoryService.deleteDeploymentCascade(deploymentId);

查詢已部署的流程定義:

List<Deployment> deployments = super.repositoryService.createDeploymentQuery().list();
for(Deployment deployment:deployments){
	System.out.println(deployment);
}

3、發起新的流程實例:

通過不同的方式發起新的流程實例。
比如我們發起了一個爲leave的.jpdl.xml文件。

// 會啓動key爲leave最新發布的版本
ProcessInstance processInstance = executionService.startProcessInstanceByKey("leave");

// 根據特定的流程定義版本發起流程實例
ProcessInstance processInstance = executionService.startProcessInstanceById("leave-" + departmentId);

發起流程標識鍵:
//我們可以獲取自動生成的id,這個id爲數據庫主鍵id。
ProcessInstance processInstance = executionService.startProcessInstanceByKey("leave");
processInstance.getId();

如果我們需要和其他系統相關聯,自己指定一個id,可以使用兩個參數的重載方法,第二個參數爲id。
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String processInstanceKey)
但是:一個標識鍵(業務鍵)必須在此流程定義所有版本的流程實例範圍內是唯一的。


4、爲發起實例賦予變量:
如果新的流程實例需要一些輸入參數啓動(或者此實例的啓動需要記錄一些參數),那麼可以將這些參數放在流程變量裏,然後在發起流程時傳入流程變量對象。
流程變量對象一般是一個Map:

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("name", "partner4java");
variables.put("age", 25);
ProcessInstance processInstance = executionService.startProcessInstan

根據ProcessInstance的id查詢相關信息:
Map<String, Object> map = executionService.getVariables(processInstance.getId(), executionService
.getVariableNames(processInstance.getId()));
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + ":" + map.get(key));
}


5、向下執行:
當流程進入一個state活動時,執行會在到達state的時候進入等待狀態 -- wait state,這是jBPM的一個重要概念,task等活動也會陷入等待狀態,支付到一個signal命令,才能進入下一個步驟的活動。
executionService的signalExecution*方法可以用來發出一個signal命令。

發出signal需要傳入executionId -- 實例id。
Execution executionInState = processInstance.findActiveExecutionIn("state1");
assertNotNull(executionInState);
executionService.signalExecutionById(executionInState.getId());


(那麼學到這裏,你是否想自己先試一下這些工具類再繼續學習,跳轉到第三部分“描述工作流程”,學習流程定義標籤中的start、state、end三個標籤,從“可以這麼說” -到- “應該爲jpdl和png的zip包”,還有不要說很難,請動手,動手後再說容不容易理解)


6、任務服務

(可能你會有遺憾,任務的用戶是如何指定的,在我們“描述工作流程”中有介紹)

TaskService的主要目的是提供對任務列表的訪問操作,這裏的任務是指jBPM4 task活動的人機交互業務。
查找某用戶的待辦任務列表:

List<Task> tasks = taskService.findPersonalTasks("partner4java");

發起的任務通常會攜帶一些參數數據,例如在界面中提交的審批相關數據:

//獲取所有variable的名稱
Set<String> variableNames = taskService.getVariableNames(task.getId());
//獲取所有variables數據
Map<String, Object> variables = taskService.getVariables(task.getId(), variableNames);
for(String name:variableNames){
	System.out.println("name:" + name + " value:" + variables.get(name));
}
TaskService也可以用來完成任務:

//根據指定的任務id完成任務(前提是你的路徑“無需選擇”)
completeTask(String taskId)

//根據指定的任務ID,同時傳入變量
completeTask(String taskId, Map<String, ?> variables)

//指定下一步的轉移路徑,完成任務
completeTask(String taskId, String outcome)

//指定下一步的轉移路徑,並傳入變量
completeTask(String taskId, String outcome, Map<String, ?> variables)
(如果傳入變量,只會覆蓋上一步傳入變量,不會刪除不衝突變量)

Demo:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("time", new Date());
variables.put("owner", "partner4java23423424");
taskService.completeTask(task.getId(), "user_to_manager_approval", variables);
任務可以擁有多個候選人,候選人可以是單個用戶也可以是用戶組。
用戶可以接收候選人是自己的任務,接收任務的意思是用戶會被流程引擎設置爲任務的分配者,接口任務是個“排他”操作,因此在任務被“接收 -- 分配”之後,其他的用戶就不能接收並辦理此任務了。

對於有候選人,但是還沒有被分配的任務,唯一應該暴漏給用戶的操作就是“接收任務”。

7、其他相關服務

歷史服務:
在流程實例執行的過程中,會不斷觸發事件,通過這些事件,已完成流程實例的歷史信息會被收集到流程歷史數據表中。

查詢某一流程定義的所有歷史流程實例:
historyService.createHistoryProcessInstanceQuery()
.processDefinitionId("leave-" + deploymentId).orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)
.list()

avgDurationPerActivity -- 獲取指定流程定義中每個活動的平均執行時間

choiceDistribution -- 獲取指定活動定義每個轉移的經過次數

管理服務:

ManagementService即管理服務,通常用來管理Job(異步工作)。
ManagementService的功能在諸如jBPM4 Web控制檯等客戶端應用上被調用。

ManagementService只有三個方法。
主要是創建JobQuery,用於Job查詢。

查詢服務:

從jBPM4開始,流程查詢系統由一組新的API來支持。
你如果對錶結構瞭解,也可以自己通過Hibernate的HQL進行查詢。

流程實例或者任務等查詢都是基於對應的service,如流程實例查詢:
executionService
//獲取流程實例查詢對象
.createProcessInstanceQuery()
//指定實例id
.processInstanceId(mProcessInstance.getId())
//或者指定流程定義id
//.processDefinitionId("leave-4")
//.page(0, 50)
//查詢執行,獲取結果集列表
.list();

三:“描述工作流程”

可以這麼說,jPDL(jBPM Process Define Language,jBPM流程定義語言)是jBPM4獨有的、最重要的“資產”。
jPDL的設計目標是儘量的精簡和儘可能地對開發者友好,即提供所有您期望從業務流程定義語言中得到的同時,也可以很“簡練”地描述業務流程的定義和圖形結構,最終使得業務分析師和流程開發者能使用“同一種語言說話”、極大地減少了他們之間的交流障礙。

通用的活動屬性:
name -- 活動的名稱(必須的)
transition -- 定義活動的流出轉移

1、process(流程 -- 根標籤)
在jPDL中process元素是是每個流程定義的頂級元素,即任何流程定義都必須以如下形式開始和結束:
<process>
...
</process>
屬性:
name  必須  在面對最終用戶展示和交互時,作爲流程顯示的標籤
key  可選 用來標識不同的流程定義。擁有同一個key的流程定義允許有不同的version。對於所有已發佈的統一流程定義的各個版本來說,“key:name”組合必須是完全一樣的。如果省略,key會根據name生成,name中非字母以及非數字的字符會被替換爲下畫線。
version 可選 標識同一流程定義的不同版本。 同一流程定義(即二者“key:name”相同),後部署的會比選部署的version加1如果是第一次部署,version從1開始。

子元素:
description 描述流程的文案
activities 活動。流程定義中會有很多不同的活動,例如start、state、decision等,但至少要有1個是start活動

流轉控制活動
流轉控制完全可以組裝出一條完整的流程定義,實現各種基本的流程流轉控制場景。
·start 開始活動
·state 狀態活動
·end 結束活動
·decision 判斷活動
·fork-join 分支/聚合活動
·task 人工任務活動
·sub-process 子流程活動
·custom 自定義活動

2、start(開始活動):
start活動的意義在於指明一個流程的實例應該從哪裏開始發起,即流程的入口。
在一個流程定義裏必須擁有一個start活動。
start活動必須有一個轉移(transition),這個轉移會在流程通過start活動的時候執行。
(注意:一個流程定義有且只能有一個start活動 -- 在group中的start活動除外)
屬性:
·name start活動的名字,在無流入轉移指向start活動的情況下,name屬性可以不指定
·g “座標”用來標識位置
元素:
·transition 流出轉移,指向流程的下一個步驟

3、state(狀態活動)
當需要使業務流程受到某些特定的外部干預(處理)後再繼續進行,而在外部干預(處理)之前流程"陷入"一箇中斷等待的狀態,需要的就是這麼一個state活動來實現。
當流程運行到state活動時,會自動陷入等到狀態(waitting state),也就是說流程引擎在收到外部觸發信號之前會一直使流程實例在此state活動等待。(從這個方面來說,task活動可以說是一種特殊化的state活動)
state活動除了最基本的name屬性和transition等元素外,沒有獨特的屬性或元素。

state_hello.jpdl.xml:

<process name="state_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 流程開始後轉移到 a state -->
	<start g="186,9,80,40">
		<transition to="a"/>
	</start>
	<!-- a state轉移到b state -->
	<state g="175,82,80,40" name="a">
		<transition to="b"/>
	</state>
	<!-- b state轉移到end -->
	<state g="177,150,80,40" name="b">
		<transition to="c"/>
	</state>
	<end g="191,215,80,40" name="c"/>
</process>
運行代碼:
ProcessEngine processEngine = Configuration.getProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
String deploymentId = repositoryService.createDeployment().addResourceFromClasspath("state_hello.jpdl.xml")
		.deploy();

ExecutionService executionService = processEngine.getExecutionService();
ProcessInstance processInstance = executionService.startProcessInstanceById(repositoryService
		.createProcessDefinitionQuery().deploymentId(deploymentId).uniqueResult().getId());

Execution executionInA = processInstance.findActiveExecutionIn("a");
//斷言流程實例在state a
Assert.assertNotNull(executionInA);

//觸發外部執行信號,行下執行
processInstance = executionService.signalExecutionById(executionInA.getId());

Execution executionInB = processInstance.findActiveExecutionIn("b");
//斷言流程實例在state b
Assert.assertNotNull(executionInB);
在state活動裏可以定義多個transition元素,我們通過信號觸發指定轉移路徑的名稱,就可以選擇其中的一個transition通過。
state_hello2.jpdl.xml:
<process name="state_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 流程開始後轉移到 a state -->
	<start g="187,5,80,40">
		<transition to="a"/>
	</start>
	<!-- a state轉移到b state -->
	<state g="175,82,80,40" name="a">
		<transition g="-30,-22" name="to_b" to="b"/>
		<transition g="-28,-22" name="to_c" to="c"/>
	</state>
	<!-- b state轉移到end -->
	<state g="102,180,80,40" name="b">
		<transition to="d"/>
	</state>
	<state g="264,175,74,41" name="c">
		<transition to="d"/>
	</state>
	<end g="201,273,80,40" name="d"/>
</process>
運行代碼:
Execution executionInA = processInstance.findActiveExecutionIn("a");
// 斷言流程實例在state a
Assert.assertNotNull(executionInA);

// 觸發外部執行信號,行下執行
processInstance = executionService.signalExecutionById(executionInA.getId(), "to_b");

Execution executionInB = processInstance.findActiveExecutionIn("b");
// 斷言流程實例在state b
Assert.assertNotNull(executionInB);
// 斷言流程實例流向了預期的活動
Assert.assertTrue(processInstance.isActive("b"));


4、end(結束活動)
end活動會終結流程。
默認情況下,當流程實例運行到end活動會結束。但是在到達end活動的流程示例中仍然活躍的流程活動將會被保留繼續執行。

在jBPM4中,流程定義允許多個end活動,如果您需要:執行到特定的end活動時,流程實例會被完全結束,即其他仍然活躍的併發活動也需要被結束,那麼,可以設置end活動的屬性“ends="execution"”來實現這種需求。

在實際應用中,我們經常需要知道流程實例是以何種“狀態”結束的。
爲了表明流程實例的結束狀態,可以利用end活動的state屬性標識,或者直接利用jBPM4提供的特殊end活動:end-cancel活動和end-error活動。

end活動的state屬性:
state 用來自定義流程實例的狀態。可以通過API:processInstance.getState()

end_hello.jpdl.xml:

<process name="end_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="195,14,80,40">
		<transition to="m_state"/>
	</start>
	
	<state name="m_state" g="189,84,80,40">
		<transition name="to_end1" to="end1" g="-51,-22"/>
		<transition name="to_end2" to="end2" g="-51,-22"/>
		<transition name="to_end3" to="end3" g="256,117:21,34"/>
	</state>
	
	<end name="end1" state="ended1" g="87,243,80,40"/>
	<end name="end2" state="ended2" g="240,247,80,40"/>
	<end name="end3" state="ended3" g="379,253,80,40"/>
</process>
執行代碼:
// 啓動實例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("end_hello");

// 獲取m_state
Execution state = processInstance.findActiveExecutionIn("m_state");

// 向下執行,並指定路徑“to_end2”
processInstance = executionService.signalExecutionById(state.getId(), "to_end2");

// 判斷活動是否已經結束
Assert.assertTrue(processInstance.isEnded());
// 獲取end的state值
Assert.assertEquals("ended2", processInstance.getState());

5、decision(判斷活動)

根據條件在多個流轉路徑中選擇其一通過,也就是做一個決定性的判斷,這時候使用decision活動是個正確的選擇。
decision活動可以擁有多個流出轉移,當流程實例到達decision活動時,會根據最先匹配成功的一個條件自動地通過相應的流出轉移。

·使用decision活動的condition元素
decision活動中會運行並判斷其沒一個transition元素裏的流轉條件 -- 流轉條件由condition元素表示。
當遇到一個transition的condition值爲true或者一個沒有設置condition的transition,那麼流程就立即轉向這個condition。

decision活動的condition元素屬性:
expr(必需) 描述轉移條件的表達式
lang(可選) 指定轉移條件表達式的語言類型(默認爲EL表達式語言)

decision_hello.jpdl.xml:

<process name="decision_hello" xmlns="http://jbpm.org/4.4/jpdl">
   <start g="471,39,48,48" name="start1">
   	<transition to="a"/>
   </start>
   
   <state name="a" g="459,103,80,40">
   		<transition to="b"/>
   </state>
   
   <decision name="b" g="474,183,80,40">
   		<!-- 以下是兩個流轉條件表達式: -->
   		<transition to="c">
   			<!-- 變量name等於c -->
   			<condition expr="#{name == 'c'}"/>
   		</transition>
   		<transition to="d">
   			<!-- 變量name等於b -->
   			<condition expr="#{name == 'b'}"/>
   		</transition>
   		<!-- 無條件轉移 -->
   		<transition to="e"/>
   </decision>
   
   <state name="c" g="340,277,80,40"/>
   <state name="d" g="469,275,80,40"/>
   <state name="e" g="636,274,80,40"/>
</process>
運行代碼:
// 獲取state a的執行實例
Execution executionInA = processInstance.findActiveExecutionIn("a");
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("name", "d");
// 攜帶參數,執行到下一步(判斷流轉)
processInstance = executionService.signalExecutionById(executionInA.getId(), variables);

// 斷言是否流向了我們指定的state
Assert.assertFalse(processInstance.isActive("c"));
Assert.assertTrue(processInstance.isActive("d"));
·使用decision活動的expr屬性
您可以利用decision活動本身具有的expr(表達式)屬性來判斷流程的轉向。
decision活動的expr屬性值可直接返回字符串類型的流出轉移名稱,指定需要轉移的路徑。


decision活動的屬性:
expr(必須) 描述轉移名稱的表達式
lang(可選) 指定轉移條件表達式語言的類型(EL表達式語言)

decision_hello2.jpdl.xml:

<process name="decision_hello2" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="476,1,80,40" name="start">
		<transition to="first_state" />
	</start>
	<state g="457,74,80,40" name="first_state">
		<transition to="choose_line" />
	</state>
	<!-- "#{line}"即爲判斷表達式,這裏會返回變量line的字符串,爲我們轉移名稱 -->
	<decision expr="#{line}" g="478,137,80,40" name="choose_line">
		<!-- line值爲to_a時的轉移 -->
		<transition g="-29,-22" name="to_a" to="a" />
		<!-- line值爲to_b時的轉移 -->
		<transition g="-30,-22" name="to_b" to="b" />
		<!-- line值爲to_c時的轉移 -->
		<transition g="-28,-22" name="to_c" to="c" />
	</decision>
	<state g="283,251,80,40" name="a">
		<transition to="end" />
	</state>
	<state g="464,262,80,40" name="b">
		<transition to="end" />
	</state>
	<state g="677,254,80,40" name="c">
		<transition to="end" />
	</state>
	<end g="484,407,80,40" name="end" />
</process>
運行代碼:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("line", "to_c");
// 攜帶參數啓動實例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("decision_hello2", variables);

Execution execution = processInstance.findActiveExecutionIn("first_state");
// 繼續向下執行
processInstance = executionService.signalExecutionById(execution.getId());

// 判斷是否立傳到了state c
Assert.assertTrue(processInstance.isActive("c"));
·使用decision活動的handler元素
如果您需要在判斷流程時計算大量、複雜的業務邏輯,那麼,自己實現判斷處理接口,即通過decision handler的方式來實現。

首先,必須實現DecisionHandler接口,將流轉判斷的決定權委派給這個實現類。
/** interface for supplying user programmed decisions.
 * 
 * @author Tom Baeyens */
public interface DecisionHandler extends Serializable {

  //接口唯一的方法,提供流程實例的執行上下文(execution)作爲參數,需要返回字符串型的轉移名稱
  /** the name of the selected outgoing transition */ 
  String decide(OpenExecution execution);
}
這個handler需要作爲decision活動的子元素被配置。
decision_hello3.jpdl.xml:
<process name="decision_hello3" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="476,1,80,40" name="start">
		<transition to="first_state" />
	</start>
	<state g="457,74,80,40" name="first_state">
		<transition to="choose_line" />
	</state>
	<decision g="478,137,80,40" name="choose_line">
		<handler class="com.partner4java.demo.decision.MyDecisionHandler"/>
		<!-- line值爲to_a時的轉移 -->
		<transition g="-29,-22" name="to_a" to="a" />
		<!-- line值爲to_b時的轉移 -->
		<transition g="-30,-22" name="to_b" to="b" />
		<!-- line值爲to_c時的轉移 -->
		<transition g="-28,-22" name="to_c" to="c" />
	</decision>
	<state g="283,251,80,40" name="a">
		<transition to="end" />
	</state>
	<state g="464,262,80,40" name="b">
		<transition to="end" />
	</state>
	<state g="677,254,80,40" name="c">
		<transition to="end" />
	</state>
	<end g="484,407,80,40" name="end" />
</process>

public class MyDecisionHandler implements DecisionHandler {
	private static final long serialVersionUID = -7062576970169892452L;

	@Override
	public String decide(OpenExecution paramOpenExecution) {
		String line = (String) paramOpenExecution.getVariable("line");
		if (line != null && line.length() > 0) {
			switch (line) {
			case "a":
				return "to_a";
			case "b":
				return "to_b";
			case "c":
				return "to_c";
			default:
				break;
			}
		}
		return null;
	}

}

運行代碼:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("line", "c");
// 攜帶參數啓動實例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("decision_hello3", variables);

Execution execution = processInstance.findActiveExecutionIn("first_state");
// 繼續向下執行
processInstance = executionService.signalExecutionById(execution.getId());

// 判斷是否立傳到了state c
Assert.assertTrue(processInstance.isActive("c"));

6、fork-join(分支/聚合活動)

當我們需要流程併發(concurrency)執行的時候,就需要使用到fork-join活動的組合,fork活動可以使流程在一條主幹上出現並行的分支,join活動則可以使流程的並行分支聚合成一條主幹。

fork活動具有jBPM活動的最基本特性,即具有1個name屬性和n個流出轉移元素。

join活動的獨特屬性:
multiplicity(可選) 流程執行中,當指定的流入轉移數量(multiplicity)到達join活動後,流程即會聚合,沿着join活動的唯一流出轉移繼續執行流轉。其他未到達的流入轉移則會忽略,從而實現按流入轉移數量聚合的場景,因此,multiplicity屬性不應該大於join活動定義流入轉移數量。(默認數量爲流入轉移數)
lockmode(可選) 指定hibernate的數據鎖模式。因爲join活動支持併發自動活動事務,因此需要在join活動上防止兩個還沒有聚合的同步事務活動互相鎖定對象的事務資源,從而導致死鎖。(值爲字符串枚舉:none、read、upgrade、upgrade_nowait、wirte,默認值upgrade)

fork_join_test.jpdl.xml:

<process name="fork_join_test" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="514,2,80,40">
		<transition to="m_fork" />
	</start>

	<!-- 流轉在這產生了四個並行的分支 -->
	<fork name="m_fork" g="522,79,80,40">
		<transition to="a_state" />
		<transition to="b_state" />
		<transition to="c_state" />
		<transition to="d_state" />
	</fork>

	<state name="a_state" g="501,219,80,40">
		<transition to="m_join" />
	</state>
	<state name="b_state" g="642,213,80,40">
		<transition to="m_join" />
	</state>
	<state name="c_state" g="798,215,80,40">
		<transition to="m_join" />
	</state>
	
	<!-- fork的分支d_state,沒有再進行聚合,而是直接走到end,也就是說本路線會直接引起活動結束 -->
	<state name="d_state" g="302,307,80,40">
		<transition to="end" />
	</state>

	<!-- 分支活動:a_state、b_state、c_state在此聚合 -->
	<join name="m_join" g="656,322,80,40" multiplicity="2">
		<transition to="end" />
	</join>

	<end name="end" g="549,445,80,40" />
</process>

運行代碼:
Set<String> names = new HashSet<String>();
names.add("a_state");
names.add("b_state");
names.add("c_state");
names.add("d_state");

// 判斷,目前是否定義的4個分支都爲活動狀態
Set<String> activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);

// 使a_state走向下一步
Execution executionA = processInstance.findActiveExecutionIn("a_state");
processInstance = executionService.signalExecutionById(executionA.getId());

// 移除已處理的活動
names.remove("a_state");

// m_join活動還沒有開始,因爲我們設置了multiplicity="2",還需要一個活動走到聚合處
activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);

// 使b_state走向下一步
Execution executionB = processInstance.findActiveExecutionIn("b_state");
processInstance = executionService.signalExecutionById(executionB.getId());

// m_join活動已經執行,我們設置了multiplicity="2",b_state執行後就已經有兩個活動到達聚合處,聚合join執行執行到了end,所以,目前已經沒有運行狀態的活動
names = new HashSet<String>();

activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);
// 活動是否已經結束
Assert.assertTrue(processInstance.isEnded());

執行到這裏,你可能會有疑問,每一次我們判斷執行到某個點太麻煩了吧?能不能圖形的給我們展示出來呢?

參照下一章“《partner4java 講述jBPM4》之第三步:圖形化查看執行位置”

如果暫時看不懂,可以部署到您的web容器,以地址訪問傳入相應id的形式查看當前流程執行情況(注意:如果使用圖形查看,部署的應該爲jpdl和png的zip包)。

(如果剛纔是從學習第二部分“流程運轉”,向下執行後,跳轉學習到這裏,那麼現在您再跳回去吧,繼續接着從“6、任務服務”學習)


7、task(人工任務活動)

在jBPM中,task活動一般用來處理涉及人機交互的活動。
task活動的功能在jBPM乃至整個工作流的應用中都具有極其重要的意義,因爲處理人工任務、電子表單是工作流應用中最繁瑣和細緻的工作。

·關於任務的分配者
我們可以使用task活動的assignee屬性(分配者屬性)簡單的將一個任務分配給指定的用戶。
屬性:
assignee(可選) 被分配到任務的用戶ID

task_hello.jpdl.xml:

<process name="task_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="454,53,80,40" name="start">
		<transition to="m_task"/>
	</start>
	<!-- EL表達式的值標識分配者ID -->
	<task assignee="#{username}" g="446,191,80,40" name="m_task">
		<transition to="end"/>
	</task>
	<end g="467,298,80,40" name="end"/>
</process>

執行代碼:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("username", "partner4java");
ProcessInstance processInstance = executionService.startProcessInstanceByKey("task_hello", variables);

// 獲取用戶的任務列表
List<Task> tasks = taskService.findPersonalTasks("partner4java");
System.out.println(tasks);
·關於任務的候選者
jBPM支持將任務分配給一組候選用戶,組中的一個用戶可以接受這個任務並完成,這就是任務的候選者機制。
task活動的候選者屬性:
candidate-groups(可選) 使用逗號分隔的用戶組ID列表。所有組的用戶將會成爲任務的候選者。
candidate-users(可選) 使用逗號分隔的用戶ID列表。所有列表中的用戶將會成爲任務的候選者。

task_hello2.jpdl.xml:
<process name="task_hello2" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="337,27,80,40">
		<transition to="m_task"/>
	</start>
	<task candidate-groups="admin_group" name="m_task" g="324,136,80,40">
		<transition to="end"/>
	</task>
	<end name="end" g="342,257,80,40"/>
</process>

執行代碼:
String deploymentId = repositoryService.createDeployment()
				.addResourceFromClasspath("jpdl/task_hello2.jpdl.xml").deploy();

/** *************************開啓流程,目前還沒有人接受任務**************************** */
ProcessInstance processInstance = executionService.startProcessInstanceByKey("task_hello2");
// 獲取user partner4java的任務
List<Task> tasks1 = taskService.findGroupTasks("partner4java");
// 目前沒有數據
Assert.assertEquals(tasks1.size(), 0);

// 獲取user helloworld的任務
List<Task> tasks2 = taskService.findGroupTasks("helloworld");
// 目前沒有數據
Assert.assertEquals(tasks2.size(), 0);

/** *************************通過identityService身份認證服務添加賬戶 *****************/
// 首先創建組admin_group
identityService.createGroup("admin_group");
// 創建用戶
identityService.createUser("partner4java", "changlong", "wang");
// 將用戶添加到組
identityService.createMembership("partner4java", "admin_group");

// 創建用戶
identityService.createUser("helloworld", "changlong", "wang");
// 將用戶添加到組
identityService.createMembership("helloworld", "admin_group");

/** ************************* 現在你會發現兩個任務都有可以接受的任務 *****************/
tasks1 = taskService.findGroupTasks("partner4java");
// 現在應該存在一條可以處理的任務
Assert.assertEquals(tasks1.size(), 1);

tasks2 = taskService.findGroupTasks("helloworld");
// 現在應該存在一條可以處理的任務
Assert.assertEquals(tasks2.size(), 1);

/** ************************* 讓一個用戶接受任務 ********************************/
for (Task task : tasks2) {
	System.out.println(task + ":" + task.getProgress());
	// 接受任務:當用戶partner4java接受了任務之後,partner4java就會由任務的候選者變爲任務的分配者。
	// 同時,此任務會從所有候選者的任務列表中消失。它會出現在partner4java的已分配任務列表中
	taskService.takeTask(task.getId(), "partner4java");
}

/** *****接受了任務之後,任務就不會存在於用戶組中,而是轉移到了具體的用戶任務列表中 ***/
tasks1 = taskService.findGroupTasks("partner4java");
Assert.assertEquals(tasks1.size(), 0);
tasks2 = taskService.findGroupTasks("helloworld");
Assert.assertEquals(tasks2.size(), 0);

List<Task> mTasks1 = taskService.findPersonalTasks("partner4java");
Assert.assertEquals(mTasks1.size(), 1);
List<Task> mTasks2 = taskService.findPersonalTasks("helloworld");
Assert.assertEquals(mTasks2.size(), 0);

for (Task task : mTasks1) {
	//完成任務
	taskService.completeTask(task.getId());
}

identityService.deleteUser("partner4java");
identityService.deleteUser("helloworld");
identityService.deleteGroup("admin_group");
·關於任務泳道
在實際的業務應用中,經常會遇到這樣一種場景:流程定義中的多個任務需要被分配或候選給同一羣用戶。
那麼我們可以統一將這個“同一羣用戶”定義爲“一個泳道”。
泳道作爲流程定義的直接子元素被整個流程定義所見,因此同一流程定義中的任何一個任務都可以引用泳道。
屬於同一個泳道的任務將會被分配或者候選給這個泳道中的所有用戶。
泳道的概念也可以理解爲流程定義的“全局用戶組”。泳道也可以被當做一個流程規則。

task的泳道屬性:
swimlane(可選) 引用一個在流程中定義的泳道
(swimlane屬性是任務活動對泳道的引用,泳道本身是作爲process流程定義的子元素被定義在整個流程範圍內的)

泳道(swimlane)元素的屬性:
name(必須) 這個泳道名稱將在任務的泳道屬性中引用
assignee(可選) 引用的單個用戶ID
candidate-groups(可選) 使用逗號分隔的用戶組ID列表。此組中的所有用戶將作爲引用此泳道任務的候選人。
candidate-users(可選) 使用逗號分隔的用戶ID列表。此列表中的所有用戶將作爲引用此泳道任務的候選人。

swimlane_hello.jpdl.xml:
<process name="swimlane_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 在這裏定義泳道,泳道是爲流程定義的子元素 -->
	<swimlane candidate-groups="admin_group" name="m_swimlane"/>
	
	<start name="start" g="511,58,80,40">
		<transition to="m_task"/>
	</start>
	<!-- 以下兩個任務的分派工作,都交給上面定義的泳道完成 -->
	<task name="m_task" swimlane="m_swimlane" g="497,180,80,40">
		<transition to="n_task"/>
	</task>
	<task name="n_task" swimlane="m_swimlane" g="505,280,80,40">
		<transition to="end"/>
	</task>
	
	<end name="end" g="528,431,80,40"/>
</process>
執行代碼:
通過打印可以總結出:
在啓動之後,swimlane的candidate-groups組包含的所有用戶都可以通過findGroupTasks查找到可被接受任務。
當某一用戶接受任務之後,findGroupTasks都查找不到任務;只能通過findPersonalTasks查找已經接受的任務。
當本次任務完成之後,下一步任務如果也指定了swimlane,則會自動選擇上一步接受任務的用戶爲代辦用戶。
StringBuilder builder = new StringBuilder();
String deploymentId = repositoryService.createDeployment().addResourceFromClasspath(
		"jpdl/swimlane_hello.jpdl.xml").deploy();

// 添加用戶
testAddUser();

ProcessInstance processInstance = executionService.startProcessInstanceByKey("swimlane_hello");
builder.append("啓動之後(findGroupTasks):");
List<Task> tasks1 = taskService.findGroupTasks("partner4java");
builder.append("partner4java獲取到的任務數:" + tasks1.size() + ":" + tasks1);
List<Task> tasks2 = taskService.findGroupTasks("helloworld");
builder.append("helloworld獲取到的任務數:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks2) {
	// 接受任務
	taskService.takeTask(task.getId(), "partner4java");
}

builder.append("\n partner4java接受任務之後(findGroupTasks):");
tasks1 = taskService.findGroupTasks("partner4java");
builder.append("partner4java獲取到的任務數:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findGroupTasks("helloworld");
builder.append("helloworld獲取到的任務數:" + tasks2.size() + ":" + tasks2);

builder.append("\n partner4java接受任務之後(findPersonalTasks):");
tasks1 = taskService.findPersonalTasks("partner4java");
builder.append("partner4java獲取到的任務數:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findPersonalTasks("helloworld");
builder.append("helloworld獲取到的任務數:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks1) {
	// 完成任務
	taskService.completeTask(task.getId());
}

builder.append("\n partner4java完成任務之後(findPersonalTasks):");
tasks1 = taskService.findPersonalTasks("partner4java");
builder.append("partner4java獲取到的任務數:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findPersonalTasks("helloworld");
builder.append("helloworld獲取到的任務數:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks1) {
	// 完成任務
	taskService.completeTask(task.getId());
}

// 刪除用戶
testDeleteUser();

System.out.println(builder.toString());
// 啓動之後(findGroupTasks):partner4java獲取到的任務數:1:[Task(m_task)]helloworld獲取到的任務數:1:[Task(m_task)]
// partner4java接受任務之後(findGroupTasks):partner4java獲取到的任務數:0:[]helloworld獲取到的任務數:0:[]
// partner4java接受任務之後(findPersonalTasks):partner4java獲取到的任務數:1:[Task(m_task)]helloworld獲取到的任務數:0:[]
// partner4java完成任務之後(findPersonalTasks):partner4java獲取到的任務數:1:[Task(n_task)]helloworld獲取到的任務數:0:[]
·關於任務變量
任務可以讀取、更新流程變量。任務還可以定義任務自由的變量,即任務變量。
一般來說,任務變量的主要作用是作爲任務表單的數據容器 -- 任務表單負責展示來自任務和流程的變量數據;
同時用戶通過任務表單錄入的數據則會被設置爲任務變量,任務變量根據需要也可以被輸出成爲流程變量。

·關於任務提醒郵件
爲任務的分配者提供電子郵件提醒,包括:
當一個任務出現在某個任務列表中時立即提醒;
指定時間間隔進行反覆提醒。

電子郵件的內容是根據一個模板生成出來的,此模板默認使用jBPM內置的,也可以進行修改,詳見jbpm.mail.templates.xml.(後面我們會具體介紹)

任務活動的電子郵件相關元素:
notification 當一個任務被分配的時候立即發送一封提醒郵件。如果沒有指定模板,郵件會使用默認的jbpm.mail.templates.xml
reminder 根據指定的時間間隔發送提醒郵件。如果沒有指定模板,郵件會使用默認的模板

notification元素的屬性:
continue 以同步、同步還是獨佔模式發送notification提醒郵件(字符串枚舉sync、async、exclusive)

reminder元素的屬性:
duedate(必須) reminder提醒電子郵件在任務產生後延遲多少時間發送(延遲時間 可包含表達式的字符串)
repeat reminder提醒電子郵件每間隔多少時間再發送一次,直到任務被辦理(間隔時間 可包含表達式的字符串)
continue 以同步、同步還是獨佔模式發送notification提醒郵件(字符串枚舉sync、async、exclusive)
<process name="email_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="537,43,80,40">
		<transition to="m_task"/>
	</start>
	<task name="m_task" g="534,157,80,40" assignee="partner4java">
		<!-- 這標識在任務產生後,立即以同步的方式發送提醒郵件 -->
		<notification/>
		<!-- 這標識在任務產生2天后,開始按發送提醒郵件,如果任務得不到處理,每隔1天再提醒一次 -->
		<reminder duedate="2 days" repeat="1 day"/>
		<transition to="end"/>
	</task>
	<end name="end" g="569,306,80,40"/>
</process>

8、sub-process(子流程活動)

當我們的流程複雜到一定程度的時候,就需要按照一定規則把業務拆分成若干子流程,這樣業務模塊之間才能明晰易於劃分。
有時候,爲了方便想對獨立的流程之間的拼裝、重組,劃分出子流程管理也是明智的選擇。

jBPM4提供了sub-process -- 子流程活動,這允許您在“主幹流程”定義中調用其他的流程定義,從而“組裝”您的流程定義。
在運行到子流程活動時,工作流引擎將創建一個子流程實例,然後等待直到其完成,當子流程實例完成後,流程就會流向下一步。

sub-process活動的屬性:
sub-process-id 流程定義的ID標識。可以通過一個流程的ID去引用此流程定義的指定版本。
sub-process-key 流程key標識。通過key去引用流程定義,意味着引用了該流程定義的最新版本。注意,該流程定義的最新版本會在每次活動室裏執行時計算得出。(sub-process-key和sub-process-id,必選其一)
outcome 當子流程活動執行結束時執行的表達式。表達式值用來匹配流出轉移中的outcome-value元素值,起到選擇sub-process活動下一步流向的作用。

sub-process活動的元素:
parameter-in 子流程輸入參數。即聲明一個變量,在創建子流程實例時傳入。
parameter-out 子流程輸出參數。即聲明一個變量,在子流程實例結束時,返回父流程實例。

sub-process的parameter-in -- 子流程活動輸入元素的屬性:
subvar(必需) 被賦值的子流程變量的名稱
var 從父流程環境中輸入的變量名稱
expr 此表達式在父流程環境中解析,結果值會被輸入到對應的子流程變量中(var和expr二者必須指定一個)
lang 表達式使用的腳本語言(EL表達式)

sub-process的parameter-out -- 子流程活動輸出元素的屬性:
var(必需) 輸出的目標 -- 父流程中的變量名稱
subvar 子流程中需要被輸出的變量名稱
expr 此表達式在子流程環境中解析,結果會被傳入到對應的父流程變量中。(subvar和expr二者必須制定一個)
lang 表達式使用的腳本語言(EL表達式)

sub-process活動的outcome元素必須有與之相呼應的outcome-value元素,這個outcome-value元素被定義在子流程活動的流出轉移(transition)中。

sub-process transition的outcome-value元素:
outcome-value 是一個值表達式。子流程活動結束時,如果某轉移的outcome-value值與子流程的outcome值匹配,那麼,父流程的下一步將會通過此轉移。注意:這個outcome-value值是由一個子元素定義的。

sub_process_main_hello.jpdl.xml:

<process name="sub_process_main_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="513,10,80,40">
		<transition to="m_sub_process" />
	</start>

	<!-- 引用標識爲sub_process_ch_hello的子流程定義 -->
	<!-- 在這裏定義了一個outcome屬性,它引用變量result -->
	<sub-process name="m_sub_process" outcome="#{line}"
		sub-process-key="sub_process_ch_hello" g="502,105,80,40">
		<!-- 父流程將變量assignee_id輸入子流程,對應的子流程變量名稱爲userid -->
		<parameter-in subvar="userid" var="assignee_id" />
		<parameter-in subvar="text" var="context" />
		<!-- 子流程將變量result返回父流程,對應的父流程變量名爲line -->
		<parameter-out subvar="result" var="line" />
		<!-- 在這個流出轉移中,定義了與outcome屬性呼應的outcome-value值。 即如果outcome值等於‘line1’,則子流程結束後,父流程會通過名稱爲‘m_line’的轉移,繼續執行, 
			而line2轉移則會被略過不執行。 -->
		<transition name="m_line" to="m_task1" g="-41,-22">
			<outcome-value>
				<string value="line1" />
			</outcome-value>
		</transition>
		<transition name="line2" to="m_task2" g="-32,-22" />
	</sub-process>
	<task name="m_task1" g="411,227,80,40" assignee="#{assignee_id}">
		<transition to="end" />
	</task>
	<task name="m_task2" g="621,221,80,40" assignee="#{assignee_id}">
		<transition to="end" />
	</task>
	<end name="end" g="538,342,80,40" />
</process>

sub_process_ch_hello.jpdl.xml:
<process name="sub_process_ch_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="280,29,80,40">
		<transition to="to_task" />
	</start>
	<!-- 子流程中的一個任務,分配給userid變量用戶。assignee從個人任務列表中拿到任務後,可以從任務中獲取子流程變量 -->
	<task name="to_task" g="270,128,80,40" assignee="#{userid}">
		<transition to="end" />
	</task>
	<!-- 子流程在這裏結束。將哪些子流程變量返回父流程由調用它的父流程決定 -->
	<end name="end" g="284,262,80,40" />
</process>

執行代碼:
// 發佈
repositoryService.createDeployment().addResourceFromClasspath("jpdl/sub_process_main_hello.jpdl.xml").deploy();
repositoryService.createDeployment().addResourceFromClasspath("jpdl/sub_process_ch_hello.jpdl.xml").deploy();

// 創建參數
Map<String, Object> variables = new HashMap<String, Object>();
// 用於子流程和主流程的任務所有者定義
variables.put("assignee_id", PARTNER4JAVA);
// 從主流程攜帶給子流程
variables.put("context", "I'm helloworld!");
ProcessInstance processInstance = executionService.startProcessInstanceByKey("sub_process_main_hello",
		variables);

// 獲取用戶任務,這裏獲取的任務已經達到了子任務的to_task,並能從子任務獲取到主任務傳遞過來的數據
List<Task> tasks = taskService.findPersonalTasks(PARTNER4JAVA);
for (Task task : tasks) {
	// 判斷是否爲指定的子任務名稱
	Assert.assertEquals("to_task", task.getName());
	// 判斷參數是否攜帶成功
	Assert.assertEquals("I'm helloworld!", taskService.getVariable(task.getId(), "text"));

	Map<String, Object> mVariables = new HashMap<String, Object>();
	mVariables.put("result", "line1");
	// 完成子任務,並攜帶參數返回給主任務
	taskService.completeTask(task.getId(), mVariables);
}

// 子任務完成後,返回到主任務,會根據子任務返回的result變量,轉化爲主任務的line變量,決定下一步的走向
tasks = taskService.findPersonalTasks(PARTNER4JAVA);
for (Task task : tasks) {
	Assert.assertEquals("m_task1", task.getName());
	taskService.completeTask(task.getId());
}

9、自定義活動

在流程執行過程中,只要拿到了流程實例及其上下文對象,再通過某種機制獲得流程定義的輸入數據、發佈輸出數據,那麼自己實現一些活動就不是什麼難事了,因爲這些活動獨特的活動也就是它們的處理邏輯。
如果有特殊而複雜的業務需求,與其生套jBPM本身提供的流轉控制活動,不如自己實現一個自定義的活動來使用。

jBPM4提供了這樣的功能,可以通過custom活動完全自定義一套活動行爲,調用自己的代碼,實現定製的活動邏輯。

處理類需要實現ExternalActivityBehaviour接口,而ExternalActivityBehaviour接口繼承自ActivityBehaviour接口。
ActivityBehaviour接口是所有jBPM4內置活動都需要實現的接口。
需要實現兩個方法:
·來自ActivityBehaviour的void execute(ActivityExecution execution)
在流程實例進入到此活動時執行此方法,完成主要的活動邏輯,提供ActivityExecution對象作爲參數,通過它可以拿到流程實例、執行上下文等您能得到的一切流程運行時對象。
·void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters)
自定義活動需要實現的方法,在流程實例得到執行信號離開此活動時執行此方法,這個接口爲您提供了更爲全面的流程運行時對象,包括信號的名稱signalName。
在實現此方法時需要做的是:離開活動時的業務處理邏輯,以及根據signalName使流程實例通過正確的流出轉移走向下一步。

custom_hello.jpdl.xml:

<process name="custom_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="486,41,80,40" name="start">
		<transition to="m_custom" />
	</start>

	<!-- 自定義活動:主要是傳入了我們定義的class -->
	<custom class="com.partner4java.demo.MyCustom" g="487,203,80,40"
		name="m_custom">
		<transition to="end" />
	</custom>

	<end g="518,371,80,40" name="end" />
</process>

public class MyCustom implements ExternalActivityBehaviour {
	private static final long serialVersionUID = 2919452947822958504L;

	@Override
	public void execute(ActivityExecution execution) throws Exception {
		// 在這裏執行自定義的處理邏輯
		System.out.println("execute(ActivityExecution execution)");
		// ....
		// 是流程陷入“等待”狀態
		execution.waitForSignal();
		// 當然也可以調用execution.take(signalName)在這裏自動發出執行信號,不等待而直接完成這個活動
	}

	@Override
	public void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) throws Exception {
		System.out.println("signal(ActivityExecution execution, String signalName, Map<String, ?> parameters)");
		// 活動收到執行信號後,進入到這裏
		// .....
		// 最後別忘了調用使流程實例進入下一步方法
		execution.take(signalName);
	}

}

10、自動活動

通過前面學習的流程定義,jBPM能完成基本的工作流管理需要,這需要根據流程定義規則,進行人工的流程操作。
同樣,jBPM4也能很好支持處理多種自動活動,所謂自動活動就是在執行過程中完全無須人工干預地編排好地程序jBPM4在處理和執行這些自動活動時能把人工活動產生的數據通過流程變量方式與之完美地結合。

jBPM4默認支持的自動活動類型有:
·java - Java程序活動
·script - 腳本活動
·hql - Hibernate查詢語言活動
·SQL - 結構化查詢語言活動
·mail - 郵件活動

·java(Java程序活動)
java活動可以指定一個Java類的方法(Java Method),當流程執行到此活動時,便會自動執行此Java方法。
java活動的屬性:
class Java的全路徑。此類的對象在活動執行時被“延遲創建”(需要提供無參構造器),即不隨jBPM工作流引擎啓動而創建。當這些對象創建後,將作爲流程定義的一部分被緩存。
expr 此表達式返回一個Java類對象。(class或expr二者必選其一)
method(必須) 調用的方法名稱
var 存儲方法結果的流程變量名稱

java活動支持的元素:
field 在方法被調用之前給指定的類成員域注入指定的值
arg 給被調用的方法提供參數

<process name="java_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="520,105,80,40" name="start">
		<transition to="m_java" />
	</start>
	<!-- 在這裏調用java活動,調用類JavaHello對象的hello方法,並將執行結果存儲到manu變量中 -->
	<java class="com.partner4java.demo.JavaHello" g="511,225,80,40"
		method="hello" name="m_java" var="manu">
		<!-- 爲方法傳入參數 -->
		<arg>
			<string value="world" />
		</arg>
		<!-- 爲成員變量注入hello值,如果變量爲類似private私有,需提供可訪問的設置方法 -->
		<field name="name">
			<string value="hello" />
		</field>
		<transition to="m_state" />
	</java>
	<state name="m_state" g="516,340,80,40">
		<transition to="end" />
	</state>
	<end g="538,434,80,40" name="end" />
</process>

repositoryService.createDeployment().addResourceFromClasspath("auto/java_hello.jpdl.xml").deploy();
ProcessInstance processInstance = executionService.startProcessInstanceByKey("java_hello");
Execution execution = processInstance.findActiveExecutionIn("m_state");
System.out.println(executionService.getVariable(execution.getId(), "manu"));
·script(腳本活動)
script -- 腳本活動爲我們解決了這個問題,您可以在script活動中定義一段EL表達式腳本,工作流引擎執行此活動時會解析這段腳本。
實際山個,不僅是EL表達式,任何一種複合JSR-223規範的腳本語言都可以在這裏使用。
(在配置文件jbpm.default.scriptmanager.xml裏定義要使用的腳本語言)

jBPM4默認的腳本語言是juel,即EL表達式。

·hql(Hibernate查詢語言活動)
在一些特殊的情況下,我們可能需要直接從持久化層讀取流程數據。
因爲jBPM4的持久化層是基於Hibernate框架實現的,因此使用hql活動,直接面向數據庫執行HQL語句查詢,並將返回結果保存到流程變量中,是個不錯的辦法。

hql活動的屬性:
var 存儲HQL執行結果的流程變量名
unique 此屬性值爲true時,在查詢結果上調用uniqueResult()方法獲得HQL查詢的唯一結果集;此屬性默認值爲false,即查詢結果調用list()方法得到HQL查詢的結果集列表。

hql活動的元素:
query HQL查詢的語句
parameter HQL查詢語句的外部參數
<process name="hql_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="501,8,80,40">
		<transition to="m_hql"/>
	</start>
	<!-- 此HQL活動將查詢名字含有m的所有任務名稱,並放入參數變量names中 -->
	<hql name="m_hql" var="names" g="491,142,80,40">
		<!-- HQL語句 -->
		<query>
			select task.name from org.jbpm.pvm.internal.task.TaskImpl as task where task.name like :taskName
		</query>
		<!-- HQL替換參數 -->
		<parameters>
			<string name="taskName" value="%m%"/>
		</parameters>
		<transition to="m_task"/>
	</hql>
	<task name="m_task" g="505,292,80,40">
		<transition to="end"/>
	</task>
	<end name="end" g="529,440,80,40"/>
</process>

·sql(結構化查詢語句活動)
與HQL活動相似,jBPM4的SQL活動能夠支持使用SQL -- 結構化查詢語言直接從流程數據庫中查詢數據,將結果返回到流程變量中。
sql活動與hql活動的屬性和元素大致一樣。


11、事件

事件(event)用來定位在流程執行過程中特定的“點”,例如“流程實例開始”、“狀態活動結束”等,可以在這些“點”註冊一系列的監聽器(listener)。
當流程的執行通過這些被監聽的點時,監聽器中設定的邏輯就會被執行。

編寫一個時間監聽器需要實現EventListener接口:

/** listener to process execution events.
 * 
 * @author Tom Baeyens
 */
public interface EventListener extends Serializable {
  //接口方法提供了流程的執行對象execution,這足夠拿到當前流程的任何信息
  /** is invoked when an execution crosses the event on which this listener is registered */
  void notify(EventListenerExecution execution) throws Exception;

}
爲了給流程或活動分配一系列的事件監聽器,可以使用on元素來爲時間監聽器分組並制定事件,on元素可以嵌入到process元素或process元素下的任何流程活動中.
on元素的屬性:
event(必須) 事件的名稱
(event屬性目前只支持“start-開始事件”和“end-結束事件”的監聽)

對於轉移的執行事件(transition take),只需直接在transition元素中嵌入相應的時間監聽器即可。

on元素支持的子元素:
event-listener 指定自定義的事件監聽器,實現EventListener接口
任何自動活動 使用自動活動(java,script,hql...)作爲事件監聽器

event-listener的獨特屬性:
propagation 指定該時間監聽器是否支持被傳播的事件調用。(enabled、disabled、true、false、on、off)默認值disabled

·事件監聽
<process name="event_listener_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 放在了process裏,是監聽<start>事件 -->
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>
	<!-- 放在了process裏,是監聽<end>事件 -->
	<on event="end">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process end" />
			</field>
		</event-listener>
	</on>
	<start g="497,76,80,40">
		<transition to="m_start" />
	</start>
	<state name="m_start" g="479,222,80,40">
		<!-- 監聽本state的開始事件 -->
		<on event="start">
			<event-listener class="com.partner4java.event.MyEventListener">
				<field name="message">
					<string value="state start" />
				</field>
			</event-listener>
		</on>
		<!-- 監聽本state的結束事件 -->
		<on event="end">
			<event-listener class="com.partner4java.event.MyEventListener">
				<field name="message">
					<string value="state end" />
				</field>
			</event-listener>
		</on>
		<transition to="end" />
	</state>
	<end name="end" g="496,382,80,40" />
</process>
·事件傳播
jBPM的事件觸發是支持從父元素傳播到子元素的。
默認情況下,事件監聽器(event-listener)只對其當前訂閱的元素所觸發的時間起作用,即propagation="disable"。
但是指定事件監聽器的傳播屬性propagation="enabled",則該事件監聽器可以對其監聽元素的所有子元素起作用。
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>
改爲:
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener" propagation="true">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>	
state的啓動動作也會觸發此事件。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章