天天看點

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流程系統中建立相關流程,提供相關流程的監控和執行接口,用戶端可以通過這些接口對流程進行操作。

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

啟動一個流程執行個體時,首先通路流程系統,取得一個新的流程執行個體ID。在業務系統中儲存這個ID。

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

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

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

如何使用

以上面購房流程為例說明,将客戶購房過程在一個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的映射機制,可以使用一個使用者賬号表,不同映射檔案,保持系統的相對獨立性。

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

如何使用

jBPM中使用者是由identity子產品提供,在實際開發中,可以以jBPM中提供的使用者表為基礎,應用系統的較詳細的使用者資訊在上面擴充。

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

也可以建立一個基礎的使用者帳号,jBPM中的使用者與應用系統中的使用者在它的基礎上擴充。

J2EE工作流管理系統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裡面取得并且在根據指定的時間來啟動該定時器

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

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。

繼續閱讀