jBPM 3.2用戶指南 Hello World范例
本教程向你展示用jpdl構(gòu)造的簡單流程和使用API管理流程運行時執(zhí)行的過程。
本教程的形式是解釋一批范例。這些范例著眼于一個特定的主題并包含大量的注解。這些范例也能夠在jBPM下載軟件包的src/java.examples目錄中找到。
最好的學(xué)習(xí)方式是創(chuàng)建一個項目,并通過創(chuàng)建下面的范例的變體來體驗它。
在開始之前,首先下載和安裝jBPM。
jBPM包含一個圖形設(shè)計器工具,可用于創(chuàng)建在這些范例中顯示的xml文件。你可以在《下載和安裝jBPM》部分找到下載這個圖形設(shè)計器的指南。你不需要為了完成本教程而使用這個圖形設(shè)計工具。
Hello World范例
流程定義是一個有向圖,由節(jié)點(node)和轉(zhuǎn)移(transition)組成。Hello world流程有三個節(jié)點。為了看到這些代碼片段如何組合在一起,我們從一個簡單的流程開始,不使用圖形設(shè)計工具。下面的圖顯示hello world流程的圖形表示:
圖 3.1. Hello world流程圖

- public void testHelloWorldProcess() {
 - // 本方法顯示一個流程定義和此流程定義的執(zhí)行。這個流程定義有三個節(jié)點:一個未命名的
 - // start狀態(tài),一個狀態(tài)s和一個名為end的結(jié)束狀態(tài)。
 - // 下面一行將一段xml文本解析為一個流程定義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>"
 - );
 - // 下面一行代碼創(chuàng)建流程定義的一個執(zhí)行。流程執(zhí)行創(chuàng)建之后將擁有一個主執(zhí)行
 - // 路徑(=根token),指向開始狀態(tài)節(jié)點。
 - ProcessInstance processInstance = new ProcessInstance(processDefinition);
 - // 流程執(zhí)行創(chuàng)建之后,擁有一個主執(zhí)行路徑(=根token)。
 - Token token = processInstance.getRootToken();
 - // 流程執(zhí)行創(chuàng)建之后,主執(zhí)行路徑指向流程定義的開始狀態(tài)節(jié)點。
 - assertSame(processDefinition.getStartState(), token.getNode());
 - // 下面我們啟動流程執(zhí)行,從缺省轉(zhuǎn)移路線離開開始狀態(tài)節(jié)點。
 - token.signal();
 - // signal方法將阻塞,直至流程執(zhí)行進入一個等待狀態(tài)。
 - // 流程執(zhí)行進入了第一個等待狀態(tài):狀態(tài)s。所以主執(zhí)行路徑現(xiàn)在指向狀態(tài)s。
 - assertSame(processDefinition.getNode("s"), token.getNode());
 - // 下面我們發(fā)送第二個信號。這將恢復(fù)流程執(zhí)行,通過缺省的轉(zhuǎn)移路徑離開狀態(tài)s。
 - token.signal();
 - // 現(xiàn)在signal方法返回了,因為流程實例到達了結(jié)束狀態(tài)節(jié)點。
 - assertSame(processDefinition.getNode("end"), token.getNode());
 - }
 
