這個(gè)講座將展示如何建造一個(gè)jpdl和如何使用API的方法來(lái)管理運行時(shí)的執行.
這個(gè)講座的方式是解釋一系列的例子. 每個(gè)例子將集中關(guān)注特別的主題和額外的說(shuō)明. 這些例子可以在jBPM包的examples目錄下找到.
最好的方法就是學(xué)著(zhù)來(lái)建立一個(gè)Project實(shí)驗所給的例子.
eclipse 用戶(hù)注意:下載jbpm-3.0-[version].zip并且解壓縮到系統. 然后從菜單 "File" --> "Import..." --> "Existing Project into Workspace". 點(diǎn) "Next" 然后瀏覽jBPM 根目錄然后點(diǎn)擊 "Finish". 現在你的有了jbpm 3 project了. 你可以發(fā)現這個(gè)講座目錄在目錄 src/java.examples/... . 你打開(kāi)這些例子后,可以運行它們"Run" --> "Run As..." --> "JUnit Test"
jBPM 包括一個(gè)圖形設計器來(lái)產(chǎn)生例子中的XML. 你可以從這里下載和學(xué)習有關(guān)圖形設計器的說(shuō)明 節 2.1, “下載一覽”
一個(gè)流程 是有一個(gè)定向圖(directed graph)來(lái)定義,由節點(diǎn)和變換組成 . hello world 流程有3個(gè)節點(diǎn).如下看如何組合起來(lái), 我們先開(kāi)始一個(gè)簡(jiǎn)單的流程不使用圖形設計器. 下面的圖形表示hello world 流程:
public void testHelloWorldProcess() { // 這個(gè)方法展現流程定義和流程執行的定義 // 流程定義有3個(gè)節點(diǎn). // 一個(gè)未命名的開(kāi)始狀態(tài)start-state,一個(gè)狀態(tài) 's' // 一個(gè)結束狀態(tài)名字為'end'. // 下面行解析一個(gè)xml text為一個(gè)ProcessDefinition對象(流程定義) // ProcessDefinition 把流程定義形式描述為java對象 ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition>" +" <start-state>" +" <transition to='s' />" +" </start-state>" +" <state name='s'>" +" <transition to='end' />" +" </state>" +" <end-state name='end' />" +"</process-definition>"); // 下一行建立了一個(gè)流程執行定義. //在構造后,流程執行有一個(gè)主要的執行路徑 // (= root token 根令牌) 此時(shí)位置在start-state處ProcessInstance processInstance =new ProcessInstance(processDefinition); // 在構造后流程執行有一個(gè)主要的執行路徑// (= roottoken 根令牌) .Token token = processInstance.getRootToken(); // 構造后, 位置處于流程定義執行路徑start-state的位置assertSame(processDefinition.getStartState(), token.getNode()); // 現在開(kāi)始流程執行,離開(kāi)start-state 結束默認的轉換(transition) token.signal(); // 這個(gè)signal方法將會(huì )阻塞直到流程執行進(jìn)入 wait 狀態(tài) // 流程執行在狀態(tài)'s' 進(jìn)入第一個(gè) 等待狀態(tài) // 因此執行主路徑現在位置在 狀態(tài)'s' assertSame(processDefinition.getNode("s"), token.getNode()); // 我們再送另外一個(gè)信號signal. 這將繼續執行離開(kāi)狀態(tài)'s' 結束默認的轉換(transition) token.signal(); // 現在信號signal方法將返回,因為流程實(shí)例到達了end-state 結束狀態(tài) assertSame(processDefinition.getNode("end"), token.getNode());}jBPM一個(gè)基本的特性是當流程處于等待狀態(tài)時(shí)候可以把流程執行 永久化到數據庫中 . 下一個(gè)例子想你展示了如何存儲一個(gè)流程實(shí)例到j(luò )BPM數據庫中. 例子給出一個(gè)將會(huì )發(fā)生的上下文.分開(kāi)的方法用來(lái)建立不同部分的用戶(hù)代碼. 比如一部分用戶(hù)代碼在web 應用程序中開(kāi)始一個(gè)流程并永久化執行到數據庫中.然后,message drive bean從數據庫中載入流程實(shí)例并繼續它的執行
jBPM 永久化的更多內容可以參看 第六章, 永久化.
public class HelloWorldDbTest extends TestCase { // 我們在每個(gè)應用程序中需要一個(gè)JbpmSessionFactory. 因此我們把它放在一個(gè)靜態(tài)變量中 // JbpmSessionFactory 在test 方法中來(lái)建立一個(gè) JbpmSession's.static JbpmSessionFactory jbpmSessionFactory =JbpmSessionFactory.buildJbpmSessionFactory();static { // 因為HSQLDBin-memory數據庫是干凈的數據庫, // 在我們開(kāi)始測試前,我們需要建立table.// The next line creates the database tables and foreign key // constraints. jbpmSessionFactory.getJbpmSchema().createSchema();}public void testSimplePersistence() { // 在3個(gè)方法調用下面方法中間,所有數據被寫(xiě)入數據庫 // 在單元測試中,這3個(gè)方法被正確執行在每個(gè)方法之后 // 因為我們想完成測試流程場(chǎng)景 // 但在實(shí)際中這些方法代表著(zhù)server不同的請求 // 因為我們開(kāi)始的數據庫是個(gè)干凈的數據庫,我們需要首先發(fā)布流程在里面 // 在真實(shí)中,這個(gè)是由流程開(kāi)發(fā)人員完成的 deployProcessDefinition(); // 假定我們想開(kāi)始流程實(shí)例(= 流程執行) // 當用戶(hù)提交一個(gè)Web表單的時(shí)候.processInstanceIsCreatedWhenUserSubmitsWebappForm(); // 然后,到達的異步消息將繼續執行 theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();}public void deployProcessDefinition() {//定義一個(gè)流程,包括三個(gè)及點(diǎn),一個(gè)未命名的start-state,一個(gè)狀態(tài)'s'//一個(gè)結束狀態(tài) end-state名字'end'.ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition name='hello world'> +" <start-state name='start'> +" <transition to='s' /> +" </start-state> +" <state name='s'> +" <transition to='end' /> +" </state> +" <end-state name='end' /> +"</process-definition>); // 打開(kāi)新的永久層會(huì )話(huà)JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession(); // 并且在永久層會(huì )話(huà)上開(kāi)啟事務(wù)jbpmSession.beginTransaction(); // 保存流程定義到數據庫中 jbpmSession.getGraphSession().saveProcessDefinition(processDefinition); // 提交事務(wù)jbpmSession.commitTransaction(); // 關(guān)閉會(huì )話(huà).jbpmSession.close();}public void processInstanceIsCreatedWhenUserSubmitsWebappForm() { // 這個(gè)方法里的代碼可以放在structs action或JSF管理bean 里 // 打開(kāi)一個(gè)新的永久層會(huì )話(huà)JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession(); // 啟動(dòng)事務(wù).jbpmSession.beginTransaction(); // 查詢(xún)數據庫得到我們在上面步驟發(fā)布的流程定義 ProcessDefinition processDefinition =jbpmSession.getGraphSession().findLatestProcessDefinition("hello world"); // 有了從數據庫中的得到的processDefinition, //我們就可以建立流程執行定義比如hello world 例子(它沒(méi)有永久化).ProcessInstance processInstance =new ProcessInstance(processDefinition);Token token = processInstance.getRootToken();assertEquals("start", token.getNode().getName()); // 開(kāi)始流程執行token.signal(); // 流程在狀態(tài)'s'.assertEquals("s", token.getNode().getName()); // 流程實(shí)例被保存在數據庫 // 所以當前流程執行的狀態(tài)被保存進(jìn)數據庫 . jbpmSession.getGraphSession().saveProcessInstance(processInstance);// The method below will get the process instance back out // of the database and resume execution by providing another // external signal. // web應用程序動(dòng)作結束出,事務(wù)被提交.jbpmSession.commitTransaction(); // 關(guān)閉jbpmSession.jbpmSession.close();}public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() { // 這個(gè)代碼可以包含在message driven bean中. // 打開(kāi)新的永久性的會(huì )話(huà).JbpmSession jbpmSession = jbpmSessionFactory.openJbpmSession(); // 永久化會(huì )話(huà)上開(kāi)始事務(wù) // 說(shuō)明它也可能使用應用服務(wù)器的DataSource的JDBC連接 jbpmSession.beginTransaction();GraphSession graphSession = jbpmSession.getGraphSession();// First, we need to get the process instance back out of the database.// There are several options to know what process instance we are dealing // with here. The easiest in this simple test case is just to look for // the full list of process instances. That should give us only one // result. So let's look up the process definition.ProcessDefinition processDefinition =graphSession.findLatestProcessDefinition("hello world"); // 現在,我們搜索這個(gè)流程定義的所有流程實(shí)例.List processInstances =graphSession.findProcessInstances(processDefinition.getId());// We know that in the context of this unit test there is // only one execution. In real life, the processInstanceId can be // extracted from the content of the message that arrived or from // the user making a choice.ProcessInstance processInstance =(ProcessInstance) processInstances.get(0);// 我們可以繼續執行. 說(shuō)明流程實(shí)例委托信號到執行主路徑(= the root token)processInstance.signal(); // 在singal后, 我們知道流程執行應該到 end-state assertTrue(processInstance.hasEnded()); // 現在我們可以更新執行狀態(tài)到數據庫中graphSession.saveProcessInstance(processInstance); // MDB結束, 事務(wù)被提交.jbpmSession.commitTransaction(); // jbpmSession被關(guān)閉.jbpmSession.close();}}在流程執行時(shí)候流程變量包含上下文信息. 流程變量同java.util.Map相似映射名字到值,值可能是個(gè)java對象 . 流程變量被永久化作為流程實(shí)例的一部分. 為了讓事情簡(jiǎn)單,這個(gè)例子中我們僅僅展示使用變量的API而沒(méi)有永久化.
關(guān)于變量的更多信息可以參看 第8章 上下文
// 這個(gè)例子也是從hello world 流程開(kāi)始. // 甚至沒(méi)有修改.ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition>" +" <start-state>" +" <transition to='s' />" +" </start-state>" +" <state name='s'>" +" <transition to='end' />" +" </state>" +" <end-state name='end' />" +"</process-definition>");ProcessInstance processInstance =new ProcessInstance(processDefinition); // 從流程實(shí)例中為流程變量獲得上下文實(shí)例 ContextInstance contextInstance =processInstance.getContextInstance(); // 在開(kāi)始之前流程離開(kāi)了start-state, // 我們準備設置一些流程變量在流程實(shí)例上下文中 .contextInstance.setVariable("amount", new Integer(500));contextInstance.setVariable("reason", "i met my deadline"); // 從現在開(kāi)始,這些變量同流程實(shí)例關(guān)聯(lián) // 流程變量可以從用戶(hù)代碼中通過(guò)下面展示的API來(lái)訪(fǎng)問(wèn) // 可可以在動(dòng)作Action和節點(diǎn)的實(shí)現中訪(fǎng)問(wèn) // 流程變量也作為流程實(shí)例的一部分保存進(jìn)數據庫 .processInstance.signal(); // 訪(fǎng)問(wèn)變量通過(guò)contextInstance. assertEquals(new Integer(500),contextInstance.getVariable("amount"));assertEquals("i met my deadline",contextInstance.getVariable("reason"));
在下個(gè)例子里我們將要展示你怎么才能分派一個(gè)任務(wù)給一個(gè)用戶(hù).因為jBPM工作流引擎和組織模型是分開(kāi)的,一種用來(lái)計算參與者表達語(yǔ)言總是受限的. 因此,你不得不指定AssignmentHandler的實(shí)現來(lái)計算任務(wù)的參與者.
public void testTaskAssignment() { // 這個(gè)流程展示基于hello world 流程. // 狀態(tài)節點(diǎn)被task-node代替.task-node在JPDL中是表示一個(gè)等待狀態(tài)并且 // 產(chǎn)生一個(gè)在流程繼續執行前要完成的任務(wù) ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition name='the baby process'>" +" <start-state>" +" <transition name='baby cries' to='t' />" +" </start-state>" +" <task-node name='t'>" +" <task name='change nappy'>" +" <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />" +" </task>" +" <transition to='end' />" +" </task-node>" +" <end-state name='end' />" +"</process-definition>"); // 產(chǎn)生一個(gè)流程執行定義.ProcessInstance processInstance =new ProcessInstance(processDefinition);Token token = processInstance.getRootToken(); // 開(kāi)始流程執行,完整默認的轉換后離開(kāi)start-state .token.signal(); // signal 方法將被阻塞知道流程執行進(jìn)入等待狀態(tài). // 在這個(gè)case中是指task-node.assertSame(processDefinition.getNode("t"), token.getNode()); // 當執行到達task-node, 任務(wù)'change nappy'// 被建立并且NappyAssignmentHandler 被調用來(lái)決定任務(wù)將分派給睡 //NappyAssignmentHandler 返回'papa' // 在真實(shí)環(huán)境中, 任務(wù)將會(huì )從數據庫中獲取,通過(guò)or g.jbpm.db.TaskMgmtSession. // 因此這個(gè)例子中我們不想包括復雜的永久化 // 我們只是得到這個(gè)流程實(shí)例的第一個(gè)task-實(shí)例 (we know there is only one in this test// 我們知道在這個(gè)測試場(chǎng)景中這里只有一個(gè)).TaskInstance taskInstance = (TaskInstance)processInstance.getTaskMgmtInstance().getTaskInstances().iterator().next(); // 現在,我們檢查taskInstance實(shí)際分配給了'papa'.assertEquals("papa", taskInstance.getActorId() ); //現在,我們期望'papa'完成了他的任務(wù)并且標記任務(wù)是完成的 taskInstance.end(); // 因為這是最后(唯一的)要做的任務(wù),這個(gè)任務(wù)的完成觸發(fā) // 流程實(shí)例的繼續執行.assertSame(processDefinition.getNode("end"), token.getNode());}動(dòng)作action是綁定你自己的定制java代碼和jBPM流程的一種機制. 動(dòng)作可以同它自己的節點(diǎn)關(guān)聯(lián)起來(lái) (如果它們在流程圖表示中相關(guān)). 動(dòng)作也可以放在事件event上比如. taking a transition, leaving a node 或者 entering a node.在這個(gè)case ,動(dòng)作不是圖表的一部分,但是它們在流程執行產(chǎn)生事件的時(shí)候,也會(huì )被執行.
我們將用一個(gè)例子: MyActionHandler 來(lái)觀(guān)察動(dòng)作的實(shí)現.這個(gè)動(dòng)作handler實(shí)現不是什么非常特別的事情.它只是設置boolean變量 isExecuted 為 true . 變量 isExecuted 是靜態(tài)的因此它可以在action handler內部被訪(fǎng)問(wèn).
關(guān)于動(dòng)作action的內容可以參看 7.4節, “動(dòng)作”
// MyActionHandler 就是一個(gè)class可以在jBPM流程執行時(shí)候在某些用戶(hù)代碼里被執行 public class MyActionHandler implements ActionHandler { // 在測試之前, isExecuted 被設置為 false.public static boolean isExecuted = false; // 動(dòng)作將設置true 因此 當動(dòng)作被執行 // unit test 將會(huì )展示public void execute(ExecutionContext executionContext) {isExecuted = true;}}
// 每次測試開(kāi)始都要設置MyActionHandler 的成員static isExecuted 為 false.public void setUp() {MyActionHandler.isExecuted = false;}
我們將要在轉換時(shí)開(kāi)始一個(gè)動(dòng)作
public void testTransitionAction() {// The next process is a variant of the hello world process.// We have added an action on the transition from state 's' // to the end-state. The purpose of this test is to show // how easy it is to integrate java code in a jBPM process.ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition>" +" <start-state>" +" <transition to='s' />" +" </start-state>" +" <state name='s'>" +" <transition to='end'>" +" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +" </transition>" +" </state>" +" <end-state name='end' />" +"</process-definition>");// Let's start a new execution for the process definition.ProcessInstance processInstance =new ProcessInstance(processDefinition);// The next signal will cause the execution to leave the start // state and enter the state 's'processInstance.signal(); // 這里將顯示 MyActionHandler還沒(méi)有被執行 assertFalse(MyActionHandler.isExecuted);// ... and that the main path of execution is positioned in // the state 's'assertSame(processDefinition.getNode("s"),processInstance.getRootToken().getNode());// The next signal will trigger the execution of the root // token. The token will take the transition with the// action and the action will be executed during the // call to the signal method.processInstance.signal();// Here we can see that MyActionHandler was executed during // the call to the signal method.assertTrue(MyActionHandler.isExecuted);}下一個(gè)例子是同樣的動(dòng)作,但動(dòng)作被分別放在 enter-node和 leave-node 事件 .注意節點(diǎn)同轉換相比有超過(guò)一個(gè)事件類(lèi)型(event type)轉換(transition)只有一個(gè)事件.
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString("<process-definition>" +" <start-state>" +" <transition to='s' />" +" </start-state>" +" <state name='s'>" +" <event type='node-enter'>" +" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +" </event>" +" <event type='node-leave'>" +" <action class='org.jbpm.tutorial.action.MyActionHandler' />" +" </event>" +" <transition to='end'/>" +" </state>" +" <end-state name='end' />" +"</process-definition>");ProcessInstance processInstance =new ProcessInstance(processDefinition);assertFalse(MyActionHandler.isExecuted);// The next signal will cause the execution to leave the start // state and enter the state 's'. So the state 's' is entered // and hence the action is executed. processInstance.signal();assertTrue(MyActionHandler.isExecuted);// Let's reset the MyActionHandler.isExecuted MyActionHandler.isExecuted = false;// The next signal will trigger execution to leave the // state 's'. So the action will be executed again. processInstance.signal();// Voila. assertTrue(MyActionHandler.isExecuted);聯(lián)系客服