jBPM 4.0 解读分析

Revision History
Revision 0.12009/06/29
增加了前3章的内容

Abstract

这篇文章主要是介绍了jBPM 4.0的基础架构,以及通过一个简单的例子来让大家知道怎么应用jBPM. 为什么选择4.0版本呢?以后的主流版本应该是4.0版本的,所以直接写4.0版本的了.

在默认情况下,jBPM包里有个jbpm.cfg.xml,这个就是他的配置文件,我们先看下它的内容.

	 
 <jbpm-configuration>
  <import resource="jbpm.default.cfg.xml" />
  <import resource="jbpm.tx.jta.cfg.xml" />
  <import resource="jbpm.jpdl.cfg.xml" />
  <import resource="jbpm.identity.cfg.xml" />	
  <import resource="jbpm.jobexecutor.cfg.xml" />	
</jbpm-configuration>
	
	 

这里,我们再继续看下jbpm.default.cfg.xml,看下配置文件到底是长啥样.

	 
  <process-engine-context> 
    <repository-service />
    <repository-cache />
    <execution-service />
    <history-service />
    <management-service />
    <identity-service />
    <task-service />
    <hibernate-configuration>
      <cfg resource="jbpm.hibernate.cfg.xml" />     
    </hibernate-configuration>
	...........
  </process-engine-context>
  
  <transaction-context>
    <repository-session />
    <db-session />   
    <message-session />
    <timer-session />
    <history-session />
    <mail-session>
      <mail-server>
        <session-properties resource="jbpm.mail.properties" />
      </mail-server>
    </mail-session>
  </transaction-context>
	
	 

这个配置文件主要包含了"process-engine-context'和 'transaction-context'的配置.

我们知道,现在都是讲究Dependency Inject (Inversion of Control),那么,我们这里到底是哪个类来实现repository-service呢?那配置mail-session又是怎么实例化的呢? 我们先来看下jBPM的IOC实现机制.

首先是Context接口,你可以从这里存储,获得对象.它的接口很简单.

  Object get(String key);
  <T> T get(Class<T> type);

  Object set(String key, Object value);
	 

你看可以从Context中获取到组件,对于IOC容器来说,一般情况下都会提供一种加载的方式,比如从xml文件进行加载、从资源文件进行加载。

Jbpm4是通过WireParser来解析xml,然后创建并把对象存放在WireContext. WireContext这个类负责存放,提取对象,内部用一个Map来存储已经创建的对象实例,可以简单得把他看成是IOC的一个实现类. 从WireContext的javadoc,我们可以看出,他主要是跟WireDefinition, Descriptor打交道. WireContext里面 包含了一个WireDefinition,而WireDefinition里面包含了一系列的Descriptor.每个Descriptor负责创建和初始化该对象. 比如我们可以看到IntegerDescriptor, FloatDescriptor, ObjectDescriptor等等. 我们来看下Descriptor的接口:

	 
  /**
   * constructs the object.
   * @param wireContext {@link WireContext} in which the object is created. This is also the {@link WireContext} 
   * where the object will search for other object that may be needed during the initialization phase.
   * @return the constructed object.
   */
  Object construct(WireContext wireContext);
  
  /**
   *called by the WireContext to initialize the specified object.
   */
  void initialize(Object object, WireContext wireContext);
	 

Descriptor对象的创建可以直接通过Java对象的实例化,比如(new IntegerDescriptor(..)),也可以通过xml的配置文件来实现.可以说我们更经常用xml来配置,所以就有了Binding的概念. Binding类最主要的任务就是把XML DOM 到Java对象的转换. Bindings是把Binding归类了一下而已. 以下是Binding的接口.

	 
public interface Binding { 
  String getCategory();

  /** does this binding apply to the given element? */
  boolean matches(Element element);

  /** translates the given element into a domain model java object.
   * Use the parse to report problems. 
   */
  Object parse(Element element, Parse parse, Parser parser);
}

如果想看实现,我们可以看下IdentityServiceBinding, RepositoryServiceBinding等等.这里注意下,在jBPM的实现当中,WireDescriptorBinding是根据tagName来解析的. 所以,从jBPM的xml配置文件,到ProcessEngine对象的构建,是这样的一个流程.

 jbpm.cfg.xml –>  jBPMConfigurationParser ->  Binding –>  Descriptor --> WireContext

或者更清楚的,我们可以看下下面这张图[1].

