J2EE工作流管理系統jBPM詳解(二)

2008-11-21 作者:王鐵民 來源:51CTO.com

子流程的使用

成果介紹

詳細闡述開發成果
評審標準:清楚介紹開發成果

當一個流程的業務邏輯非常複雜的時候,可以考慮使用子流程。子流程和主流程是相對獨立的。

設計思路

描述主要的設計思路,開發方法以及技術要點
評審標準:清晰表達設計思路和技術要點

在jbpm中,我們可以將一個複雜的業務流程文件根據業務邏輯的不同劃分爲父流程和子流程,這樣一方面可以令我們的流程定義文件不會設計得太臃腫,二來可以方便我們將來的維護,只對需要修改的流程進行修改,而不影響其他流程。

如何使用

闡述如何結合項目需要應用成果進行開發。這部分需要詳細描述,讓其他開發人員按照此成果報告,能夠進行一般簡單的開發,具有較強的可操作性。
評審標準:開發人員按此使用說明基本能應用成果進行開發

這裏我們介紹下關於jbpm子流程的使用,這裏我們定義兩個流程定義xml文件,一個是父流程定義文件,一個是子流程定義文件。這裏我想當執行完Payfirst任務的時候,jbpm流程能自動去我的子流程文件中去執行那邊定義的任務。

這裏是父流程processdefinition.xml
<?xml version="1.0" encoding="UTF-8"?>

<process-definition xmlns="urn:jbpm.org:jpdl-3.2" name="myapp">
。。。。。。
<task-node name="PayFirst">
<task name="PayFirstTask" swimlane="finance"></task>
<transition name="get house contract" to="subprocess">
<action name="action"
class="com.myapp.action.MessageActionHandler">
<message>
Has pay first bulk of money. Print constract now!
</message>
</action>
</transition>
</task-node>
<process-state name="subprocess">
<sub-process name="subprocessdefinition"/>
<transition to="end"></transition>
</process-state>
   <task-node name="pass round for perusal" 
signal="last-wait" create-tasks="false">
      <task name="perusal">
      <assignment actor-id="#{processStarter}">
</assignment>
      </task>
      <event type="node-enter">
      <action name="createInstance" 
class="com.myapp.action.CreateTaskInstanceAction"></action>
      </event>
      <transition name="backto" to="OnePersonAudit">
</transition>
   </task-node>

</process-definition>

可以看到,上面我們使用到了,在jbpm中,process-state標籤代表的是引用子流程。這裏我們接着定義子流程文件。

子流程subprocessdefinition定義文件

<?xml version="1.0" encoding="UTF-8"?>

<process-definition xmlns="urn:jbpm.org:jpdl-3.2" 
name="subprocessdefinition">


<swimlane name="service">
<assignment actor-id="service1" />
</swimlane>

<start-state name="subStart">
<transition to="PrintContract"></transition>
</start-state>


<task-node name="PrintContract">
<task name="PrintContractTask" swimlane="service"></task>
<transition name="PrintContract" to="end">
<action name="action"
class="com.myapp.action.MessageActionHandler">
<message>Finish the process instance now.</message>
</action>
</transition>
</task-node>

<end-state name="end"></end-state>
</process-definition>

示例實現

結合項目需要實現,採用開發成果開發一個簡單應用示例。可以鏈接到其它文檔,如示例實現的項目工程

評審標準:能簡單展示開發成果的開發應用

上面我們定義了兩個XML文件,一個是父流程,一個是子流程。下面我們說下如何使用這兩個文件。首先我們要先部署這兩個文件,使用子流程要注意,部署的時候一定要先部署子流程,然後在部署父流程。

ProcessDefinition subProcess = ProcessDefinition.parseXmlResource
("subprocessdefinition/processdefinition.xml");
jbpmContext.deployProcessDefinition(subProcess);
ProcessDefinition processDefinition = ProcessDefinition.
parseXmlResource("processdefinition.xml");
jbpmContext.deployProcessDefinition(processDefinition);

部署完後,jbpm會將這兩個流程定義文件保存在jbpm_processinstance表中,在調用中,與單個流程文件調用沒有任何區別,我們調用PrintContract 任務的end()方法,jbpm會根據的流程文件,自動找到子流程文件所定義的任務進行執行。

