JBPM简析-part1

JBPM4.2的源代码和单元测试用例差不多有些日子了,有了一些自己的心得和体会,一一记录下来和大家分享。错误和不足之处,欢迎大家指出。我的邮箱地址是cndoublehero#gmail.com

    JBPM4.2(以下都简称JBPM了)相关知识介绍:

    HibernateJBPM使用了hibernate作为其持久层的底层框架。好处就是JBPM可以在多种不同的数据库下运行,坏处就是对Hibernate不熟悉的同学查看JBPM代码时对JBPM操作数据库这块上手比较困难。本文我们主要讲解JBPM的流程运转机制,基本不涉及Hibernate的知识。

    小提示:JBPM提供了一整套的可运行的单元测试用例,所有的用例均放置在example文件夹下。我们可以将这个文件夹下的文件拷贝到我们的项目中,通过运行调试这些单元测试用例来逐渐熟悉JBPM现有的功能。在我们在运行或者调试JBPM提供的单元测试用例时,可以在jbpm.hibernate.cfg.xml文件中添加如下的内容,这样控制台就可以打印出JBPM在运行过程中产生的sql

<property name="hibernate.show_sql">true</property>

    工作流本质核心的东西就是流程运转机制,即一套工作流程是如何向下推进的。具体来说,即是工作流在执行完定义好的一个节点后,流程引擎采用何种方式往此节点之后定义的节点推进的。

JBPM中所有的流程运转都围绕着ExecutionImpl这个类的对象,此对象保存了流程执行中的所有信息,流程如何向后流转也完全是倚靠此对象。在下文中,我们称此ExecutionImpl类型的对象为流程上下文对象,称ActivityImpl类型的对象为活动对象。

首先我们从JBPM提供的单元测试用例中抽取出一个具体的单元测试用例。通过分析调试此单元测试用例,我们逐渐了解JBPM的流程运转机制。

xml配置文件如下所示:

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

 

<process name="StateChoice" xmlns="http://jbpm.org/4.2/jpdl">

 

  <start g="16,60,48,48">

    <transition to="wait for response"/>

  </start>

 

  <state name="wait for response" g="96,58,109,52" >

    <transition name="accept" to="submit document" g="148,34:3,-15" />

    <transition name="reject" to="try again" g="151,133:3,2" />

  </state>

 

  <state name="submit document" g="238,8,114,52" />

  <state name="try again" g="238,108,114,52" />

 

</process>

对应的流程图如下所示:

JBPM简析-part1

单元测试用例(此单元测试类继承了JBPM提供的JbpmTestCase)如下所示

public void testStateChoiceReject() {

    ProcessInstance processInstance = executionService

        .startProcessInstanceByKey("StateChoice");

   

    String executionId = processInstance

        .findActiveExecutionIn("wait for response")

        .getId();

   

    processInstance = executionService.signalExecutionById(executionId, "reject");

 

    assertTrue(processInstance.isActive("try again"));

  }

JBPM中通过ExecutionImpl类的startprocesstakeperformAtomicOperationend等几个方法来运转流程。

    结合JBPM的源代码,我们从ExecutionImplstart方法开始看:

 

 

JBPM简析-part1

    代码211行初始化了execution流程上下文对象的作用域,代码215则是开始执行ExecuteActivity类了,我们来看看AtomicOperation.EXECUTE_ACTIVITY是什么:

JBPM简析-part1

   

可以看到AtomicOperation.EXECUTE_ACTIVITY即是我们上面介绍到的ExecuteActivity类,TransitionTakeTransitionStartActivityTransitionEndActivity等类的静态常量也如上图所示,之后不再叙述了。

    我们看看ExecutionImpl类的performAtomicOperationSync方法,代码如图所示:

 JBPM简析-part1

 

代码中的atomicOperations变量是一个Queue<AtomicOperation>队列变量。查看以上的代码,我们可以看到JBPM在执行performAtomicOperationSync采用了一个while循环的方式执行AtomicOperation类的perform方法,那么有哪些类实现了AtomicOperation类的perform方法呢?如下图所示:

 JBPM简析-part1

再回到我们刚才介绍的代码,调用ExecutionImpl类的start方法时代码最终会流向为调用performAtomicOperation方法,start方法中给performAtomicOperation方法传递进去的对象为ExecuteActivity。如此,则ExecuteActivity类的对象会执行perform方法,我们看看方法的代码,看看代码中做了哪些操作:

JBPM简析-part1

注意红线标示处的代码,红线2处标示执行的activityBehaviour对象即为在xml配置文件中定义的各个不同的节点类型(如state节点)所对应的具体解析后的对象,如start节点就对应StartActivity对象、end节点就对应 EndActivity对象、sql节点对应SqlActivity等。目前activityBehaviour变量的值为开始节点活动的具体处理类(StartActivity类)对象。

    查看StartActivity类的execute方法,发现代码内容为空,此时流程的状态仍然为UNSPECIFIED,代码会一步一步执行到execution.proceed()方法。

我们来看此方法的代码:

 

 

JBPM简析-part1

代码中首先检查流程是否处于可运行状态,之后取得了一个默认的转向对象,然后就调用了take(xxx)方法(代码524行处理的是另外较为复杂的流程模型,此时暂不对此进行分析)。

    take代码如下所示:

 JBPM简析-part1