數(shù)據(jù)庫范例
jBPM的一個基本特征是把處于等待狀態(tài)中的流程執(zhí)行持久化到數(shù)據(jù)庫中的能力。下面的范例將向你展示如何將一個流程實例保存到j(luò)BPM數(shù)據(jù)庫中。本范例也暗示存在一個流程執(zhí)行的上下文。下面各個方法在不同的用戶代碼片段中創(chuàng)建,例如,在web應(yīng)用程序中的一段用戶代碼發(fā)起一個流程執(zhí)行并將它持久化到數(shù)據(jù)庫中,隨后,一個消息驅(qū)動bean從數(shù)據(jù)庫中裝載這個流程實例并恢復(fù)其執(zhí)行。
- public class HelloWorldDbTest extends TestCase {
 - static JbpmConfiguration jbpmConfiguration = null;
 - static {
 - // 像此處的范例配置文件能夠在'src/config.files'中找到。典型地配置信息存在
 - // 于資源文件'jbpm.cfg.xml'中,但在這里我們直接傳遞一個XML字符串形式的配置信息。
 - // 首先我們創(chuàng)建一個JbpmConfiguration靜態(tài)對象。系統(tǒng)中的所有線程可以使用
 - // 同一個JbpmConfiguration,因此我們可以安全地把它設(shè)定為靜態(tài)的。
 - jbpmConfiguration = JbpmConfiguration.parseXmlString(
 - "<jbpm-configuration>" +
 - // jbpm-context機制能夠從jbmp使用的環(huán)境服務(wù)中分離出jbpm核心引擎。
 - " <jbpm-context>" +
 - " <service name='persistence' " +
 - " factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
 - " </jbpm-context>" +
 - // 同樣地,jbpm使用的所有資源文件可以從jbpm.cfg.xml中引用。
 - " <string name='resource.hibernate.cfg.xml' " +
 - " value='hibernate.cfg.xml' />" +
 - " <string name='resource.business.calendar' " +
 - " value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
 - " <string name='resource.default.modules' " +
 - " value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
 - " <string name='resource.converter' " +
 - " value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
 - " <string name='resource.action.types' " +
 - " value='org/jbpm/graph/action/action.types.xml' />" +
 - " <string name='resource.node.types' " +
 - " value='org/jbpm/graph/node/node.types.xml' />" +
 - " <string name='resource.varmapping' " +
 - " value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
 - "</jbpm-configuration>"
 - );
 - }
 - public void setUp() {
 - jbpmConfiguration.createSchema();
 - }
 - public void tearDown() {
 - jbpmConfiguration.dropSchema();
 - }
 - public void testSimplePersistence() {
 - // 在以下的三個方法調(diào)用之間,所有的數(shù)據(jù)通過數(shù)據(jù)庫傳遞。在這個單元測試中,
 - // 這三個方法是順序執(zhí)行的,因為我們要測試一個完整的流程場景。但是在現(xiàn)實
 - //中,這些方法代表對服務(wù)器的不同請求。
 - // 因為我們從一個干凈的、空的內(nèi)存數(shù)據(jù)庫中啟動,必須首先部署流程?,F(xiàn)實中,
 - // 流程部署是由流程開發(fā)者一次性完成的。
 - deployProcessDefinition();
 - // 假設(shè)當(dāng)用戶在web應(yīng)用程序中提交一個form的時候,我們要啟動一個流程實例……
 - processInstanceIsCreatedWhenUserSubmitsWebappForm();
 - // 隨后,當(dāng)異步消息到達之后,將繼續(xù)執(zhí)行流程。
 - theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();
 - }
 - public void deployProcessDefinition() {
 - // 本測試展示一個流程定義和該流程定義的一個執(zhí)行實例。這個流程定義有
 - // 三個節(jié)點:一個未命名的開始狀態(tài),一個狀態(tài)s和一個名為end的結(jié)束狀態(tài)。
 - 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>"
 - );
 - // 查找在上面的過程中已配置好的POJO持久化上下文生成器。
 - JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
 - try {
 - // 將流程定義部署到數(shù)據(jù)庫。
 - jbpmContext.deployProcessDefinition(processDefinition);
 - } finally {
 - // 銷毀POJO持久化上下文。
 - // 這包括刷新SQL,將流程定義插入到數(shù)據(jù)庫中。
 - jbpmContext.close();
 - }
 - }
 - public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
 - // 本方法中的代碼應(yīng)存在于一個struts的action 或JSF托管的bean中。
 - // 查找在上面的過程中已配置好的POJO持久化上下文生成器。
 - JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
 - try {
 - GraphSession graphSession = jbpmContext.getGraphSession();
 - ProcessDefinition processDefinition =
 - graphSession.findLatestProcessDefinition("hello world");
 - // 從數(shù)據(jù)庫中取得流程定義之后,我們可以創(chuàng)建該流程定義的一個執(zhí)行實例,
 - // 就像Hello world范例中一樣(后者沒有使用持久化)。
 - ProcessInstance processInstance =
 - new ProcessInstance(processDefinition);
 - Token token = processInstance.getRootToken();
 - assertEquals("start", token.getNode().getName());
 - // 下面啟動流程執(zhí)行
 - token.signal();
 - // 現(xiàn)在流程處于狀態(tài)s。
 - assertEquals("s", token.getNode().getName());
 - // 現(xiàn)在流程實例被保存到數(shù)據(jù)庫中,所以流程執(zhí)行的當(dāng)前狀態(tài)被保存到數(shù)據(jù)庫中了。
 - jbpmContext.save(processInstance);
 - // 下面的方法將從數(shù)據(jù)庫中取回流程實例,通過提供另一個外部信號恢復(fù)流程的執(zhí)行。
 - } finally {
 - // 銷毀POJO持久化上下文。
 - jbpmContext.close();
 - }
 - }
 - public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
 - // 本方法中的代碼可以是一個消息驅(qū)動bean的內(nèi)容。
 - //查找在上面的代碼中已經(jīng)配置好的POJO持久化上下文生成器
 - JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
 - try {
 - GraphSession graphSession = jbpmContext.getGraphSession();
 - // 首先,我們需要從數(shù)據(jù)庫中取回流程實例。為了知道哪個流程實例是我們這里要使用的,
 - // 有幾個可選方法。在這個簡單的測試案例中,最容易的方式是在整個流程實例列表中查
 - // 找,這將僅僅返回一個結(jié)果。所以,讓我們獲取這個流程定義。
 - ProcessDefinition processDefinition =
 - graphSession.findLatestProcessDefinition("hello world");
 - // 現(xiàn)在,我們查找這個流程定義中的所有流程實例。
 - List processInstances =
 - graphSession.findProcessInstances(processDefinition.getId());
 - // 因為我們知道,在這個單元測試測環(huán)境中只存在一個執(zhí)行實例。在現(xiàn)實中,
 - // processInstanceId要從到達的消息內(nèi)容中提取,或來自用戶的選擇。
 - ProcessInstance processInstance =
 - (ProcessInstance) processInstances.get(0);
 - // 現(xiàn)在我們可以繼續(xù)流程執(zhí)行了。記住流程實例將信號轉(zhuǎn)發(fā)給主執(zhí)行路徑(=根token)。
 - processInstance.signal();
 - // 發(fā)出信號之后,我們知道流程執(zhí)行到達了結(jié)束狀態(tài)。
 - assertTrue(processInstance.hasEnded());
 - // 現(xiàn)在我們可以將流程執(zhí)行的狀態(tài)更新到數(shù)據(jù)庫中
 - jbpmContext.save(processInstance);
 - } finally {
 - // 銷毀POJO持久化上下文。
 - jbpmContext.close();
 - }
 - }
 - }
 