我们不妨也看下ConfigurationTest测试.

	 
  public void testConfigurationServices() {
    ProcessEngine processEngine = new Configuration()
        .setXmlString(
            "<jbpm-configuration>" +
            "  <process-engine-context>" +
            "    <repository-service />" +
            "    <execution-service />" +
            "    <management-service />" +
            "  </process-engine-context>" +
            "</jbpm-configuration>"
        )
        .buildProcessEngine();
    assertNotNull(processEngine);
    assertNotNull(processEngine.getExecutionService());
    assertNotNull(processEngine.getManagementService());
  }
	 

Configuration类是jBPM的入口,你可以从Configuration类中创建ProcessEngine,而从ProcessEngine中获取到RepositoryService, ExecutionService, TaskService等等. Configuration类里有两个实现类,一个是JbpmConfiguration,这是默认的jBPM自带的Configuration解析器,另外一个是SpringConfiguration,这个是jBPM和Spring的集成. 在这里,我们就只看下JbpmConfiguration的实现,在JbpmConfiguration类里,我们可以看到他是这么去调用Parser来解析xml的.

 
 protected void parse(StreamInput streamSource) {
   isConfigured = true;
   JbpmConfigurationParser.getInstance()
     .createParse()
     .pushObject(this)
     .setStreamSource(streamSource)
     .execute()
     .checkErrors("jbpm configuration " + streamSource);
 }
  

在这里,我们可以看到,jbpm的配置文件是有两个元素组成的,一个是process-engine-context,另外一个是transaction-context. 其中process-engine-context里面的元素是包括了对外发布的服务, 比如repository-service, execution-service等等. 而transaction-context则包括了他的内部真正实现,比如repository-service对应repository-session. 也可以简单的把repository-servie看做是API, repository-session看做是SPI. 这样做的好处是,SPI的实现,对API一点影响都没有,很大程度上提供了一个容易集成的特性.

说到这里,应该对jBPM的IOC介绍的差不多了.我自己在用JBoss IDM project[2]来实现jBPM的Identity module时.需要增加如下的配置.

 
<jbpm-configuration>
  <process-engine-context>
    <jboss-idm-identity-session-factory jndi="java:/IdentitySessionFactory" />
  </process-engine-context>
  <transaction-context>
    <jboss-idm-identity-session realm="realm://JBossIdentity" />
  </transaction-context>
</jbpm-configuration>
	 

于是,我需要增加以下的类来完成对象的创建和实例化. JbossIdmIdentitySessionFactoryBinding,JbossIdmIdentitySessionFactoryDescriptor,JbossIdmIdentitySessionBinding, JbossIdmIdentitySessionDescriptor 然后,在jbpm.wire.bindings.xml里面注册我们新加的Binding. 从上面我们所说的,不难看出本身这个IOC的实现机制也是很简单的,而且也很容易扩展,如果说到时候和Spring, JBoss MC等IOC容器的集成也是很方便的.

这里,我们看下jBPM-PVM概念和架构,这也是jBPM整个项目的核心所在.

PVM (Process Virtual Machine), 主要是想作为一个开发平台,在这个平台上,可以很方便的开发工作流,服务编制(orchestration),BPM等等.就比如 说jPDL这套语法的内部实现就是基于PVM的.将来基于PVM可以开发一个符合WS-BPEL 2.0的模块. PVM可以简单的看成是一个状态机. 我们接下去看下jBPM里面的几个重要概念.

Command概念的引入,主要是想对所有的操作做一个封装. 可以说上面每个Service的方法的实现都是通过实现一个Command来操作,然后通过CommandService调用到后面具体的实现. 我们先来看下Command的接口.

public interface Command<T> extends Serializable {
  T execute(Environment environment) throws Exception;
}
	

很简单,很典型的Command模式.

我们接下来看CommandService接口,顾名思义,他主要是负责来执行Command(s)的操作.所以其他Service的实现都是通过CommandService来调用到后面的实现,可以把CommandService 看做一个桥梁的作用.看一下CommandService的接口.

public interface CommandService {
  /**
   * @throws JbpmException if command throws an exception.
   */
  <T> T execute(Command<T> command);
}	
	

CommandService还有一个特性,就是可以配置Interceptor,比如事务就是在这一Service中来配置.看下CommandService的配置.

    <command-service>
      <retry-interceptor />
      <environment-interceptor />
      <standard-transaction-interceptor />
    </command-service>	
	

这里,在执行真正的CommandServiceImpl之前,会先之前retry-Interceptor,environment-interceptor等等.这里的Interceptor的顺序是跟配置的顺序一致的. 比如这样的配置,那就是retry-interceptor在environment-interceptor之前执行. 我们看下面这个图.

