JBPM4

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

1. jBPM 配置文件 (IOC) 2. jBPM PVM 架构
2.1. Environment 2.2. Service 2.3. Command and Command Service 2.4. PVM Models: ProcessDefinition, ActivityBehaviour, Transition, Event 2.5. API整体架构图
3. jBPM PVM Model(模型)解析
3.1. ActivityBehaviour(内部节点) 3.2. ExternalActivityBehaviour(外部节点) 3.3. 基本流程的实现 3.4. 事件机制 (Event and EventListener)
Bibliography

Abstract

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

Abstract

配置文件是jBPM里面很关键的一步,同时Configuration API可以看做是jBPM里的一个入口API,因为其他很多重要的Service,都是通过这个API,然后生成的,于是我们需要先看一下这个模块.

在默认情况下,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里面的几个重要概念.

Environment 的概念,主要有以下的作用.

  1. 使process能在不同的环境下之行,比如可以在标准的Java, 企业级Java,Seam 或者 Spring 环境下运行.
  2. 他可以存放着不同的context对象,比如我们之前所看到的配置文件中的process-engine的Context, transaction的Context等.

在代码中,我们想获得其他的组件时,或者变量时,我们总是使用Environment的API, 我们可以看下它的API的几个重要方法.

   	  public abstract <T> T get(Class<T> type); 
	  /** searches an object based on type.  The search doesn take superclasses of the context elements 
	   * into account.
	   * @return the first object of the given type or null in case no such element was found.  
	   */
	  public abstract <T> T get(Class<T> type, String[] searchOrder);
   	

Environment 内部是使用Map来保存不同的Context对象, Environment对象的创建主要是有EnvironmentFactory 来负责. Environment是被保存在ThreadLocal中,如果有多个Environment,那么是选择把Environment(s)放在一个堆栈中,然后再存放在ThreadLocal里.

Service就是PVM对外发布的服务,包括RepositoryService(比如部署jpdl文件),ExecutionService(负责管理执行),TaskService,ManagementService等等.可以说这是给用户 调用的API.比如我们看下他们的一些方法. RepositoryService接口的一些方法:

public interface RepositoryService {
  NewDeployment createDeployment();
  ProcessDefinitionQuery createProcessDefinitionQuery();
  ...
}
	

ExecutionService的一些方法:

public interface ExecutionService {
  ProcessInstance startProcessInstanceById(String processDefinitionId);
  ProcessInstance signalExecutionById(String executionId); 
  ...
}
	

ManagementService的接口:

public interface ManagementService {
  void executeJob(long jobDbid);
  JobQuery createJobQuery();
}
	

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之前执行. 我们看下面这个图.

Tip

可以说CommandService是最适合处理横截面(cross-cutting)的问题,比如事务,安全等的. CommandService默认下是DefaultCommandService,如果是在使用ejb的情况下, 它的实现类就是EjbLocalCommandService和EjbRemoteCommandService类. 所以,这里是远程调用,还是本地调用,对用户而言,都是透明的.

  1. retry-interceptor的主要作用当碰到StaleObjectException(也就是optimistic locking失败)时,过一定的时间后再尝试.
  2. environment-interceptor的主要作用就是,保证command是在Environment范围内执行,也就是在执行command之前,openEnvironment,执行之后,closeEnvironment.
  3. standard-transaction-interceptor的主要作用就是处理事务.
  4. authorization-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();
    

Tip

这里,我们注意到返回的是ClientProcessDefinition ,那么他和ProcessDefinition 的区别在哪儿呢? ClientProcessDefinition 是继承ProcessDefinition ,他还包括了一些负责创建和启动ProcessInstance 的方法. 类似的,你会发现有ClientProcessInstance ProcessInstance 接口,他们的区别也是ClientProcessInstance多了负责启动的start方法.

我们最后从整体上来看下这些API之间的联系,我觉得Developer Guide上的这个图片很清楚的描述了他的架构思路. 这里需要注意的是,本身PVM内部的流程定义模型是POJO的,所以如果你只是想测试流程的正确性,你只需要直接使用Client的API,比如ClientProcessDefinition, ClientProcessInstance等的API. 不需要去调用RepositoryService, ProcessEngineService. 这些Service是针对你把ProcessDefinition在保存在数据库的情况下才需要用的.

这里,我们将利用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);
  }	  
}


 

隐式(implicit)的execute方法执行顺序

如果我们在execute()方法中,没有显示的调用execution中的方法,比如waitForSignal(),take(transition)等方法,那么默认的顺序是这样的.
  1. 如果当前的节点有默认的outgoing transition,那么就调用这个默认的transition.
  2. 如果当前的节点有父节点,那么就调用到父节点.
  3. 最后,如果都没有,那就调用execution.end()方法.

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

Display example process

Figure 1. Display example 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执行方法.

Tip

细心的你会发现,我们在定义Process Defintion的时候,总是要在调用一个initial()方法. 这是设定当前这个节点为流程的启始节点.

外部节点就是代表着,这个活动还需要系统外部的配合,比如说人工的配合才能使得这个流程继续下去.我们一般称这种的节点为 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.

The external activity example process

Figure 2. The external activity example process


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).

The loan process

Figure 3. The 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.

Execution positioned in the 'evaluate' activity

Figure 4. Execution positioned in the 'evaluate' activity


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

instance.signal("approve");


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

Execution positioned in 'archive' activity

Figure 5. Execution positioned in 'archive' activity


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

instance.signal("done");


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

Execution positioned in the 'end' activity

Figure 6. Execution positioned in the 'end' activity


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

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


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

具体的Event是由ObservableElement EventName 来构成的.

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);
	}
}


The PrintLn listener process

Figure 7. The PrintLn listener process


我们看下是怎么来定义一个具备有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.

  1. jBPM 4.0 User Guide
  2. jBPM 4.0 Developer Guide
  3. Srping integration with jbpm4
  4. jBPM4的IOC容器


[1 ] 图来自于http://www.blogjava.net/RongHao/archive/2009/05/07/269465.html

[2 ] http://www.jboss.org/jbossidentity

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值