使用規範

描述使用該技術的規範(如接口設計、接口實現、框架設計、數據結構設計等)、約定、約束等
評審標準:清晰、詳細描述出其應用規範

注意事項

描述配置、開發等需要注意的問題,包括各種關鍵點和難點。可逐步補充
評審標準:開發過程中遇到的關於應用開發成果開發的問題,大部分都可以從這裏找到答案

使用子流程要注意:

要先部署子流程,然後再部署主流程,否則,主流成執行的時候會報找不到子流程的異常

直接查看jbpm_Token或者jbpm_log無法找到流程間的關係,需要查看jbpm_processinstance表,才能找到父流程,因爲 Token在離開process state的時候就會刪除subprocessid,直接看jbpm_log也無法看出兩個token之間的關係。

應用系統與jBPM的結合

成果介紹

在實際開發使用jBPM,可以採用jBPM系統與業務系統完全分離的策略。jBPM系統只負責流程的監控和執行,業務的重心仍然是實際業務需求。

設計思路

客戶端訪問系統時,一切業務相關的操作在業務系統中實現,需要流程監控的業務在jBPM流程系統中建立相關流程,提供相關流程的監控和執行接口,客戶端可以通過這些接口對流程進行操作。

啓動一個流程實例時,首先訪問流程系統,取得一個新的流程實例ID。在業務系統中保存這個ID。

在進行流程監控和執行時,根據這個ID對流程實例進行操作。

如何使用

以上面購房流程爲例說明,將客戶購房過程在一個Order中進行處理。

客戶登記看房,啓動一個流程實例,取得流程ID,保存在Order中
銷售人員,銷售經理,財務人員,都可以通過流程系統提供的API查找當前任務,執行任務時,一方面執行流程,一方面修改Order記錄。

示例實現

Order要記錄流程ID。
public class Order implements Serializable {
private Long id;
private Long processId;
}

流程和業務系統的接口爲OrderManager 和BpmManager。
客戶看房登錄時先啓動一個流程。

BpmManager bpmManger=...;
Long processId=bpmManager.createProcess();
Order order=new Order();
order.setProcessId(processId);
session.save(order);

在後面的步驟中,可以根據Order的processId取得流程ID,執行流程任務。
bpmManager.executeProcessTask();
......
session.update(order);
......

注意事項

應用系統中用戶角色如何與jBPM結合

成果介紹

應用系統中的用戶應該與jBPM流程系統中一致,必須統一起來才能使用。一方面可以採用用戶帳號同步的策略,從業務系統複製必要的用戶信息到jBPM流程系統中,另一方面可以使用共用用戶賬號的策略,保持最基本的用戶賬號獨立性,業務系統從最基本的用戶賬號上擴展用戶信息。

設計思路

由於兩個系統中用戶在需求上有一定的差別,得益Hibernate的映射機制,可以使用一個用戶賬號表,不同映射文件,保持系統的相對獨立性。

如何使用

jBPM中用戶是由identity模塊提供,在實際開發中,可以以jBPM中提供的用戶表爲基礎,應用系統的較詳細的用戶信息在上面擴展。

也可以建立一個基礎的用戶帳號,jBPM中的用戶與應用系統中的用戶在它的基礎上擴展。

示例實現

jBPM中User提供了幾最基本的字段。

public class User extends Entity implements Principal {

  private static final long serialVersionUID = 1L;
  
  protected String password = null;
  protected String email = null;
  protected Set memberships = null;
 
  public User() {
  }

  public User(String name) {
    super(name);
}
}

Hibernate映射文件內容爲:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping auto-import="false" default-access="field">
  <class name="org.jbpm.identity.User" 
         table="JBPM_ID_USER"
         discriminator-value="U">
    <id name="id" column="ID_"><generator class="native" /></id>
    <discriminator type="char" column="CLASS_"/>
    <property name="name" column="NAME_"/>
    <property name="email" column="EMAIL_"/>
    <property name="password" column="PASSWORD_"/>
    <set name="memberships" cascade="all">
      <key column="USER_" />
      <one-to-many class="org.jbpm.identity.Membership" />
    </set>
 <set name="permissions" cascade="all" table="JBPM_ID_PERMISSIONS">
      <key column="ENTITY_" foreign-key="none" />
   <element type="org.jbpm.identity.hibernate.PermissionUserType">
        <column name="CLASS_"/>
        <column name="NAME_"/>
        <column name="ACTION_"/>
      </element>
    </set>
  </class>