#p#
上下文(context)范例:流程變量
流程變量包含流程執(zhí)行過程中的上下文信息。流程變量類似于java.util.Map,將變量名映射到值,值是java對象。流程變量作為流程實例的一部分被持久化。為了保持簡單,在此范例中我們僅僅展示與流程變量有關(guān)的API,不考慮持久化。
- // 這個范例同樣從Hello world流程開始。
 - // 這次甚至沒有修改。
 - 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);
 - // 從流程實例中取得上下文實例,以處理流程變量。
 - ContextInstance contextInstance =
 - processInstance.getContextInstance();
 - // 在流程離開開始狀態(tài)之前,我們準(zhǔn)備在流程實例的上下文中設(shè)置一些流程變量。
 - contextInstance.setVariable("amount", new Integer(500));
 - contextInstance.setVariable("reason", "i met my deadline");
 - // 從現(xiàn)在開始,這些變量關(guān)聯(lián)到這個流程實例了?,F(xiàn)在這些變量可以通過用戶代碼
 - // 使用在這里顯示的API來訪問了,但是,也可以在action和節(jié)點實現(xiàn)中訪問。流
 - // 程變量作為流程實例的一部分,也被存儲到數(shù)據(jù)庫中。
 - processInstance.signal();
 - // 流程變量可以通過contextInstance訪問。
 - assertEquals(new Integer(500),
 - contextInstance.getVariable("amount"));
 - assertEquals("i met my deadline",
 - contextInstance.getVariable("reason"));
 
任務(wù)分配范例
在下面的例子中我們展示如何將任務(wù)分配給用戶。因為jBPM工作流引擎和組織機構(gòu)模型的分離,一個用于計算參與者(actor)的表達式語言總是太受限了。因此,你必須指定一個 AssignmentHandler接口的實現(xiàn)類來包含計算任務(wù)的參與者的過程。
- public void testTaskAssignment() {
 - // 下面的流程基于hello world流程。狀態(tài)節(jié)點被任務(wù)節(jié)點取代。任務(wù)節(jié)點是jPDL中
 - // 的一種節(jié)點,表示一個等待狀態(tài),并創(chuàng)建在流程繼續(xù)執(zhí)行之前必須完成的任務(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>"
 - );
 - // 創(chuàng)建流程定義的一個執(zhí)行實例。
 - ProcessInstance processInstance =
 - new ProcessInstance(processDefinition);
 - Token token = processInstance.getRootToken();
 - // 下面開始流程執(zhí)行,從缺省轉(zhuǎn)換路徑離開開始狀態(tài)。
 - token.signal();
 - // signal方法將阻塞,直至流程執(zhí)行進入一個等待狀態(tài)。在這里,就是任務(wù)節(jié)點t。
 - assertSame(processDefinition.getNode("t"), token.getNode());
 - // 當(dāng)流程執(zhí)行到達任務(wù)節(jié)點,一個'換尿布(change nappy)'任務(wù)將被創(chuàng)建,
 - // NappyAssignmentHandler被調(diào)用以決定把這個任務(wù)分配給誰。
 - // NappyAssignmentHandler返回'(爸爸)papa'。
 - // 在真實的環(huán)境中,任務(wù)通過org.jbpm.db.TaskMgmtSession類中的方法從數(shù)據(jù)
 - // 庫中取得。因為我們不想在本范例中包含持久化的復(fù)雜性,我們僅僅從流程實例中
 - // 取出第一個任務(wù)實例(我們知道在這個測試場景中只有一個任務(wù)實例)。
 - TaskInstance taskInstance = (TaskInstance)
 - processInstance
 - .getTaskMgmtInstance()
 - .getTaskInstances()
 - .iterator().next();
 - // 現(xiàn)在,我們檢測任務(wù)實例是否真正被指派給'papa'.
 - assertEquals("papa", taskInstance.getActorId() );
 - // 現(xiàn)在我們假設(shè)爸爸已經(jīng)完成他的任務(wù),并把任務(wù)標(biāo)記為已完成。
 - taskInstance.end();
 - // 因為這是最后(唯一)一個要完成的任務(wù),這個任務(wù)的完成出發(fā)了流程實例的繼續(xù)執(zhí)行。
 - assertSame(processDefinition.getNode("end"), token.getNode());
 - }
 
