本教程向你展示用jpdl构造的简单流程和使用API管理流程运行时执行的过程。
本教程的形式是解释一批范例。这些范例着眼于一个特定的主题并包含大量的注解。这些范例也能够在jBPM下载软件包的src/java.examples目录中找到。
最好的学习方式是创建一个项目,并通过创建下面的范例的变体来体验它。
在开始之前,首先下载和安装jBPM。
jBPM包含一个图形设计器工具,可用于创建在这些范例中显示的xml文件。你可以在《下载和安装jBPM》部分找到下载这个图形设计器的指南。你不需要为了完成本教程而使用这个图形设计工具。
Hello World范例
流程定义是一个有向图,由节点(node)和转移(transition)组成。Hello world流程有三个节点。为了看到这些代码片段如何组合在一起,我们从一个简单的流程开始,不使用图形设计工具。下面的图显示hello world流程的图形表示:
图 3.1. Hello world流程图
- public void testHelloWorldProcess() {
- // 本方法显示一个流程定义和此流程定义的执行。这个流程定义有三个节点:一个未命名的
- // start状态,一个状态s和一个名为end的结束状态。
- // 下面一行将一段xml文本解析为一个流程定义ProcessDefinition。流程定义是对流程
- // 的正式描述,表示为一个Java对象。
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition>" +
- " <start-state>" +
- " <transition to='s' />" +
- " </start-state>" +
- " <state name='s'>" +
- " <transition to='end' />" +
- " </state>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- // 下面一行代码创建流程定义的一个执行。流程执行创建之后将拥有一个主执行
- // 路径(=根token),指向开始状态节点。
- ProcessInstance processInstance = new ProcessInstance(processDefinition);
- // 流程执行创建之后,拥有一个主执行路径(=根token)。
- Token token = processInstance.getRootToken();
- // 流程执行创建之后,主执行路径指向流程定义的开始状态节点。
- assertSame(processDefinition.getStartState(), token.getNode());
- // 下面我们启动流程执行,从缺省转移路线离开开始状态节点。
- token.signal();
- // signal方法将阻塞,直至流程执行进入一个等待状态。
- // 流程执行进入了第一个等待状态:状态s。所以主执行路径现在指向状态s。
- assertSame(processDefinition.getNode("s"), token.getNode());
- // 下面我们发送第二个信号。这将恢复流程执行,通过缺省的转移路径离开状态s。
- token.signal();
- // 现在signal方法返回了,因为流程实例到达了结束状态节点。
- assertSame(processDefinition.getNode("end"), token.getNode());
- }
数据库范例
jBPM的一个基本特征是把处于等待状态中的流程执行持久化到数据库中的能力。下面的范例将向你展示如何将一个流程实例保存到jBPM数据库中。本范例也暗示存在一个流程执行的上下文。下面各个方法在不同的用户代码片段中创建,例如,在web应用程序中的一段用户代码发起一个流程执行并将它持久化到数据库中,随后,一个消息驱动bean从数据库中装载这个流程实例并恢复其执行。
- public class HelloWorldDbTest extends TestCase {
- static JbpmConfiguration jbpmConfiguration = null;
- static {
- // 像此处的范例配置文件能够在'src/config.files'中找到。典型地配置信息存在
- // 于资源文件'jbpm.cfg.xml'中,但在这里我们直接传递一个XML字符串形式的配置信息。
- // 首先我们创建一个JbpmConfiguration静态对象。系统中的所有线程可以使用
- // 同一个JbpmConfiguration,因此我们可以安全地把它设定为静态的。
- jbpmConfiguration = JbpmConfiguration.parseXmlString(
- "<jbpm-configuration>" +
- // jbpm-context机制能够从jbmp使用的环境服务中分离出jbpm核心引擎。
- " <jbpm-context>" +
- " <service name='persistence' " +
- " factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
- " </jbpm-context>" +
- // 同样地,jbpm使用的所有资源文件可以从jbpm.cfg.xml中引用。
- " <string name='resource.hibernate.cfg.xml' " +
- " value='hibernate.cfg.xml' />" +
- " <string name='resource.business.calendar' " +
- " value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
- " <string name='resource.default.modules' " +
- " value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
- " <string name='resource.converter' " +
- " value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
- " <string name='resource.action.types' " +
- " value='org/jbpm/graph/action/action.types.xml' />" +
- " <string name='resource.node.types' " +
- " value='org/jbpm/graph/node/node.types.xml' />" +
- " <string name='resource.varmapping' " +
- " value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
- "</jbpm-configuration>"
- );
- }
- public void setUp() {
- jbpmConfiguration.createSchema();
- }
- public void tearDown() {
- jbpmConfiguration.dropSchema();
- }
- public void testSimplePersistence() {
- // 在以下的三个方法调用之间,所有的数据通过数据库传递。在这个单元测试中,
- // 这三个方法是顺序执行的,因为我们要测试一个完整的流程场景。但是在现实
- //中,这些方法代表对服务器的不同请求。
- // 因为我们从一个干净的、空的内存数据库中启动,必须首先部署流程。现实中,
- // 流程部署是由流程开发者一次性完成的。
- deployProcessDefinition();
- // 假设当用户在web应用程序中提交一个form的时候,我们要启动一个流程实例……
- processInstanceIsCreatedWhenUserSubmitsWebappForm();
- // 随后,当异步消息到达之后,将继续执行流程。
- theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();
- }
- public void deployProcessDefinition() {
- // 本测试展示一个流程定义和该流程定义的一个执行实例。这个流程定义有
- // 三个节点:一个未命名的开始状态,一个状态s和一个名为end的结束状态。
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition name='hello world'>" +
- " <start-state name='start'>" +
- " <transition to='s' />" +
- " </start-state>" +
- " <state name='s'>" +
- " <transition to='end' />" +
- " </state>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- // 查找在上面的过程中已配置好的POJO持久化上下文生成器。
- JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
- try {
- // 将流程定义部署到数据库。
- jbpmContext.deployProcessDefinition(processDefinition);
- } finally {
- // 销毁POJO持久化上下文。
- // 这包括刷新SQL,将流程定义插入到数据库中。
- jbpmContext.close();
- }
- }
- public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
- // 本方法中的代码应存在于一个struts的action 或JSF托管的bean中。
- // 查找在上面的过程中已配置好的POJO持久化上下文生成器。
- JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
- try {
- GraphSession graphSession = jbpmContext.getGraphSession();
- ProcessDefinition processDefinition =
- graphSession.findLatestProcessDefinition("hello world");
- // 从数据库中取得流程定义之后,我们可以创建该流程定义的一个执行实例,
- // 就像Hello world范例中一样(后者没有使用持久化)。
- ProcessInstance processInstance =
- new ProcessInstance(processDefinition);
- Token token = processInstance.getRootToken();
- assertEquals("start", token.getNode().getName());
- // 下面启动流程执行
- token.signal();
- // 现在流程处于状态s。
- assertEquals("s", token.getNode().getName());
- // 现在流程实例被保存到数据库中,所以流程执行的当前状态被保存到数据库中了。
- jbpmContext.save(processInstance);
- // 下面的方法将从数据库中取回流程实例,通过提供另一个外部信号恢复流程的执行。
- } finally {
- // 销毁POJO持久化上下文。
- jbpmContext.close();
- }
- }
- public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
- // 本方法中的代码可以是一个消息驱动bean的内容。
- //查找在上面的代码中已经配置好的POJO持久化上下文生成器
- JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
- try {
- GraphSession graphSession = jbpmContext.getGraphSession();
- // 首先,我们需要从数据库中取回流程实例。为了知道哪个流程实例是我们这里要使用的,
- // 有几个可选方法。在这个简单的测试案例中,最容易的方式是在整个流程实例列表中查
- // 找,这将仅仅返回一个结果。所以,让我们获取这个流程定义。
- ProcessDefinition processDefinition =
- graphSession.findLatestProcessDefinition("hello world");
- // 现在,我们查找这个流程定义中的所有流程实例。
- List processInstances =
- graphSession.findProcessInstances(processDefinition.getId());
- // 因为我们知道,在这个单元测试测环境中只存在一个执行实例。在现实中,
- // processInstanceId要从到达的消息内容中提取,或来自用户的选择。
- ProcessInstance processInstance =
- (ProcessInstance) processInstances.get(0);
- // 现在我们可以继续流程执行了。记住流程实例将信号转发给主执行路径(=根token)。
- processInstance.signal();
- // 发出信号之后,我们知道流程执行到达了结束状态。
- assertTrue(processInstance.hasEnded());
- // 现在我们可以将流程执行的状态更新到数据库中
- jbpmContext.save(processInstance);
- } finally {
- // 销毁POJO持久化上下文。
- jbpmContext.close();
- }
- }
- }
#p#
上下文(context)范例:流程变量
流程变量包含流程执行过程中的上下文信息。流程变量类似于java.util.Map,将变量名映射到值,值是java对象。流程变量作为流程实例的一部分被持久化。为了保持简单,在此范例中我们仅仅展示与流程变量有关的API,不考虑持久化。
- // 这个范例同样从Hello world流程开始。
- // 这次甚至没有修改。
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition>" +
- " <start-state>" +
- " <transition to='s' />" +
- " </start-state>" +
- " <state name='s'>" +
- " <transition to='end' />" +
- " </state>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- ProcessInstance processInstance =
- new ProcessInstance(processDefinition);
- // 从流程实例中取得上下文实例,以处理流程变量。
- ContextInstance contextInstance =
- processInstance.getContextInstance();
- // 在流程离开开始状态之前,我们准备在流程实例的上下文中设置一些流程变量。
- contextInstance.setVariable("amount", new Integer(500));
- contextInstance.setVariable("reason", "i met my deadline");
- // 从现在开始,这些变量关联到这个流程实例了。现在这些变量可以通过用户代码
- // 使用在这里显示的API来访问了,但是,也可以在action和节点实现中访问。流
- // 程变量作为流程实例的一部分,也被存储到数据库中。
- processInstance.signal();
- // 流程变量可以通过contextInstance访问。
- assertEquals(new Integer(500),
- contextInstance.getVariable("amount"));
- assertEquals("i met my deadline",
- contextInstance.getVariable("reason"));
任务分配范例
在下面的例子中我们展示如何将任务分配给用户。因为jBPM工作流引擎和组织机构模型的分离,一个用于计算参与者(actor)的表达式语言总是太受限了。因此,你必须指定一个 AssignmentHandler接口的实现类来包含计算任务的参与者的过程。
- public void testTaskAssignment() {
- // 下面的流程基于hello world流程。状态节点被任务节点取代。任务节点是jPDL中
- // 的一种节点,表示一个等待状态,并创建在流程继续执行之前必须完成的任务。
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition name='the baby process'>" +
- " <start-state>" +
- " <transition name='baby cries' to='t' />" +
- " </start-state>" +
- " <task-node name='t'>" +
- " <task name='change nappy'>" +
- " <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' />" +
- " </task>" +
- " <transition to='end' />" +
- " </task-node>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- // 创建流程定义的一个执行实例。
- ProcessInstance processInstance =
- new ProcessInstance(processDefinition);
- Token token = processInstance.getRootToken();
- // 下面开始流程执行,从缺省转换路径离开开始状态。
- token.signal();
- // signal方法将阻塞,直至流程执行进入一个等待状态。在这里,就是任务节点t。
- assertSame(processDefinition.getNode("t"), token.getNode());
- // 当流程执行到达任务节点,一个'换尿布(change nappy)'任务将被创建,
- // NappyAssignmentHandler被调用以决定把这个任务分配给谁。
- // NappyAssignmentHandler返回'(爸爸)papa'。
- // 在真实的环境中,任务通过org.jbpm.db.TaskMgmtSession类中的方法从数据
- // 库中取得。因为我们不想在本范例中包含持久化的复杂性,我们仅仅从流程实例中
- // 取出第一个任务实例(我们知道在这个测试场景中只有一个任务实例)。
- TaskInstance taskInstance = (TaskInstance)
- processInstance
- .getTaskMgmtInstance()
- .getTaskInstances()
- .iterator().next();
- // 现在,我们检测任务实例是否真正被指派给'papa'.
- assertEquals("papa", taskInstance.getActorId() );
- // 现在我们假设爸爸已经完成他的任务,并把任务标记为已完成。
- taskInstance.end();
- // 因为这是最后(唯一)一个要完成的任务,这个任务的完成出发了流程实例的继续执行。
- assertSame(processDefinition.getNode("end"), token.getNode());
- }
定制action范例
action是把你的客户java代码绑定到jBPM流程的一种机制。action能够关联到它自己的节点(如果它们在流程的图形表示中是相关的的话)。action也能够放置在事件(例如进入转换、离开节点、进入节点等)之中。在这种情况下,action不是流程的图形表示的一部分,但是,当运行时流程执行触发了这些事件的时候,这些action将被执行。
- // MyActionHandler代表一个能够在jBPM流程执行过程中执行某些用户代码的类。
- public class MyActionHandler implements ActionHandler {
- // 在每个test之前(在setUp方法中), isExecuted成员将被设置为false。
- public static boolean isExecuted = false;
- // action将把 isExecuted设置为true,使得单元测试能够显示action在什么时候被执行。
- public void execute(ExecutionContext executionContext) {
- isExecuted = true;
- }
- }
我们从在下面的范例中将要用到的action实现:MyActionHandler开始。这个action处理器实现没有做真正有用的工作,仅仅把布尔变量isExecuted设置为true。变量isExecuted是静态的,所以能够同时从action处理器内部和要校验它的值的action中访问。
- // 每个测试将在开始时设置MyActionHandler的静态成员isExecuted为false。
- public void setUp() {
- MyActionHandler.isExecuted = false;
- }
下面的例子显示同样的action,但是现在action被分别放在enter-node和leave-node事件之中。请注意与转换只有一个事件不同,节点拥有多个事件类型,因此被放置到节点中的action应该放在event元素之中。
- public void testTransitionAction() {
- // 下面的流程是hello world流程的一个变体。我们把一个action加入从状态s到结束状
- // 态的转换之中。本测试的目的是显示要把java代码集成到jBPM流程中是多么容易。
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition>" +
- " <start-state>" +
- " <transition to='s' />" +
- " </start-state>" +
- " <state name='s'>" +
- " <transition to='end'>" +
- " <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
- " </transition>" +
- " </state>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- // 启动流程定义的一个新的执行实例。
- ProcessInstance processInstance =
- new ProcessInstance(processDefinition);
- // 下一个信号将导致执行离开开始状态,到达状态s。
- processInstance.signal();
- // 这里我们显示 MyActionHandler还没有被执行。
- assertFalse(MyActionHandler.isExecuted);
- // ... 以及主执行路径指向状态s
- assertSame(processDefinition.getNode("s"),
- processInstance.getRootToken().getNode());
- // 下一个信号将触发根token的执行。该token将取得包含action的转换,
- // 该action将在调用signal方法的过程中执行。
- processInstance.signal();
- // 这里我们可以看到在signal方法被调用的时候, MyActionHandler被执行了。
- assertTrue(MyActionHandler.isExecuted);
- }
- ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
- "<process-definition>" +
- " <start-state>" +
- " <transition to='s' />" +
- " </start-state>" +
- " <state name='s'>" +
- " <event type='node-enter'>" +
- " <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
- " </event>" +
- " <event type='node-leave'>" +
- " <action class='org.jbpm.tutorial.action.MyActionHandler' />" +
- " </event>" +
- " <transition to='end'/>" +
- " </state>" +
- " <end-state name='end' />" +
- "</process-definition>"
- );
- ProcessInstance processInstance =
- new ProcessInstance(processDefinition);
- assertFalse(MyActionHandler.isExecuted);
- // 下一个信号将导致流程执行离开开始状态,进入状态s。因此状态s被进入,action被执行。
- processInstance.signal();
- assertTrue(MyActionHandler.isExecuted);
- // 重置MyActionHandler.isExecuted。
- MyActionHandler.isExecuted = false;
- // 下一个信号将触发流程执行离开状态s,因此action将被再次执行。
- processInstance.signal();
- // 瞧……
- assertTrue(MyActionHandler.isExecuted);
【编辑推荐】