</hibernate-mapping>

這裏,應用系統用戶爲CustomUser,這裏採用從jBPM中的User中繼承的策略,它多出一個字段carId。

public class CustomUser extends User {

private String cardId;

public String getCardId() {
return cardId;
}

public void setCardId(String cardId) {
this.cardId = cardId;
}

}

映射文件爲:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping auto-import="false" default-access="field">
<subclass name="com.sample.model.CustomUser"
extends="org.jbpm.identity.User" discriminator-value="U">
<join table="CUSTOM_USER">
<key column="ID_"></key>
<property name="cardId" column="CARDID_" />
</join>

</subclass>
</hibernate-mapping>

這裏,CustomUser是從jBPM中的User繼承的。

jBPM當前版本的穩定性評估

成果介紹

通過官方jbpmRoadMap以及jbpm jira上面所寫的計劃,得出目前jbpm的版本更新速度將會比較頻繁,計劃上說將在今年年底完成jbpm4.0,目前版本已經更新到jbpm3.2.1版本,而且從jira上發現jbpm3.3的版本也在快速開發中。而且從jira上看,目前版本升級主要是bug的修改和功能的完善。

流程執行步驟耗時閥值和自動提醒設置

成果介紹

Jbpm內置調度功能, jbpm的調度部分分爲2塊,timer主要是流程設計人員的工作,將timer放置到流程中;scheduler是jbpm自己維護的,我們只需要在後臺進行調用即可。

設計思路

流程執行可以建立或刪除定時器. 定時器存放在一個timer store裏. 當一個定時器的運行必須先從timer store裏面取得並且在根據指定的時間來啓動該定時器

Jbpm時間管理思路整體來說實現的非常清晰:

1、引擎解析流程定義xml時,給相應的事件掛接上create-timer 和 cancel-timer動作

2、流程實例實際運轉時,create-timer動作在相應事件觸發時執行

3、create-timer在job表裏插入相應時間job記錄,給該job記錄附上計算完畢的執行時間

4、JobExecutorServlet在後臺啓動一到多個JobExecutorThread線程

5、JobExecutorThread線程不停的每隔一段時間對job表掃描一次,找出需要執行的job記錄,執行之

6、只執行一次的job記錄,執行完畢後刪除之;重複執行的job記錄,寫入新的執行時間,更新之

7、相應事件觸發cancel-timer動作,將對應job記錄從job表裏刪除

如何使用

jBPM通過定時器(timer)實現日程調度。在node中加入timer元素,即可實現基於定時器的節點執行監控,實現自動提醒功能。

jbpm提供了2種調用scheduler的方法:

一種是用在web應用的,採用org.jbpm.scheduler.impl.SchedulerServlet,具體的方法這個類的javadoc有很好的示例,我們只需在web.xml中加載它就行了;
另一種是針對的c-s程序,jbpm提供了一個很好的示例

org.jbpm.scheduler.impl.SchedulerMain,我們可以參照它編寫我們自己的Scheduler。

實例實現

最容易的方法指定一個定時器是在節點裏加入定時器元素.
運用action的timer的例子

<state name='catch crooks'>   
  <timer name='reminder' duedate='3 business hours' repeat='10 business minutes'   
      transition='time-out-transition' >   
    <action class='the-remainder-action-class-name' />   
  timer>      
state> 

運用script的timer的例子

<state name='catch crooks'>   
  <timer name='reminder' duedate='3 business hours' repeat='10 business minutes'   
      transition='time-out-transition' >   
    <script>System.out.println(new Date())script>     
  timer>      
state>   

在上例中,一旦流程實例運行進入state 'catch crooks',定時器reminder即被創建。該定時器延遲3 business hours開始執行動作,每10 business minutes重複執行一次,到期後馬上執行action類中的Java代碼,然後實施time-out-transition(或script打印時間)遷移。