定制action范例
action是把你的客戶java代碼綁定到j(luò)BPM流程的一種機制。action能夠關(guān)聯(lián)到它自己的節(jié)點(如果它們在流程的圖形表示中是相關(guān)的的話)。action也能夠放置在事件(例如進入轉(zhuǎn)換、離開節(jié)點、進入節(jié)點等)之中。在這種情況下,action不是流程的圖形表示的一部分,但是,當(dāng)運行時流程執(zhí)行觸發(fā)了這些事件的時候,這些action將被執(zhí)行。
- // MyActionHandler代表一個能夠在jBPM流程執(zhí)行過程中執(zhí)行某些用戶代碼的類。
 - public class MyActionHandler implements ActionHandler {
 - // 在每個test之前(在setUp方法中), isExecuted成員將被設(shè)置為false。
 - public static boolean isExecuted = false;
 - // action將把 isExecuted設(shè)置為true,使得單元測試能夠顯示action在什么時候被執(zhí)行。
 - public void execute(ExecutionContext executionContext) {
 - isExecuted = true;
 - }
 - }
 
我們從在下面的范例中將要用到的action實現(xiàn):MyActionHandler開始。這個action處理器實現(xiàn)沒有做真正有用的工作,僅僅把布爾變量isExecuted設(shè)置為true。變量isExecuted是靜態(tài)的,所以能夠同時從action處理器內(nèi)部和要校驗它的值的action中訪問。
- // 每個測試將在開始時設(shè)置MyActionHandler的靜態(tài)成員isExecuted為false。
 - public void setUp() {
 - MyActionHandler.isExecuted = false;
 - }
 
下面的例子顯示同樣的action,但是現(xiàn)在action被分別放在enter-node和leave-node事件之中。請注意與轉(zhuǎn)換只有一個事件不同,節(jié)點擁有多個事件類型,因此被放置到節(jié)點中的action應(yīng)該放在event元素之中。
- public void testTransitionAction() {
 - // 下面的流程是hello world流程的一個變體。我們把一個action加入從狀態(tài)s到結(jié)束狀
 - // 態(tài)的轉(zhuǎn)換之中。本測試的目的是顯示要把java代碼集成到j(luò)BPM流程中是多么容易。
 - 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>"
 - );
 - // 啟動流程定義的一個新的執(zhí)行實例。
 - ProcessInstance processInstance =
 - new ProcessInstance(processDefinition);
 - // 下一個信號將導(dǎo)致執(zhí)行離開開始狀態(tài),到達狀態(tài)s。
 - processInstance.signal();
 - // 這里我們顯示 MyActionHandler還沒有被執(zhí)行。
 - assertFalse(MyActionHandler.isExecuted);
 - // ... 以及主執(zhí)行路徑指向狀態(tài)s
 - assertSame(processDefinition.getNode("s"),
 - processInstance.getRootToken().getNode());
 - // 下一個信號將觸發(fā)根token的執(zhí)行。該token將取得包含action的轉(zhuǎn)換,
 - // 該action將在調(diào)用signal方法的過程中執(zhí)行。
 - processInstance.signal();
 - // 這里我們可以看到在signal方法被調(diào)用的時候, MyActionHandler被執(zhí)行了。
 - assertTrue(MyActionHandler.isExecuted);
 - }
 
- 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);
 - // 下一個信號將導(dǎo)致流程執(zhí)行離開開始狀態(tài),進入狀態(tài)s。因此狀態(tài)s被進入,action被執(zhí)行。
 - processInstance.signal();
 - assertTrue(MyActionHandler.isExecuted);
 - // 重置MyActionHandler.isExecuted。
 - MyActionHandler.isExecuted = false;
 - // 下一個信號將觸發(fā)流程執(zhí)行離開狀態(tài)s,因此action將被再次執(zhí)行。
 - processInstance.signal();
 - // 瞧……
 - assertTrue(MyActionHandler.isExecuted);
 
【編輯推薦】















 
 
 





 
 
 
 