ProcessDefinition是一个定义好的工作流程. OpenProcessDefinition里面包含了启始的Activity. 流程的走向是通过Activity的流向来组成的. Transition就是用来连接Activity而后来构成一个流程的. 一个工作流的工作引擎,最基本的两个功能:一个是设计好当前的工作流程.第二个是有个东西需要来体现当前走到流程的哪一步,那么PVM中的Execution API就是这个作用. 至于最后一个Event,就是你可以定义一些事件,比如当流程进入到某一个Activity的时候,促发email. Event和Activity最大的区别在于Event本身不会构成对流程走向的改变.

我们先看下ActivityBehaviour的接口.

public interface ActivityBehaviour extends Serializable {
  void execute(ActivityExecution execution) throws Exception;
}	
	

就是到这个Activity,需要执行的操作都在execute方法里. 还有一种ActivityBehaviour,就是属于wait state,也就是会停留在这个节点上, 需要外部的一些触发,才会继续执行下去,这种情况,需要实现的接口就是ExternalActivityBehaviour, 接口如下.

public interface ExternalActivityBehaviour extends ActivityBehaviour { 
  //handles an external trigger.  
  void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) throws Exception;
}
 

Wait State (也就是实现ExternalActivityBehaviour)的促发,是通过Transition来完成的,来看下Transition这个接口.

public interface Transition extends ObservableElement {
  /** the activity from which this transition leaves. */
  Activity getSource();

  /** the activity in which this transition arrives. */ 
  Activity getDestination();
}
  	

在pvm中,也包括了对event的支持,event的接口如下.

public interface EventListener extends Serializable { 
  void notify(EventListenerExecution execution) throws Exception;
}
	

如我们之前所说的, ProcessDefinition是由Activity,Transition以及Event组成的,ProcessDefinition是由ProcessDefinitionBuilder 的API来创建.我们稍微看下这个API的使用.

		ClientProcessDefinition definition = ProcessDefinitionBuilder.startProcess("jeffProcess")
											 .startActivity("initial", new AutomaticActivity())
											 .initial()
											 .transition("first")
											 .endActivity()
											 .startActivity("first", new WaitStateActivity())
											 .transition("end", "endSignal")
											 .endActivity()
											 .startActivity("end", new AutomaticActivity())
											 .endActivity()
											 .endProcess();
    

这里,我们将利用PVM所提供的Model,来实现一个基本的工作流引擎.

正如我们之前所说的,ActivityBehaviour是整个流程的定义核心所在,我们再看下它的API.

public interface ActivityBehaviour extends Serializable {    
  void execute(ActivityExecution execution) throws Exception;
}

当之行到ActivityBehaviour的时候,整个流程的走向完全是由他的execute()方法来决定. 比如你可以调用execution.end()来结束这个流程,或者调用execution.waitForSignal()进入一个等待状态. 我们接下去来实现一个很简单的ActivityBehaviour.

public class Display implements ActivityBehaviour {
  String message;

  public Display(String message) {
    this.message = message;
  }

  public void execute(ActivityExecution execution) {
    System.out.println(message);
  }	  
}

我们先用这个Display,来创建下面的process.


ClientProcessDefinition processDefinition = ProcessDefinitionBuilder.startProcess("helloworld")
							 .startActivity("a", new Display("Hello"))
							 .initial()
							 .transition("b")
							 .endActivity()
							 .startActivity("b", new Display("World"))
							 .endActivity()
							 .endProcess();

然后,你调用

processDefinition.startProcessInstance();

就会得到如下的结果

Hello
World

我们这个Display的节点就是采用的隐式execution执行方法.

外部节点就是代表着,这个活动还需要系统外部的配合,比如说人工的配合才能使得这个流程继续下去.我们一般称这种的节点为 Wait State. 因为他需要一直等待,直至外部活动的促发,然后流程才继续. 这种的节点需要实现ExternalActivityBehaviour的API.

public interface ExternalActivityBehaviour extends ActivityBehaviour { 
  //handles an external trigger.  
  void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) throws Exception;
}

跟内部节点类似,执行到ExternalActivityBehaviour的时候,也是执行它的execute()方法,但是一般来说,在外部活动的execute()方法中, 会调用execution.waitForSignal()方法,使得activity进入一个等待状态. 直到外部调用signal()方法来使得流程再次从等待状态变成激活.一般来说在signal()方法中,会调用execution.take(signalName)根据signalName(也就是transition name)去找到 下一个节点,然后把整个流程走到下一个节点.

很简单的一个例子是,比如说一个申请审批的流程,员工递交一份申请上去,然后继续就进入一个wait state的状态,因为他需要经理的审批(也就是一个人工的活动),那么经理可以选择一个ok的signalName,使得整个 流程进入到下一个节点,这里就好比如是结束的节点,又或者使得整个流程直接结束.

我们接下来实现一个简单的WaitState,实现ExternalActivityBehaviour的接口.