通過在事件的action中加入create-timer和cancel-timer動作,可以分別實現事件對定時器的創建和取消。

定時器timer可以被用於decision fork join node process-state state super-state task-node等節點,可以設置開始時間duedate和頻率repeat,定時器動作可以是所支持的任何動作元素,如action或script,會運行我們設置的動作。定時器通過動作創建和取消,有兩個動作元素create-timer和cancel-timer。事實上,默認的定時器元素只是create-timer動作依附於node-enter事件、cancel-timer動作依附於node-leave事件的一個簡略表示。
說說整個過程:

  1. 令牌進入節點catch crooks
  2. timer被觸發(實際這時是在執行create-timer動作)
  3. 3 business hours後 timer 事件觸發
  4. 定義的action被執行
  5. 令牌順着time-out-transition路徑離開catch crooks節點
  6. cancel-timer動作被執行即timer終止(沒有給repeat的機會)

另注: 運用timer要先啓動scheduler,如果是web項目則只要在web.xml中配置JbpmThreadsServlet,這樣在項目啓動後會自動開啓scheduler。

JbpmThreadsServlet配置如下:         

<!-- JbpmThreadsServlet -->   
<servlet>   
<servlet-name>JbpmThreadsServletservlet-name>   
<servlet-class>org.jbpm.web.JbpmThreadsServletservlet-class>   
<load-on-startup>1load-on-startup>   
servlet>   
<servlet-mapping>   
<servlet-name>JbpmThreadsServletservlet-name>   
<url-pattern>/threadsurl-pattern>   
servlet-mapping>    

注意事項

對time節點來說 name、repeat、transition都是可選屬性。對一個流程定義來說,每一個time節點的name必須唯一,如果你不定義name屬性,引擎會默認把node節點的name賦給timer。在上面這個例子裏,如果你不定義timer節點的name,則它的name就會是catch crooks。說說repeat屬性,如果你不定義它,則timer就會只執行一次動作不會重複執行。transition屬性,如果定義了這個屬性,流程令牌會在timer執行動作完畢後,順着這個路徑離開node節點。所以在上面這個例子裏,儘管定義了repeat屬性,action還是會只執行一次。

action節點,可選,即timer節點在時間到時執行的動作,可以是任意action類型,包括script。注意與時間有關的兩種action類型:create-timer 和 cancel-timer。其實一個timer節點在被引擎解釋時就是被分解爲create-timer 和 cancel-timer兩個action,create-timer掛接到node-enter事件中,cancel-timer掛接到node- leave事件中。action節點最多只可以掛一個。

傳閱功能的實現

成果介紹

傳閱功能是管理系統中比較常見的一個功能,這裏使用jbpm實現該功能。

設計思路

這裏通過使用jbpm的transition來實現傳閱功能。

如何使用

關於jbpm的transition使用很簡單,大家可以參考jbpm用戶指南

示例實現

<task-node name="Coding">
<task name="Coding"  swimlane="programmer"/>

<transition name="to_CodeReview" to="Code Review">
</transition>

<transition name="to_IntegratedTest" to="IntegratedTest">
</transition>

</task-node>


<task-node name="Code Review">
<task name="Review Code" swimlane="manager"/>

</task>

<transition name="to_Coding" to="Coding"></transition>

</task-node>

上面是一個“代碼檢查”的類似傳閱的流程,程序員編寫完代碼之後需要傳給manager進行代碼審查,manager審查完畢需要發回給程序員。

動態指定執行者

成果介紹

上面講了傳閱功能的實現,但大家可以發現,上面的例子只能傳閱給流程定義xml文件上面指定的人閱讀,即不能是吸納動態指定傳閱。如果不能動態指定執行者,則上面的實現意義不大,在實際操作中,很多操作都充滿了不確定性,即可能執行者會經常改變。這裏我們介紹如何給任務動態指定執行者。

設計思路

這裏我們是通過jbpm的ActionHandler操作動態指定執行者的操作,當進入該任務節點的時候,我們可以通過爲該任務指定一個action操作,該操作根據業務規則進行任務執行者的動態指定。

