传智播客课程——工作流管理系统和jBPM<2>_西域郎_新浪博客

    今天,在课堂上,汤阳光老师教我们大家熟悉了Jbpm,并实现了一个简单的办公流程事例,以下是课堂总结和相关实现代码。


1. 概念
<1> 流程定义:预先定义的业务流转逻辑。
  

<2> 流程实例(process instance):业务的一次实际流转过程。(是流程定义执行期间的体现,包含了流程定义被解释之后的一些信息,诸如开始时间、结束时间和其他相关联的信息)。

<3> 任务实例(task instance): 组成流程实例的元素。(Task被解释执行时的信息,
有开始时间、结束时间、参与者等,有create、start、end 三种状态)。


2. 搭建环境:
* Jbpm的API:
流程有关的所有信息都是要保存到数据库中的。jBPM的其中一个特色就是它使用Hibernate来管理它的数据库。这样,jBPM就将数据的管理职能分离出去,自己则专注于商务逻辑的处理。
<1> 添加jar包:
   jbpm-jpdl.jar(核心);
   jbpm-identity.jar(可选,组织机构);
   bsh.jar(BeanShell脚本);
   jboss-j2ee.jar;

   hibernate3.jar(Hibernate);
   antlr-2.7.6.jar(Hibernate);
   asm.jar(Hibernate);
   cglib.jar(Hibernate);
   dom4j.jar(Hibernate);
   commons-collections.jar(Hibernate);
   commons-logging.jar(Hibernate);

   hsqldb.jar(可选,纯Java写的数据库);
   log4j.jar(可选,日志记录);

   Junit4(单元测试);

<2> 添加配置文件(在jbpm-jpdl-3.2.2/config目录中):
   jbpm.cfg.xml;
   hibernate.cfg.xml;
   log4j.properties(可选,log4j配置文件);
修改数据库库连接信息,如果使用的不是HsqlDB数据库,还应添加相应的jdbc驱动。我们选用mysql数据库驱动mysql-connector-java-5.1.7-bin.jar。

<3> 生成数据库表。
选用单元测试的方式生成数据库表,以下是测试类代码:
 public void createSchema() {
  // 读取配置文件并创建数据库
 
 new Configuration().configure().buildSessionFactory();
 }


3. 编写流程定义
员工报销,由其经理审批。审批通过后,此员工到财务去领款,流程结束;如果
未经理审批通过,流程结束。经理在审批的时候应能看到这个员工要报销的金额,
根据金额做出是否通过的决定。

* 编程步骤,要实现的功能有:
    a> 部署流程定义。
    b> 启动流程。
    c> 获取任务列表。
    d> 开始任务。
    e> 结束任务。
    其中编写流程定义和步骤a是添加流程定义到系统中;步骤b,c,d,e是使用系统中存在的某个流程定义执行流程;步骤c,d,e是办理一个任务的过程,流程的执行就是一次次办理任务的过程。
    有两个重要的类:JbpmConfiguration和JbpmContext。JbpmConfiguration是jBPM的相关配置信息,并有创建JbpmContext的功能,可以把JbpmConfiguration想像成Hibernate中的Configuration与SessionFactory的结合体。使用JbpmConfiguration.getInstance()方法创建一个JbpmConfiguration对象,这会使用默认的jbpm配置文件:在classpath根目录中,并且名字为jbpm.cfg.xml。或者是使用getInstance(String configFilePath)方法,用指定的配置文件构造。
    Jbpm中几乎所有的操作都是通过JbpmContext完成的,可以把他想像成Hibernate中的Session。他是通过JbpmConfiguration.createJbpmContext()方法创建的,JbpmContext中包装有一个Hibernate的Session(通过他做的数据库操作)。使用完JbpmContext后一定要调用他的close()方法,否则所有信息都不会持久化到数据库当中。因为在调用JbpmConfiguration.createJbpmContext()方法时会创建一个Hibernate的Session并开始事务;在调用JbpmContext.close()方法时会提交事务并且关闭所关联的Session。如果在执行JbpmContext.close()之前调用方法jbpmContext.setRollbackOnly(),则在JbpmContext.close()时会回滚事务。


4. 部署流程定义
这个过程就是把流程定义的相关信息保存到jBPM的数据库中。

解析流程定义大致有三种方式,所使用的方法都在ProcessDefinition中,并且都是static的:

1)parseXmlResource,parseXmlInputStream和parseXmlReader,是解析一个xml的流程定义文件;

2)parseXmlString,是解析一个字符串;

3)parseParResource与parseParZipInputStream是解析一个par包。

ProcessDefinition.parseXmlResource(xmlResource)接受的参数是流程定义文件的路径,这个路径是相对于classpath的根路径的一个相对路径。

