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。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织