如何使用

我們可以在一個任務task節點使用assignment標籤指定運行該任務的執行者,如果沒指定的人則不能執行該任務,另外我們也可以通過action操作來在程序中動態設置assignment中的執行人來實現,這裏可以是一個或多個執行人。

示例實現

首先我們將流程在processdefinition.xml定義,示例如下:

<?xml version="1.0" encoding="UTF-8"?>

<process-definition xmlns="urn:jbpm.org:jpdl-3.2" name="myapp">
... ...


<task-node name="OnePersonAudit">
<task name="OnePersonAuditTask" swimlane="manager">
<controller>
<variable name="pass" access="read,write,required"></variable>
</controller>
</task>
<!-- event type="node-leave">
<action name="createInstance"
 class="com.myapp.action.CreateTaskInstanceAction">
</action>
</event-->
<transition name="OnePersonAduit" to="IsAgreeAduit" />
  <transition name="perusaltoone" to="pass round for 
perusal"></transition>
</task-node>

   <task-node name="pass round for perusal" 
signal="last-wait" create-tasks="false">
      <task name="perusal">
      <assignment actor-id="#{processStarter}"></assignment>
      </task>
      <event type="node-enter">
<action name="createInstance" 
class="com.myapp.action.CreateTaskInstanceAction">
</action>
      </event>
      <transition name="backto" to="OnePersonAudit"></transition>
   </task-node>

</process-definition>

上面我們有個任務OnePersonAudit,裏面有個transition爲perusaltoone,它指向任務pass round for perusal,這裏是多人傳閱的一個流程,在pass round for perusal任務節點中我們使用來指定該任務的執行者, 我們還在該任務中使用了

<event type="node-enter">
<action name="createInstance" 
class="com.myapp.action.CreateTaskInstanceAction"></action>
</event>

事件類型“node-enter”表示當進入該任務時執行CreateTaskInstanceAction類的操作,我們在該類中動態設定該任務的執行者

CreateTaskInstanceAction的代碼如下:

public class CreateTaskInstanceAction implements ActionHandler {

public void execute(ExecutionContext executionContext) throws Exception {
// TODO Auto-generated method stub
System.out.println("************************************");
System.out.println( "    CreateTaskInstanceAction       " );
System.out.println("************************************");

Token token = executionContext.getToken();
TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
TaskNode taskNode = (TaskNode) executionContext.getNode(); 
Task task= taskNode.getTask("perusal");
tmi.createTaskInstance(task, token).setActorId("mytest1");  
tmi.createTaskInstance(task, token).setActorId("mytest2");  
tmi.createTaskInstance(task, token).setActorId("mytest3"); 

}
}

與SSH框架整合

SSH(Struts+Spring+Hibernate)是一種流行的web開發框架。在SSH使用是jBPM,可以考慮使用springmodules的提供的集成方案,在類的管理上會帶來一些便利。

在Spring配置文件中聲明jbpm使用。

<!-- jBPM Configuration -->
<bean id="jbpmConfiguration"
class="org.springmodules.workflow
.jbpm31.LocalJbpmConfigurationFactoryBean">
<!-- pass in existing sessionFactory -->
<property name="sessionFactory" ref="sessionFactory" />
<property name="configuration"
value="classpath:/org/appfuse/jbpm/jbpm.cfg.xml" />
<property name="processDefinitions">
<list>
<ref local="testProcess" />
</list>
</property>
<!--property name="createSchema" value="true" /-->
</bean>

<bean id="testProcess"
class="org.springmodules.workflow.jbpm31
.definition.ProcessDefinitionFactoryBean">
<property name="definitionLocation"
value="classpath:org/appfuse/jbpm/process/testprocess.xml" />
</bean>

<bean id="jbpmTemplate"
class="org.springmodules.workflow.jbpm31.JbpmTemplate">
<constructor-arg index="0" ref="jbpmConfiguration" />
<constructor-arg index="1" ref="testProcess" />
</bean>

jbpmConfigration依賴的sessionFactory使用SSH的中配置的sessionFactory。

現在就可以像使用Hibernate一樣使用jBPM。

發佈了31 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章