fire方法的代码我们不作具体分析,这里我们只需要知道的是fire方法在触发相关的Event接口后会调用其第三个参数对象(即实现了AtomicOperation类的对象,见图xxx)中的perform方法。

    我们查看TransitionEndActivity类的perform方法,如下所示:

 

 

 

 JBPM简析-part1

可以看到其调用了TransitionTake类的perform方法,代码如下:

 

 

 

 JBPM简析-part1

我们可以看到其执行了TransitionStartActivity类的perform方法,代码如下:

JBPM简析-part1

我们可以看到其执行了TransitionStartActivity类的perform方法,代码如下:

 JBPM简析-part1

可以看到此方法在保存了流程的历史数据后,就设置了流程的状态为WAIT

JBPM流程运转情形可以总结为:

ExecuteActivity ---à执行具体的JpdlActivity类中的execute方法---àTransitionEndActivity---àTransitionTake---àTransitionStartActivity---àExecuteActivity,如此来回反复,直到流程运行到结束节点或者流程处于WAIT状态。

通过这种类似于圆圈的执行机制,JBPM就可以咣咣当当地流转下去了。

    上面分析的是JBPM流程在一般正常运转情况下的流程运转机制。接下来我们分析并介绍JBPM中另外3个有意思的流程模型,即执行sql取得变量(即分析SqlActivity)、子流程、分发汇聚。介绍第一点内容是为了进一步加深我们对JBPM工作流流转机制和代码结构的印象,后两点则是JBPM中比较复杂的内容。清楚了解了后两处内容,可为我们进一步学习了解JBPM的源代码打下坚实的基础。

    执行sql取得变量功能:

    配置文件定义如下:

 

 

 

<sql name="get task names"

       var="tasknames with i"

       g="96,16,126,52">

    <query>

      select NAME_

      from JBPM4_TASK

      where NAME_ like :name

    </query>

    <parameters>

      <string name="name" value="%i%" />

    </parameters>

    <transition to="count tasks" />

  </sql>

 正如上面我们介绍的,sql标签对应着SqlActivity类。那么我们就打开这个类的源代码,看看里面具体作了哪些工作,代码如下所示示:

JBPM简析-part1

 

可以看到SqlActivity类继承了HqlActivity类,并重写了HqlActivity类的createQuery方法,在此createQuery方法中创建了一个HibernateQuery查询对象。

    我们来看看HqlAcitivity类的代码,代码如下:

 

JBPM简析-part1

可以看到HqlAcitivity类是继承了JpdlAutomaticActivity类并重写perform方法,在HqlAcitivity类的perform方法中,主要做的事情即是根据xml配置文件中定义的sql标签定义的内容查询出一个结果值,之后将这个结果值result对象存放起来。

    那么具体的sqlquery对象的值是从哪里得到的呢?答案是我们定义的xml配置文件中。感兴趣的同学可以看看HqlBinding类的parseJpdl方法。以后若是有时间,我会把JBPM如何解析jpdl.xml配置文件也整理成一份文档。

    细心的同学可能已经想到了,ExecuteActivity中的activityBehaviour对象调用的是 execute方法,但是上面我们看到的HqlAcitivity类中并没有execute方法,那么我就看看JpdlAutomaticActivity类,看看execute方法是否是在这个类中定义的。代码如下所示:

JBPM简析-part1

 

 

可以看到JpdlAutomaticActivity类是一个抽象类,其非抽象类的子类必须要实现其抽象的perform方法。我们看到JpdlAutomaticActivity类中是定义了execute方法的,在执行了perform方法后触发执行一个AutomaticEnd类型的Event,保存一下历史数据,它所有的功能即宣告完成。

    我们再看看JpdlActivity类的代码,如下所示:

JBPM简析-part1

可以看到JpdlActivity抽象类实现了ActivityBehaviour接口,那么所有JpdlActivity类的非抽象类的子类肯定是实现了execute方法的。以后我们想要查看一个特定的标签(如FockJoin等)对应的实现类具体作了些什么工作,我们只需要查看其对应的实现类(如ForkActivityJoinActivity)的execute方法的代码实现即可。

子流程功能:

配置文件定义如下:

主流程的配置文件定义如下:

 

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

 

<process name="SubProcessDocument" xmlns="http://jbpm.org/4.2/jpdl">

 

  <start g="43,109,48,48">

    <transition to="review" />

  </start>

 

  <sub-process name="review"

               sub-process-key="SubProcessReview"

               g="118,106,99,52">

              

    <transition name="ok" to="next step" g="167,67:6,-19"/>

    <transition name="nok" to="update" g="-22,-18"/>

    <transition name="reject" to="close" g="167,200:7,3"/>

  </sub-process>

 

  <state name="next step" g="255,41,88,52"/>

  <state name="update" g="256,106,88,52"/>

  <state name="close" g="258,175,88,52"/>

 

</process>

子流程的配置文件定义如下:

 

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

 

<process name="SubProcessReview" xmlns="http://jbpm.org/4.2/jpdl">

 

  <start g="25,101,48,48">

    <transition to="get approval"/>

  </start>

 

  <task name="get approval"

        assignee="johndoe"

        g="107,97,127,52">

 

    <transition name="ok" to="ok" g="171,71:9,-16"/>

    <transition name="nok" to="nok" g="-16,-16"/>

    <transition name="reject" to="reject" g="170,179:8,3"/>

  </task>

 

  <end name="ok" g="269,48,88,52" />

  <end name="nok" g="270,101,88,52" />

  <end name="reject" g="270,156,88,52"/>

 

</process>

 

prat-2