`
CaptainCook
  • 浏览: 136718 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

jBPM工作流应用

    博客分类:
  • J2EE
阅读更多

1. 工作流
  <1> 工作流(Workflow):就是自动运作的业务过程部分或整体,表现为参与者对文件、信息或任务按照规程采取行动,并令其在参与者之间传递。简单地说,工作流就是一系列相互衔接、自动进行的业务活动或任务。工作流是针对工作中具有固定程序的常规活动而提出的一个概念。通过将工作活动分解成定义良好的任务、角色、规则和过程来进行执行和监控,达到提高生产组织水平和工作效率的目的。
  <2> 工作流就是工作流程的计算机化,即将工作流程中的工作如何前后组织在一起的逻辑和规则在计算机中以恰当的模型进行表示并对其实施计算。工作流要解决的主要问题是:为实现某个业务目标,在多个参与者之间,利用计算机,按某种预定规则自动传递文档、信息或者任务。
  <3> 通俗的说,就是多个人在一起合作完成某件事情。
  <4> 工作流可实现业务或公文的申请、审批、会签、登记、操作等环节的管理,可将工作过程进行记录,便于日后检查。并实现数据的规范化录入、查询、统计和存档。OA(办公自动化)主要技术之一就是工作流。


2. 工作流管理系统(Workflow Management System, WfMS)
  <1> 主要功能是通过计算机技术的支持去定义、执行和管理工作流,协调工作流执行过程中工作之间以及群体成员之间的信息交互。工作流需要依靠工作流管理系统来实现。
  <2> 工作流管理系统是定义、创建、执行工作流的系统,应能提供以下三个方面的功能支持:
    ① 定义工作流:包括具体的活动、规则等;
    ② 运行控制功能:在运行环境中管理工作流过程,对工作流过程中的活动进行调度;
    ③ 运行交互功能:指在工作流运行中,WfMS与用户(活动的参与者)及外部应用程序工具交互的功能。


3. JBPM,全称是Java Business Process Management
<1> JBPM是一种基于J2EE的轻量级工作流管理系统,jBPM是公开源代码项目, 官方主页为:
http://www.jboss.org/jbossjbpm
<2> Jbpm的使用
** 步骤
* 编写流程定义(定义规则);
* 部署流程定义到系统中;
* 执行流程(使用指定的流程定义);

① 编写流程定义
举例:员工报销费用的流程描述:员工需要先填写报销单据;交给其经理进行审批;审批(通过)后到财务去领款。

以上流程由三个步骤组成,每个步骤即是一个任务,分别由一个角色执行。应该使用jBPM提供的JPDL(jBPM Process Definition Language)来描述这个流程,这样使用了jBPM的系统才能看的懂。

jPDL:是一种直观的流程语言,可以使用任务、等待状态、定时器、自动化动作等等图形化地表示流程。jPDL认为一个商务流程可以被看作是一个UML状态图。
jPDL就是详细定义了这个状态图的每个部分,如起始、结束状态,状态之间的转
换等。(就是写一个xml文件)

② 使用 jbpm 提供的 jbpm console web 应用程序测试/使用流程
jBPM Console web 是由 jBPM 提供的一个web应用。它是一个管理和监控流程的控制台,用于检查和操纵运行的流程实例。

双击 server/start.bat 启动jbpm提供的jboss服务器,要访问地址为:http://localhost:8080/jbpm-console/

部署流程定义到 jbpm-console 中时的注意事项:
A)要求流程定义文件的名字为processdefinition.xml,并且在一个zip压缩文件的根目录中;
B)存放流程定义的zip文件所在的路径中不能含有中文(因为jbpm-console应用没有处理中文)。

③ 执行流程
部署流程定义后,就可以使用这个流程定义了(执行流程)。在开始一个流程后,要先进入到 tokens 页面中点击 Signal 链接,这样才能使流程离开开始状态,以后的步骤中都不需再做这个操作。当流程执行到有任务的节点时,就可以看到当前节点中定义的任务了。任务的办理分为两步:开始和结束。
* 流程实例通过Token的维护当前正在执行的节点
Jbpm是怎么知道一个流程实例现在走到哪个节点了呢?每个流程实例都有一个指针,指向当前正在执行的节点,在当前节点执行完后,到达了下一个节点,就是让这个指针指向下一个节点。这个指针叫做Token(也叫做令牌)。

Token是运行时的概念,用来维护指向图结点的指针。Token需要一个信号(signal)来让流程向前执行。当一个signal来到的时候,Token就会通过transition离开当前的结点走到下一个结点。


4. jBPM提供了一个图形化的流程设计器,是一个eclipse插件。
<1> 使用插件设计流程定义还可以生成图片,这样就可以实现流程的图形化监控。

<2> 使用插件设计完流程定义并保存后,会有三个文件:A)processdefinition.xml,流程定义文件;B)processimage.jpg,流程图片;C)gpd.xml,保存流程图片中的各个节点的坐标信息。

<3> 插件安装方法:在Eclipse安装目录下的links目录中新建一个文本文件,把扩展名改为.link,并且修改内容为path=${插件中的eclipse文件夹所在的路径}。

<4> 使用插件时会遇到的几个问题
① 插件在eclipse3.3的版本上才能正常工作,所以myeclipse5.1(eclipse3.2)不能使用这个插件。

② 安装插时,如果 Myseclipse安装目录下的eclipse目录中没有links目录,可以手工创建一个。还要注意指定的插件的路径中不能含有中文。

③ 使用插件设计流程定义时,如果再点击插件下方的 "Source" 选项卡修改生成的processdefinition.xml,会出现突然少了一些内容或多出一些内容等情况,这是插件的问题。如果想修改流程定义源文件,可以使用普通的xml编辑器打开进行修改,方法是在文件上点击右键 -> Open With -> MyEclipse XML Editor。

④ 如果节点的名字中有中文,则生成的gpd.xml中的节点名字会出现乱码,这会导致不能正确的进行图形化监控。修正方法:在部署流程定义前把gpd.xml文件的编码改为GBK(改为本地编码,也要修改xml文件中的encoding的值为GBK)。要注意如果修改并保存了一下流程定义,就会重新生成processimage.jpg和gpd.xml文件,这时gpd.xml中的encoding又变成了UTF-8,应再改为GBK。(这是一个Bug)。

 

 

 

 

 

 

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有一个明智的流程版本机制。版本机制允许在数据库中多个同名流程定义共存,流程实例以当时的最新版本来启动,并且在它的整个生命周期中将保持以相同的流程定义执行。当一个新的版本被部署,新的流程实例以新版本启动,而老的流程实例则以老的流程定义继续执行。

 

 

    部署流程定义时,如果存在同名的流程定义,则版本自动累加(加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()方法是使用第一个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 |  流程变量
+-------------------+----------------------+----------- 

 

 

 

 

 

 

 

 

1. 节点
不同的节点类型代表不同的行为。

 

<1> start-state
    开始节点,标识流程开始边界。开始状态有且只有一个,就是说流程中不能有任何
transition指向开始节点。
    在流程实例被创建后(启动),RootToken(根令牌)就指向start-state,并且处于等待状态,需要给Token发信号(signal)才能使流程继续执行。

<2> end-state
    结束节点,标识流程的结束边界。不需要transition元素。

<3> task-node
    任务节点,可以包含任意数量的Task(任务)。当执行到一个任务节点的时候,将会根据这个task-node中的task的定义,创建相同数量的TaskInstance(为每一个Task都创建一个任务实例,并且分配给任务定义中指定的参与者)。然后,这个task-node一直处于等待状态,等到这些任务实例都被完成后,才会离开当前的节点,到达下一个节点。
    调用TaskInstance.end(String transitionName)时,如果这个任务实例不是当前任务节点中的最后完成的那个,那这个方法就只是完成任务而已,并不会使用这指定的transition离开节点。就是说:只有当这个任务实例完成后,Token就要离开当前节点,到达下一个节点时,指定的transitionName才有作用。
    如果一个task-node中没有定义任务,执行时,一进入这个节点就会马上离开,即Token不会在这个task-node上停留,因为他发现所有的任务实例都执行完了(任务实例的数量为0)。

<4> state
    状态节点,这是一个等待节点。当执行到达这个节点的时候,就会处于等待状态,须给token发一个信号(signal)才能使流程继续执行。

<5> decision
    决策节点,用于计算流向。有两种计算方式:
  1) 在decision中使用DecisionHandler,或使用一个表达式。
     (都是返回要使用的transition的name);
  2) 在decision中的transition元素中使用boolean表达式。这样将使用表达式计算结果为真的那个transition离开节点。
     ① 只有decision节点中的transition元素的的condition可以计算。在其他节点是不起作用的!
     ② 如果有多个transition的condition计算为true,将会使用第一个结果为true的transition(按定义中的顺序);
     ③ 如果所有的transition的condition的值都为false,则使用第一个。

        如果以上方法同时使用,调用的顺序为:
        如果有decisionDelegation,则使用它的计算结果。否则,
        如果有decisionExpression,则使用它的计算结果。否则,
        如果使用decisionConditions(trasnition中的表达式)的计算结果。

<6> expression(表达式)
和我们在jsp中使用的el表达式相似,只是${}改为#{}。

表达式中可以直接使用已设置的流程变量,并且可以直接使用以下变量:
  taskInstance
  processInstance
  processDefinition
  token
  taskMgmtInstance
  contextInstance

<7> fork / join
    分支节点与合并节点。分支节点的作用是将单个执行流程分裂成多个并发的执行流程。默认的行为是为每个子流程生成一个子令牌,并建立子令牌和主流程令牌之间的父子关系。此时,父令牌指向fork节点,子令牌指向各个分支节点。
    合并节点将分支收拢。当所有的分支(从同一个fork中出来的)都到达该节点的时候,join结点将结束这些分支上的子token,并通给他们的父Token发一个信号离开join节点。如果只有分支中的部分token到达时,join结点将处于等待状态。
    需要注意的是:fork和join节点要成对的出现。
    fork节点中的transition都要指定名字。否则。用程序运行没有问题,用jbpm-console运行时执行到这个fork节点的时候就会抛空指针异常。

<8> node
    可定制的节点。可以在node元素中指定一个Action子元素,可以指定一个实现了Actionhandler接口的类,用这个类可定制这个节点的行为。这时,如是果想让流程继续执行,需要给token发一个信号。
    如果node中没有action子元素,他的默认行为是一到达就离开了,不作停留。


2. Action
    动作,是一段代码,在指定的情况下被执行。

    属性:
      class   :指定处理类;
      name    :action的名字;
      ref-name:所引用的action的名字;

    一个action是一段java代码,用来引入附加的处理逻辑。可以放在node节点中,也可以放在event(事件)中,用来辅助当前节点完成业务逻辑。

    Action是一种在图形表示之外增加更多技术细节的机制。可以让java代码在不修改图结构的情况下和图关联起来。


3. script 与 beanshell
    beanshell,可以直接使用java类。声明变量时可以不指定类型而直接使用。可以在写脚本时当成是写Java代码(可以透明的使用Java的API)。

    script就是使用的beanshell。script也可以指定一个name属性,可以在配置action元素的时候指定ref-name为此script的name,代表引用这个动作。

以下变量在Script中可以直接引用:
  executionContext
  token
  node
  task
  taskInstance

提示:能使用action的地方一般也能使用script;
      配置script时可用<![CDATA[ ]]>包围内容。


4. event
    事件。

    每个事件有一个动作(action)清单。当jBPM引擎产生一个事件,动作(action)清单就会被执行. 不同的节点支持的事件类型不同,是由event元素所在的节点的类型决定的,例如transition只有一个事件。可以在event元素用可以指定一个动作,当指定的事件发生时,这个动作被执行。可以给同一个事件指定多个动作,当这个事件触发的时候,这些动作执行的顺序和定义先后顺序是一致的。

    不同元素支持不同的事件类型:
一般的节点都具有的事件:node-enter,node-leave;
start-state只有node-leave;
end-state只有node-enter;
transition只有一个执行转换的事件(taking a transition)
       (所以配置时不用写event元素,而直接配置Action)
task有task-create,task-assign,task-start,task-end。

关于哪些元素支持哪些事件,可以通过文档的18.4节中的xml文件的写法中获得。

如果配置的事件类型不存在,不会报错,也不会执行。

注意:在事件中定义的动作不应该影响流程的执行。即不要在事件的动作中给
token发信号,否则会抛异常。

task-node的singal属性可取的值:
last: 默认值.当last实例被完成时候获得执行.当在这个入口没有任务被生成时候,执行继续.
first: 当first实例被完成时候获得执行. 当在这个入口没有任务被生成时候,执行继续.
never: 执行永远不继续, 不管任务是否建立或依然没有完成.
unsynchronized: 执行一直继续,不管任务是否没建立或依然没有完成.
last-wait: 当last实例被完成时候获得执行.当这个节点入口没有任务被建立时,任务节点执行等待直到任务被建立.
first-wait: 当first实例被完成时候获得执行.当在这个入口没有任务被生成时候,执行继续.

last-wait和first-wait的wait是分开来理解的,wait是指的等待任务实例的创建,而
到达task-node后有任务实例被创建了,wait就没有影响了,这时task-node的行为
就是wait前面那个'first'或'last'决定了,即执行完第一个任务实例就离开节点
还是所有任务实例都执行完才离开节点.

在演示last-wait时,要用create-tasks属性配合(设置他的值为false,就是不创建任务实例).
在设置为last-wait或first-wait时,在task-node元素中没有配置任务,流程执行到这个节点时也会等待.


5. 动态的创建不确定数量的任务实例
实现任务分配给多个人,需要做以下工作:
1, 阻止jBPM自动创建任务实例(设置task-node的create-tasks="false");
2,在流程定义中定义的相应的任务,不指定参与者;
3,在node-enter事件中定义一个动作指定用于创建TaskInstance的类。

创建任务实例要调用方法:
  TaskMgmtInstance.createTaskInstance(Task, ExecutionContext);
其中的Task 是任务的定义,可以先得到当前的节点:
  TaskNode taskNode = (TaskNode) executionContext.getNode();
然后通过任务的名字得到任务的定义:
 
Task task = taskNode.getTask("审批");

<!-- -->
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics