轉http://linliangyi2007.javaeye.com/blog/176345
本片文章,我們將從業務流程的設計開始,通過帶領大家完成一個完整工作流的程序設計,來學習jPDL的使用。
業務流程設計
這里我們實現一個相對簡化的公司借款申請流程。流程圖如下:
在jPDL中,與流程設計相關的文件有三個:processdefinition.xml、gdp.xml、processimage.jpg。
其中processdefinition.xml是流程定義的描述文件;gpd.xml是對圖形界面呈現的XML描述;而
processimage.jpg則是對圖形界面的快照。下面我們將展示本樣例的流程定義文件。
[@more@]流程定義描述
在樣例流程中,除了開始和結束結點外,我們定義了三種類型的結點:
任務結點
任務結點是一個需要人工參與的結點類型。當流程進入結點時,會生成相應的任務實例(TaskInstatnce),并通過委派接口
AssignmentHandler或jBPM表達式將任務委派給一個或多個特定的角色或參與者。結點自身進入等待狀態,直到任務被參與者完成或者跳過,
流程繼續。
判定結點
判定結點的設計目標是根據上下文環境和程序邏輯,判定流程轉向。通過指定一個實現DecisionHandlder接口的Java委派類或jBPM表達式,來返回轉向(transition)的字符竄類型的名稱(可以是中文哦),來達到決定流程方向的功能。
普通結點
普通結點也可以定義相應的處理任務,通過定義相應的ActioinHandler類。同任務結點不同的是,普通結點定義的任務是由流程自動執行的,無須人工干預。
三種結點都可定義結點事件(event):
node-enter,該事件在流程進入結點時觸發
node-leave,該事件在流程離開節點是觸發
可以在事件上掛接ActioinHandler接口的實現類來完成一些特定的功能。
三種節點都可以定義異步處理方式(async屬性):
異步處理意味著每個結點的事務處理是通過消息機制分離的,不再同一線程中統一調用執行。而是由消息監聽線程從消息隊列中取得消息體來運行相應得程序。
此外我們定義了結點間的轉向(transition),用來記錄和處理狀態的變遷。每個轉向中,可以委派一個或多個的ActioinHandler接口實現類,負責處理節點變遷時的上下文狀態變更及回調用戶定義的處理程序。
流程的程序接口說明
動作處理接口(ActioinHandler)
接口方法:void execute( ExecutionContext executionContext ) throws Exception
該接口是jPDL中最常用的一個回調接口。從它的接口方法可以發現,它僅僅暴露了流程執行上下文變量ExecutionContext。用戶程序
通過ExecutionContext來了解流程的執行狀態,并通過改變ExecutionContext中的屬性值來影響流程的執行。
ActioinHandler接口可以在所有能包含事件(event)、動作(action)元素的地方被回調。
判定處理接口(DecisionHandlder)
接口方法:String decide(ExecutionContext executionContext) throws Exception
判定接口僅適用于判定節點(decision)中。從它的接口方法可以看出,方法要返回一個字符串型的結果,這個結果必須和判定節點擁有的轉向(transition)集合中的一條轉向名稱相匹配。
在DecisionHandlder的接口方法中一樣能訪問到ExecutionContext變量,這為判定提供了執行上下文的根據。當然,如果有必要,用戶也可以在該接口中改變ExecutionContext中的變量值。
委派處理接口(AssignmentHandler)
接口方法:void assign(Assignable assignable, ExecutionContext executionContext) throws Exception;
委派處理接口被用戶任務元素(task)的委派(assignment)子元素中,它的職責很明確,就是將任務分配給指定的人員或角色。
在AssignmentHandler接口的方法中,Assignable變量通常指任務實例(TaskInstance)。通過將
ExecutionContext和TaskInstance兩個變量都暴露給接口方法,用戶就可以根據流程上下文情況,來決定要將指定的任務分配個誰。
流程的部署
用戶使用jPDL的流程設計器定義業務流程,當然,你也可以直接用文檔編輯器直接編輯processdefinition.xml定義文件。定義文檔是可
以直接被ProcessDefinition類載入使用的,但在正式運行的系統中,流程定義信息更多是使用關系型數據庫來存儲。從流程定義文件將數據導入
流程數據庫的過程,我們稱之為流程部署。
jPDL的流程部署文件包含processdefinition.xml的定義部分和Java處理器的代碼部分,這些文件可以被一起打包成.jpdl的zip格式包而后上傳服務器端。這個過程可以在流程設計器界面的“deployment”標簽頁中操作:
這里我們著重要講述的是接受部署文件上載的服務器端配置。在jBPM3.2的包中帶著一個jPDL的管理控制臺web應用,默認名字為jbpm-
console。該應用帶有接受流程定義包部署的程序,但不是最小化的。實際上完成流程部署功能的,只是jbpm-jpdl.jar核心包中的一個
servlet類:org.jbpm.web.ProcessUploadServlet . 完成這個Servlet的成功部署,需要以下工作:
1. 配置web.xml,將servlet配置成啟動時加載,如下:
2. 建立流程定義存儲數據庫表:
Demo中,我們使用的數據庫是MySQL的,在E:Javatoolsjbpm-jpdl-3.2.2db目錄下有個
jbpm.jpdl.mysql.sql數據庫腳本文件。但我們不能直接導入該文件, 會提示有錯誤,
應為該文件的SQL語句末尾少了分號,在批量執行時,MySQL報錯。需要在每一行SQL的末尾添加一個分號,這樣就可以用source命令導入了。
3. 配置Hibernate.cfg.xml
由于jBPM的數據庫持久化是依靠Hibernate進行的,因此需要配置Hibernate.cfg.xml使其適應我們的MySQL環境
4. Import需要的jar包
這里的jar包包括三部分:jbpm的核心包;Hibernate及其支撐包;MySQL的JDBC驅動包。
到此,我們的配置工作完成,這是實現jBPM流程部署服務端的最小化應用配置。
流程控制及API使用
樣例程序中的Handler接口實現
下面,根據上述的接口分類,列出樣例程序中的類名及相應的功能說明,具體可參考源代碼。
動作處理接口(ActioinHandler)
這里要提到一個很重要的區別,就是作用于Node上的ActoinHandler和作用于Transition上的ActoinHandler是
有不同的。區別在于,Node上的ActoinHandler在結束業務邏輯處理后,必須調用
executionContext.leaveNode();或executionContext.leaveNode(transition)來保證流
程向下執行;而作用于Transition上的則不需要。
判定處理接口(DecisionHandlder)
委派處理接口(AssignmentHandler)
流程測試剖析
本章節,我們將給大家剖析兩個流程測試類。一個是簡單的基于內存模型的流程測試FirstFlowProcessTest;一個是更貼近實用的,基于MySQL數據庫操作的標準測試案例。通過對這兩個測試例程的分析,來直觀的學習如何通過Java API操作jPDL。
簡單流程測試案例
測試案例類:FirstFlowProcessTest.java
public class FirstFlowProcessTest extends TestCase {
ProcessDefinition pdf ;
ProcessInstance pi;
public void test4000YuanApplication() throws Exception {
deployProcessDefinition();
createProcessInstance("linly");
submitApplication(4000);
approveByManager(true);
checkTasks();
}
public void test6000YuanApplication() throws Exception {
deployProcessDefinition();
createProcessInstance("linly");
submitApplication(6000);
approveByManager(true);
approveByPresident(true);
checkTasks();
}
public void test7000YuanApplication() throws Exception {
deployProcessDefinition();
createProcessInstance("linly");
submitApplication(7000);
approveByManager(true);
approveByPresident(false);
checkTasks();
}
/**
* 部署流程定義
* @throws Exception
*/
protected void deployProcessDefinition() throws Exception{
System.out.println("==FirstFlowProcessTest.deployProcessDefinition()==");
pdf = ProcessDefinition.parseXmlResource("firstflow/processdefinition.xml");
assertNotNull("Definition should not be null", pdf);
}
/**
* 生成流程實例
*/
protected void createProcessInstance(String user){
System.out.println("==FirstFlowProcessTest.createProcessInstance()==");
assertNotNull("Definition should not be null", pdf);
//生成實例
pi = pdf.createProcessInstance();
assertNotNull("processInstance should not be null", pi);
//設置流程發起人
pi.getContextInstance().createVariable("initiator", user);
//觸發流程轉向
pi.signal();
}
/**
* 填寫提交申請單
* @param money
*/
protected void submitApplication(int money){
System.out.println("==FirstFlowProcessTest.submitApplication()==");
TaskInstance ti = (TaskInstance)pi.getTaskMgmtInstance().getTaskInstances().iterator() .next() ;
System.out.println("ti.actor = " + ti.getActorId());
ContextInstance ci = ti.getContextInstance();
ci.setVariable("money",new Integer(money));
ti.end();
}
/**
* 部門經理審批
* @param pass
*/
@SuppressWarnings("unchecked")
protected void approveByManager(boolean pass){
System.out.println("==FirstFlowProcessTest.approveByManager()==");
Iterator
該案例是在沒有數據庫支持的情況下,對報銷流程進行運行測試,測試邏輯如下:
1. 加載流程定義
ProcessDefinition.parseXmlResource("firstflow/processdefinition.xml")代碼說明:
在沒有數據庫存儲的情況下,流程定義通過ProcessDefinition類直接從processdefinition.xml文件中解析加載。
2. 實例化流程對象
//生成實例
pi = pdf.createProcessInstance();
assertNotNull("processInstance should not be null", pi);
//設置流程發起人
pi.getContextInstance().createVariable("initiator", user);
//觸發流程轉向
pi.signal();代碼說明:
在獲得流程定義的實例后,可以用它生成流程實例,使用如下的語句:
pi = pdf.createProcessInstance();
流程實例擁有自己的ContextInstance環境變量對象。它實際上是一個HashMap,以key-value方式記錄了流程的上下文變量值,代碼中的
pi.getContextInstance().createVariable("initiator", user);就是向環境變量中添加一個key為initiator的對象。
每個流程實例都擁有自己Token令牌對象,主流程有自己的RootToken,子流程也擁有自己的子Token。父流程的Token和子流程的Token相互關聯,形成Token樹。
Token對象表示流程運行的當前位置(運行到哪個節點了)。通過對Token對象的signal()方法調用,可以使流程向下運行。代碼中的
pi.signal();實際上是間接調用了pi.getRootToken().signal();它使得新建的流程繼續向下個節點(即借款申請單填
寫)進發。
3. 員工發起借款申請
/**
* 填寫提交申請單
* @param money
*/
protected void submitApplication(int money){
System.out.println("==FirstFlowProcessTest.submitApplication()==");
TaskInstance ti = (TaskInstance)pi.getTaskMgmtInstance().getTaskInstances().iterator() .next()
System.out.println("ti.actor = " + ti.getActorId());
ContextInstance ci = ti.getContextInstance();
ci.setVariable("money",new Integer(money));
ti.end();
}代碼說明:
在借款流程發起后,流程進入了申請單填寫階段。這個階段是個人工的任務,需要用戶的介入。因此,對于要借款的用戶而言,首先是獲取填寫申請單的任務實例:
TaskInstance ti = (TaskInstance)pi.getTaskMgmtInstance().getTaskInstances().iterator() .next()
在這個測試類中,由于沒有數據庫。對流程實例的引用是依靠了類的全局標量pi。這里通過pi獲取全部的任務列表。實際上有且僅有一個任務,就是我們剛剛發起的申請單填寫任務。
接下來,我們獲取流程的上下文變量,將申請借款的數額記錄在上下文變量中ContextInstance ci = ti.getContextInstance();
ci.setVariable("money",new Integer(money));
最后,我們要結束當前任務,告訴流程繼續下行,調用ti.end();這個方法的本質依然是調用了token.signal(),它選擇了一個默
認的transition進行轉向。這里要說明的是signal方法有多態的實現signal(Transition
transition),是可以指定具體轉向參數的。
4. 部門領導審批申請
/**
* 部門經理審批
* @param pass
*/
@SuppressWarnings("unchecked")
protected void approveByManager(boolean pass){
System.out.println("==FirstFlowProcessTest.approveByManager()==");
Iterator代碼說明:
這里,流程進入了部門經理審批階段。由于沒有數據庫支持,我們只能采取遍歷任務列表,并比對委派者ID的方式來確定委派給部門經理的任務實例。(在后面的基于數據庫的標準案例中,我們會看到如果根據用戶的ID來獲取分配給指定用戶的任務)
ti.getActorId().equals("DepartmentManager") // 比對任務的委派人。
ti.getToken().getNode().getLeavingTransitions();//獲取任務在當前節點上的所有轉向。
這里我們要特別指出的是ti.end("部門經理審批通過")和ti.end("部門經理駁回")這實際上調用token.signal(transition);來完成任務的轉向,從而使流程繼續。
5. 總經理審批申請
/**
* 總經理審批
* @param pass
*/
@SuppressWarnings("unchecked")
protected void approveByPresident(boolean pass){
System.out.println("==FirstFlowProcessTest.approveByPresident()==");
Iterator代碼說明:
此步代碼同“部門經理審批”代碼相似,不作更多說明。
標準流程測試案例
該案例模擬了標準運行環境中,基于關系型數據庫的jBPM系統是如何執行流程的。
測試案例類:FirstFlowProcessDBTest.java
public class FirstFlowProcessDBTest {
/*
* 初始化jBPM配置
* 包含對Hibernate的數據庫初始化
*/
static JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
public static void main(String[] args){
FirstFlowProcessDBTest test = new FirstFlowProcessDBTest();
test.test4000YuanApplication();
test.test6000YuanApplication();
test.test7000YuanApplication();
}
public void test4000YuanApplication(){
ProcessInstance pi = createProcessInstance("linly");
submitApplication("linly" , 4000);
approveByManager(true);
checkTasks(pi);
}
public void test6000YuanApplication() {
ProcessInstance pi = createProcessInstance("linly");
submitApplication("linly" , 6000);
approveByManager(true);
approveByPresident(true);
checkTasks(pi);
}
public void test7000YuanApplication() {
ProcessInstance pi = createProcessInstance("linly");
submitApplication("linly" , 7000);
approveByManager(true);
approveByPresident(false);
checkTasks(pi);
}
/**
* 生成流程實例
*/
protected ProcessInstance createProcessInstance(String user){
System.out.println("==FirstFlowProcessTest.createProcessInstance()==");
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
GraphSession graphSession = jbpmContext.getGraphSession();
/*
* 從數據庫獲取指定的流程定義
*/
ProcessDefinition pdf = graphSession.findLatestProcessDefinition("simple");
//生成流程實例
ProcessInstance pi = pdf.createProcessInstance();
//設置流程發起人
pi.getContextInstance().createVariable("initiator", user);
//觸發流程轉向
pi.signal();
/*
* 保存流程實例
*/
jbpmContext.save(pi);
return pi;
}finally{
jbpmContext.close();
}
}
/**
* 填寫提交申請單
* @param money
*/
@SuppressWarnings("unchecked")
protected void submitApplication(String actorId , int money){
System.out.println("==FirstFlowProcessTest.submitApplication()==");
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
/*
*根據操作者ID,獲取屬于該操作者的任務集 免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。