Process archive(par),流程档案文件,是一个zip文件。必须要有一个流程定义文件,名字为processdefinition.xml,流程档案也可以包含其他的相关文件,如processimage.jpg,gpd.xml或classes(类的字节码文件)等等,(流程定义中使用到的class要放到classpath中;或者是放到流程档案的/classes文件夹中,这样就会被流程类装载器来装载)。
以下是部署流程的实现相关代码:
 @Test
 public void deployProcessDefinition() {
  // 1, 从文件到实体
  // ProcessDefinition pd = ProcessDefinition.parseXmlString(xml);
  // 接受流程定义文件
  // ProcessDefinition pd = ProcessDefinition.parseXmlInputStream(inputStream);
  // ProcessDefinition pd = ProcessDefinition.parseXmlReader(reader);
  // ProcessDefinition pd = ProcessDefinition.parseXmlResource("first/processdefinition.xml");
  // 接受流程定义文档
  // ProcessDefinition pd = ProcessDefinition.parseParZipInputStream(zipInputStream);
  ProcessDefinition pd = ProcessDefinition.parseParResource("firstPD.zip");
  // 2, 从实体到数据库
  JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); // session
  try {
   jbpmContext.deployProcessDefinition(pd);
  } catch (Exception e) {
   jbpmContext.setRollbackOnly();
// 告诉JbpmContext在close的时候回滚
   throw new RuntimeException(e);
  } finally {
   jbpmContext.close();
  }
 }
    流程定义不应该改变,因为预测流程变化带来的所有可能的影响是非常困难的(或者说是不可能的)。围绕这个问题,jBPM有一个明智的流程版本机制。版本机制允许在数据库中多个同名流程定义共存,流程实例以当时的{zx1}版本来启动,并且在它的整个生命周期中将保持以相同的流程定义执行。当一个新的版本被部署,新的流程实例以新版本启动,而老的流程实例则以老的流程定义继续执行。

    部署流程定义时,如果存在同名的流程定义,则版本自动累加(加1);如果不存在,则版本为1。

    在部署到jBPM数据库之后改变流程定义有很多潜在的缺陷,因此非常不鼓励这样做。可以直接部署为一个新的版本。


5. 执行流程
    流程实例可以通过ProcessDefinition.createProcessInstance()方法创建,或者是使用new ProcessInstance(ProcessDefinition pd)并传递一个流程定义来创建,总之创建的流程实例一定是要是属于(使用)某个流程定义的。启动流程后不要忘了使用ProcessInstance.signal()方法离开开始状态。

    注意:流程定义的名字是processdefinition.xml文件中的根元素的name属性的值,不是流程定义文件的名字!

    任务列表:当前需要办理(未完成)的任务集合。可以通过TaskMgmtSession获得。

    开始任务的方法为:TaskInstance.start();结束任务为:TaskInstance.end(),TaskInstance.end(String transitionName);无参的end()方法是使用{dy}个transition离开节点;第二个方法是指定完成任务后使用指定的transition离开节点。一个任务实例只能开始和结束一次(再次开始或结束会抛异常)。
下面是执行流程的相关代码:
 @Test
 public void startProcessInstance() {
  JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); // session
  ProcessDefinition pd = jbpmContext.getGraphSession().findLatestProcessDefinition("first");
  // ProcessInstance pi = new ProcessInstance(pd);
  ProcessInstance pi = pd.createProcessInstance();
  jbpmContext.save(pi);
  // signal
  pi.getRootToken().signal();
  jbpmContext.close();
 }

 @Test
 @SuppressWarnings("unchecked")
 public void getTaskList() {
  JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
// session
  String actorId = "员工张三";
  // String actorId = "经理李四";
  List<TaskInstance> taskList = jbpmContext.getTaskList(actorId);
  System.out.println("\n------------------------- " + actorId + "的任务列表");
  for (TaskInstance ti : taskList) {
   System.out.println("id=" + ti.getId()//
     + ",name=" + ti.getName()//
     + ",actorId=" + ti.getActorId()//
     + ",create=" + ti.getCreate()//
     + ",start=" + ti.getStart()//
     + ",end=" + ti.getEnd());
  }
  System.out.println();
  jbpmContext.close();
 }

 @Test
 public void startTask() {
  JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); // session
  TaskInstance ti = jbpmContext.getTaskInstance(3);
  ti.start();
  jbpmContext.close();
 }

 @Test
 public void endTask() {
  JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
  JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext(); // session
  TaskInstance ti = jbpmContext.getTaskInstance(3);
  // ti.end();
 
 ti.end("to end");

  jbpmContext.close();
 }
    在员工填写报销单任务完成前,应把业务数据"报销金额"与流程关联起来,可以通过设置流程变量实现。流程变量是与流程实例关联的,并且会持久化到数据库中。不同的流程实例是互不相干的,就好比web中的session,在其中设置变量(setAttribute)是跟其它的session中的变量无关的。
    利用变量动态的设置参与者。在流程定义中指定参与者(actor-id)时,可以使用变量,变量是以"#{"开头,以"}"结束,中间的是变量名。这里的变量引用的是在执行流程时设置的流程变量。
    流程实例有了结束时间(end!=null)就表示这个流程实例结束了。任务实例如果开始时间不为null(start!=null),表示已经开始了,即已对其调用了start()方法;同样在调用任务实例的end()方法时,结束时间被填充(end!=null表示任务实例已结束)。

以下是一些实体与表的对应关系:
+-------------------+----------------------+-----------
       PO               table          实体
+-------------------+----------------------+-----------
|ProcessDefinition  |jbpm_processdefinition|  流程定义
+-------------------+----------------------+-----------
|ProcessInstance    |jbpm_processinstance  流程实例
+-------------------+----------------------+-----------
|TaskInstance       |jbpm_taskinstance     任务实例
+-------------------+----------------------+-----------
|VariableInstance   |jbpm_variableinstance |  流程变量
+-------------------+----------------------+----------- 

郑重声明:资讯 【传智播客课程——工作流管理系统和jBPM<2>_西域郎_新浪博客】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——