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