public class WaitState implements ExternalActivityBehaviour {

  public void execute(ActivityExecution execution) {
    execution.waitForSignal();
  }

  public void signal(ActivityExecution execution, 
                     String signalName, 
                     Map<String, Object> parameters) {
    execution.take(signalName);
  }
}	 

一样的,我们来看一个简单的从a->b的流程.这次不同的是,a和b都是wait state.


ProcessDefinition的定义

ClientProcessDefinition pd = ProcessDefinitionBuilder.startProcess("helloworld")
							 .startActivity("a", new WaitState())
							 .initial()
							 .transition("b", "b-transition")
							 .endActivity()
							 .startActivity("b", new WaitState())
							 .endActivity()
							 .endProcess();

启动这个ProcessDefinition

ClientProcessInstance instance = pd.startProcessInstance();
instance.isActive("a")

在启动之后,因为执行到a的时候,是一个wait state,所以,当前的流程活动应该是指向a. 如果要到b这个activity,那么就需要调用

instance.signal("b-transition");
instance.isActive("b")

那么,你就会发现,经过我们调用signal方法,instance根据所提供的transitionName (b-transition),找到下一个节点,也就是b. 但因为b也是一个wait state,所以此刻,整个流程就停留在了b节点身上.

接下来,我们基于前面两种节点的实现,来实现一个稍微比较正式的流程(loan process).


ProcessDefinition的定义

	ClientProcessDefinition pd = ProcessDefinitionBuilder.startProcess("loanprocess")
								 .startActivity("submit loan request", new Display("submit a loan request"))
								 .initial()
								 .transition("evaluate", "evaluate-transition")
								 .endActivity()
								 .startActivity("evaluate", new WaitState())
								 .transition("wiremoney", "approve")
								 .transition("end", "reject")
								 .endActivity()
								 .startActivity("wiremoney", new Display("wire the money"))
								 .transition("archive")
								 .endActivity()
								 .startActivity("archive", new WaitState())
								 .transition("end", "done")
								 .endActivity()
								 .startActivity("end", new WaitState())
								 .endActivity()
								 .endProcess();   

启动这个processInstance

instance = pd.startProcessInstance();

启动这个processInstance后,它开始点在submit loan request这个节点,后面经过Display这个节点,默认走到了evaluate这个节点. 因为evaluate是个wait state,所以流程停在了evaluate.


现在呢, evaluate这个节点有两条支路,一个是approve,指向wiremoney节点;另外一个是reject,直接走向end. 假设我们选择approve这条支路.

instance.signal("approve");

那么,我们就走向了wiremoney这个节点,因为wiremoney是个Display节点,所以它显示完后,默认的走向下一个节点,archive.


同样的,因为archive节点是个wait state,所以需要再一次的signal,才能走到end这个节点.

instance.signal("done");

这样的话,整个流程就会走向了end节点.


事件的订阅可以通过实现EventListener来实现.

public interface EventListener extends Serializable {
  void notify(EventListenerExecution execution) throws Exception;
}

Event概念的引入,主要是为了弥补分析员(Business Analyst)和开发人员(Developer)之间的不同需求. developer可以使用Event在一些节点上来做一些操作(比如说操作数据库),这样呢,也不会影响整个流程,所以分析员不用去关心这些具体的Event, 他们只需要看流程是否跟他们所期望的是一致的.

具体的Event是由ObservableElementEventName来构成的.

public interface EventListenerExecution extends OpenExecution {
void fire(String eventName, ObservableElement eventSource);
}

我们来实现一个简单的EventListener, 叫PrintLn

public class PrintLn implements EventListener {
	
	String message;
	
	public PrintLn(String message) {
		this.message = message;
	}
	
	public void notify(EventListenerExecution execution) throws Exception {
		System.out.println(message);
	}
}


我们看下是怎么来定义一个具备有Events的ProcessDefinition:

ClientProcessDefinition pd = ProcessDefinitionBuilder.startProcess("ab")
							 .startActivity("a", new Display("Testing Event"))
							 .initial()
							 .transition("b")
							 .startEvent(Event.END)
							 .listener(new PrintLn("leaving a"))
							 .listener(new PrintLn("second message while leaving a"))
							 .endEvent()
							 .startEvent(Event.TAKE)
							 .listener(new PrintLn("taking transition"))
							 .endEvent()
							 .endActivity()
							 .startActivity("b", new WaitState())
							 .startEvent(Event.START)
							 .listener(new PrintLn("entering b"))
							 .endEvent()
							 .endActivity()
							 .endProcess();

我们可以看到,一个事件可以有无穷多个的Listener(s).

至此,我们主要看了PVM里面内部Model的一些设计,一些核心的概念和API.