BPMN2.0(Business Process Model and Notation)
是一套业务流程模型与符号建模标准
精准的执行语义来描述元素的操作
以XML为载体,以符号可视化业务
BPMN2.0-流对象
活动(Activities)【UserTask、Service、Task…】
事件(Event)【Start Event、End Event…】
网关(Gateways)【Exclusive Gateway…】
BPMN2.0-事件
事件分类方式
位置分类
特殊分类
事件定义分类
事件分类方式-位置分类
开始事件
中间事件/边界事件
结束事件
事件分类方式-按照特性分类
捕获事件(Catching)
抛出事件(Throwing)
事件分类方式-按照定义分类
定时事件
错误事件
信号事件
消息事件
BPMN2.0-定时事件
定时事件定义
指定时间(time Date)
指定持续时间(time Duration)
周期执行(timeCycle)
错误边界事件(reviewSalesLead.bpmn20.xml)
BPMN-2.0流程任务
核心流程任务
用户任务(User Task)
脚本任务(Script Task)
服务任务(Java Service Task)
BPMN2.0-脚本任务
脚本任务(Script Task)
JUEL脚本(默认)
Geoovy脚本(依赖groovy-all.jar)
JavaScript脚本
脚本任务(Script Task)设置返回值
BPMN2.0-服务任务
服务任务(Java Service Task)
服务任务(Java Service Task)执行Java程序的方法
执行实现JavaDelegate或ActivityBehavior的类
执行一个javaDelegate对象的表达式,通常事spring配置的Bean
执行调用方法表达式和值表达式
执行实现JavaDelegate或ActivityBehavior的类
业务流程模型注解(Business Process Modeling Notation - BPMN)是
业务流程模型的一种标准图形注解。这个标准
是由对象管理组(Object Management Group - OMG)维护的。
基本上,BPMN规范定义了任务看起来怎样的,哪些结构可以
与其他进行连接,等等。这就意味着
意思不会被误解。
标准的早期版本(1.2版以及之前)仅仅限制在模型上,
目标是在所有的利益相关者之间形成通用的理解,
在文档,讨论和实现业务流程之上。
BPMN标准证明了它自己,现在市场上许多建模工具
都使用了BPMN标准中的元素和结构。
实际上,现在的jPDL设计器也使用了
BPMN元素。
BPMN规范的2.0版本,当前已经处于最终阶段了,
已经计划不就就会完成,允许添加精确的技术细节
在BPMN的图形和元素中,
同时制定BPMN元素的执行语法。
通过使用XML语言来指定业务流程的可执行语法,
BPMN规范已经演变为业务流程的语言,
可以执行在任何兼容BPMN2的流程引擎中,
同时依然可以使用强大的图形注解。
jBPM BPMN2的实现是在jBPM 4.0发布之后
在2009年8月,在与社区进行了紧密协作之后启动的。
而后,我们决定了第一个发布版(比如,文档/QA)
涉及一部分BPMN2规范,将在jBPM 4.3发布。
我们的目标是建立一个原生 **BPMN2运行引擎**
(或者说实现'可执行的BPMN2')基于流程虚拟机
(Process Virtual Machine - PVM)。
注意,这个版本的主要目标是原生可执行,
不是图形注解 - 但是我们清楚
对于未来的版本是很重要的。
如果用户已经了解了jBPM,就会发现
配置结构保持不变API与已经存在的完全一样或者很类似测试BPMN2流程也可以使用常用的java测试框架数据库表结构保持不变
所以,总体来说,我们的主要目标是保持所有在jBPM上好的事情,
加强它们,使用一个标准的流程语言。
第一个问题可能是,很正当的,映入脑海的是,
为什么已经有了jPDL还要实现BPMN2。它们两个语言
的目标都是定义可执行的业务流程。从高层次来看,
两个语言是等效的。主要的区别是
BPMN2是“厂商中立”的,你可以使用标准,
而jPDL是绑定在jBPM上的(虽然会有一些争论
绑定在开源语言厂商比如jPDL
和绑定在闭源产品)。
在jBPM中,两个语言实现都是建立在jBPM流程虚拟机上的
(PVM)。这意味着两个语言共享通用功能
(持久化,事务,配置,也有基本流程结构,等等)。
结果就是,对jBPM核心的优化
会对两个语言有益。依靠PVM,BPMN2实现
建立在基础上,已经在过去证明了它自己,
并拥有了很大的最终用户社区。
当执行语言,把它们相互比较的时候,
下面几点必须纳入考虑:
BPMN2是基于被BPM工业接受的一个标准。BPMN2是与实现无关的。这一点的缺点是集成java技术
jPDL总会更早。
所以,从java开发者的角度,jPDL更简单,感觉更自然
(一些BPEL/WSDL的“层次”也在BPMN中)。jPDL的一个目标是XML可读,BPMN2流程在
一定程度上也是可读的,但是工具和更多规范的细节
会要求实现同等级的
生产力。java开发者可以很快学会jPDL,因为他们很了解jPDL语言,
会发现实用工具有时候很麻烦,
语言本身也过于复杂了。BPMN2包含一个很大的描述结构的集合,在规范中。
然而,对接口代码的绑定在规范中是开放的
(与XPDL相比),即使WSDL通常会被默认使用。
这意味着流程的可移植性丧失了,
当我们把流程移植到一个引擎上,而这个引擎不支持同样的绑定机制。
比如,调用java类通常是jBPM的默认实现
的绑定方式。
很自然的,因为政治原因,BPMN2规范发展的会比较慢。
jPDL就可以快速变化,和新技术进行集成,
当他们发布的时候,
与BPMN2相比可以加快步伐进行演化。
当然,因为两个都建立在同一个PVM上,jPDL中的逻辑
也可以一直到BPMN2上,
作为一个扩展,不会出现很多麻烦。
BPMN2规范定义了非常丰富的语言,为建模和执行业务流程。
然而,也意味着它非常困难总览BPMN2可能是怎样
为了简化这种情况,我们决定把
BPMN2结构分为三个等级。
区分的方式主要基于Bruce Silver写的
'BPMN method and Style'这本书
我们定义了三种BPMN2结构分类:
基本:这个分类的结构很直接
并且容易了解。这个分类的结构可以用来为
简单的业务流程建模。:包含更强大或更复杂的结构,
这些都提高了建模和执行语法的学习曲线。
业务流程的主要目标是使用这个
和之前的分类来实现结构。复杂:这个分类的结构用来实现罕见的情况,
或者它们的语法难以理解。
在你的应用中使用BPMN 2.0是很简单的:只要把下面一行
加入jbpm.cfg.xml文件。
<import resource="jbpm.bpmn.cfg.xml" />
这里的引用会启用BPMN 2.0的流程发布,通过把BPMN 2.0发布器安装到流程引擎中。
注意流程引擎可以同时使用jPDL和BPMN 2.0流程。
这意味着在你的应用里,一些流程可能是jPDL,
其他的可能是BPMN 2.0。
流程引擎是根据定义文件的后缀来区分流程定义的。
对于BPMN 2.0,使用*.bpmn.xml后缀
(jPDL使用*.jpdl.xml后缀)。
发布中包含的例子也包含了下面章节中
讨论的每个结构的实例。查看BPMN 2.0的流程实例
和测试用例,
在org.jbpm.examples.bpmn.* 包下。
参考用户指南,第二章(安装),研究一下如何导入实例。
查看章节'导入实例'。
一个BPMN 2.0 XML流程的根是definitions元素。
在命名状态,子元素会包含真正的业务流程定义。
每个process子元素
可以拥有一个id(必填)和
name(可选)。一个空的BPMN 2.0业务流程
看起来像下面这样。也要注意把BPMN2.xsd放在classpath下,
来启用XML自动补全。
<definitions id="myProcesses"
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://schema.omg.org/spec/BPMN/2.0 BPMN20.xsd”
xmlns=“http://schema.omg.org/spec/BPMN/2.0”
typeLanguage=“http://www.w3.org/2001/XMLSchema”
expressionLanguage=“http://www.w3.org/1999/XPath”
targetNamespace=“http://jbpm.org/example/bpmn2”>
...
如果为process元素定义了name,它会被用做流程的key。
(比如,启动一个流程可以通过调用executionService.startProcessInstanceByKey(“myBusinessProcess”)。
如果没有指定name,id会被用做key。所以只有id定义时,
会允许通过id来启动一个流程实例。所以基本上name和key在使用上是等价的,比如搜索流程定义。
注意key的规则与jPDL一样:
空格和非字母数字的字符会被下划线代替。
与活动和网关一起,事件用来在实际的每个业务流程中。
事件让业务建模工具用很自然的方式描述业务流程,比如
'当我接收到客户的订单,这个流程就启动',
'如果两天内任务没结束,就终止流程'
或者'当我收到一封取消邮件,当流程在运行时,
使用子流程处理邮件'。注意典型的业务
通常使用这种事件驱动的方式。人们不会硬编码顺序创建,
但是他们倾向于使用在他们的环境中发生的事情(比如,事件)。
在BPMN规范中,描述了很多事件类型,为了覆盖可能的事情,
在业务环境中可能出现的情况。
一个启动事件说明了流程的开始(或子流程)。图形形式,它看起来
是一个圆(可能)内部有一个小图标。图标指定了事件的实际类型
会在流程实例创建时被触发。
空启动事件画出来是一个圆,内部没有图标,意思是
这个触发器是未知或者未指定的。jPDL的开始活动基本是一样的语法。
流程实例的流程定义包含一个空启动事件,
可以使用executionService的API调用创建。
一个空开始事件像下面这样定义。id是必填的,name是可选的。
<startEvent id="start" name="myStart" />
结束事件指定了流程实例中一个流程路径的结束。
图形上,它看起来就是一个圆
拥有厚边框(可能)
内部有小图标。
图标指定了结束的时候
会执行哪种操作。
空结束事件画出来是一个圆,拥有厚边框,内部没有图标,
这意味着当流程到达事件时,不会抛出任何信号。
jPDL中的结束事件与空结束事件语义相同。
空结束事件可以像下面一样定义,id是必填的,name是可选的。
<endEvent id="end" name="myEnd" />
下面的例子显示了只使用空开始和结束事件的流程:
这个流程对应的可执行XML像这样
(忽略声明用的definitions根元素)
<process id="noneStartEndEvent" name="BPMN2 Example none start and end event">
<startEvent id="start" />
<sequenceFlow id="flow1" name="fromStartToEnd"
sourceRef="start" targetRef="end" />
<endEvent id="end" name="End" />
现在可以通过调用startProcessInstanceXXX操作,
创建一个流程实例。
ProcessInstance processInstance = executionService.startProcessInstanceByKey("noneStartEndEvent");
终止和空结束事件的区别是 实际中流程的路径是如何处理的(或者使用BPMN 2.0的术语叫做token)。 终止结束事件会结束整个流程实例,而空结束事件只会结束当前流程路径。 他们都不会抛出任何事情 当到达结束事件的时候。
一个终止结束事件可以像下面定义。id是必填的,name是可选的。
<endEvent id="terminateEnd" name="myTerminateEnd">
终止结束事件被描绘成结束事件一样(圆,厚边框),
内部图标时一个完整的圆。在下面的例子中,完成task1
会结束流程实例,当完成task2时只会结束到达结束事件
的流程路径,只剩下task1打开。
参考jBPM发布包中的实例,
单元测试和业务流程对应XML。
顺序流是事件,活动和网关之间的连线,显示为一条实线 带有箭头,在BPMN图形中(jPDL中等效的是transition)。 每个顺序流都有一个源头和一个 目标引用,包含了 活动,事件或网关的id。
<sequenceFlow id="myFlow" name="My Flow" sourceRef="sourceId" targetRef="targetId" />
与jPDL的一个重要区别是多外向顺序流的行为。 在jPDL中,只有一个转移会成为外向转移,除非活动是fork (或自定义活动拥有fork行为)。然而,在BPMN中, 多外向顺序流的默认行为是切分进入的token(jBPM中术语叫做execution) 分成token集合,每个顺序流一个。在下面情况中, 在完成第一个任务,就会激活三个任务。
为了避免使用一个顺序流,必须添加condition条件到顺序流中。 在运行时,只有当condition条件结果为true, 顺序流才会被执行。
为了给顺序流添加condition条件,添加一个conditionExpression 元素到顺序流中。条件可以放在 ${}中。
<sequenceFlow id=....>
注意,当前必须把
xsi:type="tFormalExpression"添加到
conditionExpression中。一个条件性的顺序流可以看到一个小菱形图片
在顺序流的起点。记住表达式一直可以定义在顺序流上,
但是一些结构不会解释它(比如,并行网关)。
活动(比如用户任务)和网关(比如唯一网关)可以用户默认顺序流。
默认顺序流只会在活动或网关的
所有其他外向顺序流的condition条件为false时才会使用。
默认顺序流图形像是顺序流多了一个斜线标记。
默认顺序流通过指定活动或网关的 'default'
属性 来使用。
也要注意,默认顺序流上的表达式会被忽略。
BPMN中的网关是用来控制流程中的流向的。更确切的是, 当一个token(BPMN 2.0中execution的概念注解)到达一个网关, 它会根据网关的类型进行合并或切分。
网关描绘成一个菱形,使用一个内部图标来指定类型 (唯一,广泛,其他)。
所有网关类型,都可以设置gatewayDirection。 下面的值可以使用:
比如下面的例子:并行网关的gatewayDirection属性为'converging', 会拥有json行为。
<parallelGateway id="myJoin" name="My synchronizing join" gatewayDirection="converging" />
注意:gatewayDirection属性根据规范是可选的。 这意味着我们不能通过这个属性来 在运行时知道一个网关的行为(比如,一个并行网关, 如果我们用够切分和合并行为)。然而,gatewayDirection属性用在解析时 作为约束条件对进入、外出顺序流。所以使用这个属性 会减低出错的机会,当引用顺序流时, 但不是必填的。
唯一网关表达了一个流程中的唯一决策。 会有一个外向顺序流被使用,根据定义在 顺序流中的条件。
对应的jPDL结构,相同的语法是 decision活动。唯一网关的 完全技术名称是'基于数据的唯一网关', 但是也经常称为XOR 网关。 XOR网关被描绘为一个菱形,内部有一个'X', 一个空的菱形,没有网关也象征着唯一网关。
下面图形显示了唯一网关的用法:根据amount变量的值, 会选择唯一网关外向的三个外向顺序流 中的一个。
这个流程对应的可执行XML看起来像下面这样。 注意定义在顺序流中的条件。唯一网关会选择一个顺序流, 如果条件执行为true。如果多个条件 执行为true,第一个遇到的就会被使用 (日志信息会显示这种情况)。
<process id="exclusiveGateway" name="BPMN2 Example exclusive gateway"> <startEvent id="start" />
${amount < 100}
${amount <= 500 && amount >= 100}
${amount > 500}
这个流程需要一个变量,这样表达式就可以在运行期间执行。
变量可以被提供,当流程实例执行的时候(类似jPDL)。
Mapvars = new HashMap ();
vars.put(“amount”, amount);
ProcessInstance processInstance = executionService.startProcessInstanceByKey(“exclusiveGateway”, vars);
唯一网关需要所有外向顺序流上都定义条件。
对这种规则一种例外是默认顺序流。
使用default 属性来引用一个已存在的
顺序流的id。这个顺序流会被使用
当其他外向顺序流的条件都执行为false时。
<exclusiveGateway id="decision" name="decideBasedOnAmountAndBankType" default="myFlow"/>
唯一网关可以同时实现汇聚和发散功能。这个逻辑很容易理解:
对于每个到达这个网关的分支流程,都会选择一个外向顺序流来继续执行。
下面的图形在BPMN 2.0中是完全合法的
(忽略名称和声明的条件)。
并行网关用来切分或同步相关的进入或外出 顺序流。
并行网关像下面这样定义:
<parallelGateway id="myParallelGateway" name="My Parallel Gateway" />
注意,gatewayDirection属性可以被使用, 已获得建模错误,在解析阶段(参考上面)。
下面的图形显示了一个并行网关可以如何使用。在流程启动后, 'prepare shipment' 和 'bill customer'用户任务都会被激活。 并行网关被描绘为一个菱形,内部图标是一个十字, 对切分和归并行为都是一样。
图形对应的XML如下所示:
<process id="parallelGateway" name="BPMN2 example parallel gatewar"> <startEvent id="Start" /> <sequenceFlow id="flow1" name="fromStartToSplit" sourceRef="Start" targetRef="parallelGatewaySplit" /> <parallelGateway id="parallelGatewaySplit" name="Split" gatewayDirection="diverging"/> <sequenceFlow id="flow2a" name="Leg 1" sourceRef="parallelGatewaySplit" targetRef="prepareShipment" /> <userTask id="prepareShipment" name="Prepare shipment" implementation="other" /> <sequenceFlow id="flow2b" name="fromPrepareShipmentToJoin" sourceRef="prepareShipment" targetRef="parallelGatewayJoin" /> <sequenceFlow id="flow3a" name="Leg 2" sourceRef="parallelGatewaySplit" targetRef="billCustomer" /> <userTask id="billCustomer" name="Bill customer" implementation="other" /> <sequenceFlow id="flow3b" name="fromLeg2ToJoin" sourceRef="billCustomer" targetRef="parallelGatewayJoin" /> <parallelGateway id="parallelGatewayJoin" name="Join" gatewayDirection="converging"/> <sequenceFlow id="flow4" sourceRef="parallelGatewayJoin" targetRef="End"> </sequenceFlow> <endEvent id="End" name="End" />
一个并行网关(其实是任何网关)可以同时拥有切分和汇聚行为。
下面的图形在BPMN 2.0中是完全合法的。
在流程启动之后,A和B任务都会激活。当A和B完成时,C,D和E
任务会被激活。
一个包含网关 - 也叫做OR-gateway - 被用来 进行“条件性”切分或汇聚顺序流。它基本的行为就和一个并行网关一样, 但是它也可以统计条件,在外出顺序流上(切分行为) 和计算,如果这儿有流程离开,可以到达网关(合并行为)。
包含网关显示为一个典型的网关图形,里边有一个圆圈(参考'OR'的语法)。 和唯一网关不同,所有条件表达式被执行(发散或切分行为)。 对于每个表达式结果为true时,一个新的子流程分支就会被创建。 没有定义条件的顺序流会永远被选择(比如。一个子流程 在这种情况下总是会被创建)。
一个收敛的包含网关(合并行为)有一个更困难的执行逻辑。 当一个执行(在BPMN 2.0的语法中叫做Token)到达一个合并包含网关。 就会进行下面的检测(引用规范的文字):
对于每个空的进入顺序流,这里没有Token
在顺序流的图形上面,比如,这里有一个没有直接的路径
(由顺序流组成)从Token到这个顺序流,除非
a) 路径到达了一个包含网关,或
b) 路径到达了一个节点,直接到一个非空的
进入顺序流的包含网关 "
简单来说:当一个流程到达了这个网关,所有的激活流程会被检测
它们是否可以到达包含网关,只是统计顺序流
(注意:条件不会被执行!)。当包含网关被使用时,
它通常用在一个切分/汇聚包含网关对中。在其他情况,
流程行为足够简单,只要通过看图就可以理解了。
当然,不难想象情况,当流程切分和汇聚在复杂的组合,
使用大量的结构,其中包括包含网关。
在那些情况,很可能出现实际的流程行为可能
与建模者的期望不符。所以,当使用包含网关时,要注意
通常的最佳实践是让包含网关成对使用。
下面的图形演示了如何使用包含网关。
(例子来自于Bruce Silver的"BPMN method and style")
我们可以区分下面的情况:
-
现金多于10000,不是国外银行:只有
"Large deposit" 任务会被激活。
-
现金多于10000,是国外银行:
"Large deposit" 和 "Foreign deposit" 任务会被激活。
-
现金少于10000,是国外银行: 只有
"Foreign deposit" 任务会被激活。
-
现金少于10000,不是国外银行: 在这种情况
所有表达式的结果都是false,默认的顺序流会被选择。
在这个例子中国,这意味着"Standard deposit"任务会被激活。
无论在包含网关之后多少任务被激活,右侧的收敛包含网关会等到
左侧的包含网关所有外向顺序流
到达合并网关(有时,只有一个,有时两个)。
看一下org.jbpm.examples.bpmn.gateway.inclusive.InclusiveGatewayTest
可以看到在单元测试中是如何反应这个例子的。
这个例子的XML版本看起来像下面:
<process id="inclusiveGateway" name="BPMN2 Example inclusive gateway">
<startEvent id="start" />
<sequenceFlow id="flow8" sourceRef="inclusiveGatewayMerge" targetRef="theEnd" />
和其他网关类型一样,包含网关类型可以同时拥有合并和切分行为。
在这种情况下,包含网关将先等到所有分支流程到达,
在位每个顺序流进行再次切分之前,这里会有一个表达式执行
为true(获得没有一个表达式)。
一个任务表示工作需要被外部实体完成, 比如人工或自动服务。
重要的是注意BPMN语法的'task'与jPDL语法的区别。 在jPDL中,'task'的概念总是用在人工做一些事情的环境。 的那个流程引擎遇到jPDL中的task,它会创建一个task, 交给一些人的任务列表,然后它会进入等待状态。然而在BPMN 2.0中, 这里有很多任务类型,一些表示等待状态(比如,User Task 一些表示自动活动(比如,Service Task。 所以小心不要混淆了任务的概念,在切换语言的时候。
任务被描绘成一个圆角矩形,一般内部包含文字。 任务的类型(用户任务,服务任务,脚本任务,等等)显示在矩形的左上角,用小图标区别。 根据任务的类型, 引擎会执行不同的功能。
user task是典型的'人工任务', 实际中的每个workflow或BPMN软件中都可以找到。当流程执行到达这样一个user task时, 一个新人工任务就会被创建,交给用户的任务列表。
和manual task的主要区别是 (也与人工工作对应)是流程引擎了解任务。 引擎可以跟踪竞争,分配,时间,其他,这些不是manual task的情况。
user task描绘为一个圆角矩形,在左上角是一个小用户图标。
user task被定义为下面的BPMN 2.0 XML:
<userTask id="myTask" name="My task" />
根据规范,可以使用多种实现(WebService, WS-humantask,等等)。 通过使用implementation属性。 当前,只有标准的jBPM任务机制才可以用,所以这里(还)没有 定义'implementation'属性的功能。
BPMN 2.0规范包含了一些方法把任务分配给用户,组,角色等等。 当前的BPMN 2.0 jBPM实现允许使用一个 resourceAssignmentExpression来分配任务, 结合humanPerformer or PotentialOwner结构。 这部分希望在未来的版本里能够进一步演化。
potentialOwner用来在你希望确定用户,组,角色的时候。 这是一个task的候选人。 参考下面的例子。这里的'My task'任务的候选人组是'management'用户组。 也要注意,需要在流程外部定义一个资源, 这样任务分配器可以引用到这个资源。 实际上,任何活动都可以引用一个或多个资源元素。 目前,只需要定义这个资源就可以了(因为它是规范中的一个必须的元素), 但是在以后的发布中会进行加强(比如,资源可以拥有运行时参数)。
<resource id="manager" name="manager" />
…
注意,我们使用了一个特定的后缀
(jbpm:type=“group”),来定义这是一个用户组的分配方式。
如果删除了这个属性,就会默认使用用户组的语法
(在这个例子中也是没问题的)。
现在假设Peter和Mary是management组的成员
(这里使用默认的身份服务):
identityService.createGroup(“management”);
identityService.createUser(“peter”, “Peter”, “Pan”);
identityService.createMembership(“peter”, “management”);
identityService.createUser(“mary”, “Mary”, “Littlelamb”);
identityService.createMembership(“mary”, “management”);
Peter和Mary都可以在他们的任务列表中看到这条任务
(代码来自实例单元测试):
// Peter and Mary are both part of management, so they both should see the task
Listtasks = taskService.findGroupTasks(“peter”);
assertEquals(1, tasks.size());
tasks = taskService.findGroupTasks(“mary”);
assertEquals(1, tasks.size());
// Mary claims the task
Task task = tasks.get(0);
taskService.takeTask(task.getId(), “mary”);
assertNull(taskService.createTaskQuery().candidate(“peter”).uniqueResult());
taskService.completeTask(task.getId());
assertProcessInstanceEnded(processInstance);
当分配方式应该是候选用户时,
只需要使用jbpm:type="user"属性。
jbpm:type=“user”>
peter
在这个例子里,Peter将可以看到任务,因为他是这个任务的候选用户。
Listtasks = taskService.createTaskQuery().candidate(“peter”).list();
human performer用来,当你想把一个任务直接分配给一个人,
组,角色时。这个方法的使用方式
看起来和potential owner很像。
<resource id="employee" name="employee" />
…
在这个例子中,任务会直接分配给Mary。
她可以在自己的任务列表中看到这个任务:
Listtasks = taskService.findPersonalTasks(“mary”);
因为任务分配已经完成,通过使用
formalExpression,它也可以定义表达式
在运行期解析。表达式本身需要放在
${}中,这和jBPM一样。
比如,如果流程变量'user'被定义了,然后,它可以用在表达式中。
当然也可以使用更复杂的表达式。
<userTask id="myTask" name="My User task">
注意不需要在humanPerformer元素中使用’jbpm:type’,因为只能进行
直接用户分配。如果任务需要被分配给一个角色或一个组,
使用potentialOwner和group类型(当你把任务分配给一个组时,
组中的所有成员都会成为候选用户 -
参考potentialOwner的用法)。
Service Task是一个自动活动,它会调用一些服务,
比如web service,java service等等。当前jBPM引擎
只支持调用java service,但是web service的调用
已经在未来的版本中做了计划。
定义一个服务任务需要好几行XML(这里就可以看到BPEL的影响力)。
当然,在不久的未来,我们希望有工具可以把这部分大量的简化。
一个服务任务需要如下定义:
<serviceTask id="MyServiceTask" name="My service task"
implementation=“Other” operationRef=“myOperation” />
服务任务需要一个必填的id和一个可选的
name。implementation元素
是用来表示调用服务的类型。可选值是WebService, Other或者Unspecified。
因为我们只实现了Java调用,
现在只能选择Other。
服务任务将调用一个操作,operation的id
会在operationRef属性中引用。
这样一个操作就是下面实例的
interface的一部分。每个操作都至少有一个
输入信息,并且
最多有一个输出信息。
<interface id="myInterface"
name="org.jbpm.MyJavaServicek">
<operation id="myOperation2" name="myMethod">
<inMessageRef>inputMessage</inMessageRef>
<outMessageRef>outputMessage</outMessageRef>
</bpmn:operation>
对于java服务,接口的名称用来
指定java类的全类名。操作的名称
用来指定将要调用方法名。
输入/输出信息表示着java方法的参数/返回值,
定义如下所示:
BPMN中很多元素叫做’item感知’,包括这个消息结构。
这意味着它们会在流程执行过程中保存或读取item。
负责这些元素的数据结构需要使用ItemDefinition。
在这个环境下,消息指定了它的数据结构,
通过引用
structureRef属性中定义的ItemDefinition。
注意,这写不是标准的BPMN 2.0标准(因此都有'jbpm'的前缀)。
实际上,根据标准,ItemDefinition不应该包含多余一个数据结构定义。
实际在输入参数的映射,使用一个数据结构,
在serviceTask的ioSpecification章节已经完成了。
然而,当前jBPM BPMN 2.0实现还没有实现那个结构。
所以,这意味着当前使用的上面这种方法,
很可能在不久的未来就会出现变化。
重要提醒:接口,ItemDefinitions和消息需要定义在
<process>外边。参考实例
ServiceTaskTest的实际流程和单元测试。
脚本任务时一个自动活动,当到达这个任务的时候
流程引擎会执行一个脚本。脚本任务使用方式如下:
<scriptTask id="scriptTask" name="Script Task" scriptLanguage="bsh">
脚本任务,除了必填id和可选的
name之外,还允许指定
scriptLanguage和script。
因为我们使用了JSR-223(java平台的脚本语言),修改脚本语言就需要:
- 把scriptLanguage 属性修改为JSR-223兼容的名称
- 在classpath下添加JSR规范的ScriptEngine实现
上面的XML对应图形如下所示(添加了空开始和结束事件)。
像上面例子中显示的那样,可以在脚本中使用流程变量。
我们现在可以启动一个这个例子的流程,也要提供一些随机生成的输入变量:
Map<String, Object> variables = new HashMap<String, Object>();
Integer[] values = { 11, 23, 56, 980, 67543, 8762524 };
variables.put(“input”, values);
executionService.startProcessInstanceBykey(“scriptTaskExample”, variables);
在输出控制台里,我们现在可以看到执行的执行的脚本:
11 x 2 = 22
23 x 2 = 46
56 x 2 = 112
980 x 2 = 1960
67543 x 2 = 135086
8762524 x 2 = 17525048
手工任务时一个由外部人员执行的任务,但是没有指定是
一个BPM系统或是一个服务会被调用。在真实世界里,有很多例子:
安装一个电话系统,使用定期邮件发送一封信,
用电话联系客户,等等。
<manualTask id="myManualTask" name="Call customer" />
手工任务的目标更像 文档/建模提醒的,因为它
对流程引擎的运行没有任何意义,因此,当流程引擎遇到一个手工任务时
会简单略过。
receive task是一个任务会等到外部消息的到来。
除了广泛使用的web service用例,规范在其他环境中的使用也是一样的。
web service用例还没有实现,
但是receive task已经可以在java环境中使用了。
receive task显示为一个圆角矩形(和task图形一样)
在左上角有一个小信封的图标。
在java环境中,receive task没有其他属性,除了id和name(可选),
行为就像是一个等待状态。为了在你的业务流程中使用等待状态,
只需要加入如下几行:
<receiveTask id="receiveTask" name="wait" />
流程执行会在这样一个receive task中等待。流程会使用
熟悉的jBPM signal methods来继续执行。
注意,这些可能在未来改变,因为'signal'
在BPMN 2.0中拥有完全不同的含义。
Execution execution = processInstance.findActiveExecutionIn("receiveTask");
executionService.signalExecutionById(execution.getId());
子流程的第一目的是实现流程的“继承”,意味着
设计者可以创建多个不同“级别”的细节。顶级视图理解为做
一件事情的最高级别方式,最低的级别
就关注具体细节。
比如下面的图形。在这个模型里,只有最高级的步骤显示出来。
实际的实现"Check credit"步骤隐藏在
折叠子流程中,这可能是最完美的级别
细节来讨论业务流程,与最终用户。
子流程的第二种主要功能是子流程"容器"作为
事件的作用域。当一个事件在子流程中触发时,获取事件
在子流程的边界上就会首先获得这个事件。
定义在顶级流程的子流程被称为内嵌子流程。
上级流程中的所有流程数据也可以在子流程中使用。
下面的图形演示了
上面模型的展开形式。
这部分的XML内容看起来像是这样:
<process id="embeddedSubprocess">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="receiveOrder" />
<receiveTask name="Receive order" id="receiveOrder" />
<sequenceFlow id="flow2" sourceRef="receiveOrder" targetRef="checkCreditSubProcess" />
<subProcess id="checkCreditSubProcess" name="Credit check">
...
</subProcess>
<sequenceFlow id="flow9" sourceRef="checkCreditSubProcess" targetRef="theEnd" />
<endEvent id="theEnd" />
注意在子流程内部,事件,活动,任务的定义与顶级流程中是一样的。
(因此在上面的XML例子中是三个"…")
子流程只允许有一个空开始事件。
结论,一个内嵌子流程会像下面这样运行:当一个流程执行到子流程,
一个子分支会被创建。子分支以后还可以创建其他子分支,
比如,当一个并发网关使用在子流程中。
子流程,只会在没有任何活动的分支时才会完成。
这时,上级流程会
继续执行。
比如,在下面的图形中,"Third task"
只会在"First task"和"Second task"都完成时才会到达。
子流程的其中一个任务不会触发子流程向下运行,
因为另一个分支在子流程中还是活动的。
子流程可以拥有多个开始事件。这种情况下,多个并行分支就在流程中存在。
子流程完成的规则没有改变:
子流程只有在所有并行分支都完成时
才会结束。
内嵌子流程也是可以的。这时,流程可以分散成多个不同级别的细节。
这里没有对内嵌级别做任何限制。
实现提醒:按照BPMN2规范,一个没有外向顺序流的活动会隐式结束当前分支。
然而当前,必须特别指定一个结束事件
在子流程中,来结束一个分支,
这会在未来的规范兼容过程中加强。
定时启动事件用来表示流程需要在指定时间启动。 可以指定一个特殊的时间点(比如,2010年10月10日下午5点), 但是也可以用一个通常的时间(比如,每个周五的半夜)。
定时启动事件看起来是在圆圈中有一个表的图标。
使用定时启动事件,要添加一个timerEventDefinition元素 在开始事件元素下面:
<startEvent name="Every Monday morning" id="myStart">
可以使用下面的时间定义:
10/10/2099 00:00:00
时间段表达式:
quantity [business] {second | seconds | minute | minutes |
hour | hours | day | days | week |
weeks | month | months | year | years}
这与jPDL中的定时器时间段定义是完全相同的。注意,
BPMN2定时启动事件也可以理解"业务时间"。
这允许,比如定义一个"业务日期"作为周期,从早九点到晚五点。
这样,从下午5点到上午9点的时间就不会被计算,
当事件触发的事件被计算的时候。
请参考jPDL用户手册,获得更多信息,关于如何自定义业务日历。
下面的例子演示了定时启动事件会启动给一个新流程实例
每隔5个小时。
<startEvent id="myStartEvent" >
Cron 表达式:
虽然时间段表达式已经很好的覆盖了
延迟定义,有时,它们不太好用。
当,比如,一个流程实例应该在每个周五晚上23点执行,
cron表达式允许一个更自然的方式来定义这种重复的行为的发生。
下面的例子演示了定时启动事件会在
每周五的23点启动一个新的流程实例。
0 0 23 ? * FRI
jBPM中实现的定时启动事件也拥有如下的特性:
-
声明了定时启动事件的流程定义,也可以当做一个无启动事件启动。
这就是说,比如调用
executionService.startProcessInstanceByKey(key)也是可以的。
-
定时启动事件的内部实现是一个定时任务。这意味着
必须配置job executor,定时启动事件才能工作。
这种实现的优点是,定时启动事件的触发是事务性的
(比如,如果定时启动事件后的一个服务任务失败了,
事务就会回滚,定时启动事件就会稍后执行)
并且可以应付服务器崩溃。(比如,当服务器备份时,
定时启动事件会由job executor获取,
就像什么也没有发生一样)。
-
当一个拥有定时启动事件的流程定义发布新版本时,
旧版本的定时启动事件的任务会被从系统中删除。这意味着
只有最新版本的流程定义会被使用
来创建一个流程实例。
中间事件用来表示在流程执行过程中发生的事件(比如, 在流程启动之后,在它完成之前)。中间事件看起来就像 一个有着双边线的圆圈,圆圈中的图标表示了事件的类型。
这儿有好多种中间事件类型,比如定时器事件,触发事件,传播事件,等等。 中间事件既可以抛出也可以捕获:
内部定时器事件用来表示一个流程的延迟。 直接的用例是收集数据, 只在没有人工作的晚上执行大量的逻辑,等等。
注意,一个内部定时器只能是一个捕获事件(抛出一个定时器事件时没有意义的)。 下面的图形中演示了内部定时器事件的图形形式。
定义一个内部定时器事件,在XML里像是这样:
<intermediateCatchEvent id="myTimer" name="Wait for an hour">
有两种方法可以来指定延迟,使用timeCycle
或 a timeDate。在上面例子中,使用的是 timeCycle 。
下面的延迟定义也是可以用的(这与启动定时器是相同的)。
-
timeDate: 指定一个固定时间,
这时定时器会触发,流程会继续。默认的时间格式是
"dd/MM/yyyy hh:mm:ss"。这是引擎范围的,可以通过设置
配置中的jbpm.duedatetime.format属性来改变。
<intermediateCatchEvent id="myTimer" >
10/10/2099 00:00:00
timeCycle: 指定一个延迟时间段,
相对于流程进入定时器事件时。可以用两种定义方式:
时间段表达式:
quantity [business] {second | seconds | minute | minutes |
hour | hours | day | days | week |
weeks | month | months | year | years}
这与jPDL中的定时器时间段定义是完全相同的。注意,
BPMN2的内部定时器事件也可以理解"业务时间"。
这允许,比如定义一个"业务日期"作为周期,从早九点到晚五点。
定时器会从下午四点开始中间等待两个小时,然后会在第二个工作日的上午十点触发。
请参考jPDL用户手册,获得更多信息,
关于如何自定义业务日历。
<intermediateCatchEvent id="intermediateTimer" >
5 hours
Cron 表达式: 虽然时间段表达式已经很好的覆盖了
延迟定义,有时,它们不太好用。
当,比如,流程需要延迟到周五晚上23点,这样流程可以在周末执行,
时间段表达式就难以使用了
(你需要一些东西,像"#{calculated_value} 秒",
你可以先计算这个值)。
Cron 表达式
允许我们定义延迟,这种方式很多人都知道(因为CRON表达式
在Unix中用来定义任务)。注意一个cron表达式
通常用来定义重复执行。在这个环境下,就是
第一个满足cron表达式的时间点
用来设置定时器事件的持续时间(所以不会重复执行)。下面的例子
展示了一个内部定时器事件是如何执行流程
在下一个星期五晚上23点执行。
0 0 23 ? * FRI
前提条件:为了运行实例,我们假设
已经在JBoss server中安装了jBPM控制台。如果没有,
请先执行'demo.setup.jboss'安装脚本。
我们实现的业务流程实现起来像下面这样:
你可能已经看过这个例子了,因为我们也在发布包中的
实例中使用jPDL实现过它了。
业务流程很简单:一个员工可以启动一个新流程,
申请一定时间的假期。在请求任务完成之后,
经理会在任务列表中看到审核任务。
经理可以决定批准或驳回这个申请。
根据outcome(那是外向顺序流上的小菱形 -
这意味着在顺序流上有条件表达式),
会发送一个驳回信息或者流程结束。注意,实际上我们这里使用了简写:
不是在'verify request'任务的外向顺序流上设置表达式,
我们可以在用户任务之后使用一个唯一网关来控制流程的流向。
也要注意,因为我们还没有实现泳道(可能在下一个版本会实现),
所以很难看到谁在业务流程中。
流程的XML版本看起来像下面这样:
<process id="vacationRequestProcess" name="BPMN2 Example process using task forms">
<startEvent id="start" />
<sequenceFlow id="flow1" name="fromStartToRequestVacation"
sourceRef="start" targetRef="requestVacation" />
<userTask id="requestVacation" name="Request Vacation"
implementation="other">
<potentialOwner resourceRef="user" jbpm:type="group">
<resourceAssignmentExpression>
<formalExpression>user</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
<rendering id="requestForm">
<jbpm:form>org/jbpm/examples/bpmn/usertask/taskform/request_vacation.ftl</jbpm:form>
</rendering>
</userTask>
<sequenceFlow id="flow2"
name="fromRequestVacationToVerifyRequest" sourceRef="requestVacation"
targetRef="verifyRequest" />
<userTask id="verifyRequest" name="Verify Request"
implementation="other">
<potentialOwner resourceRef="user" jbpm:type="group">
<resourceAssignmentExpression>
<formalExpression>manager</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
<rendering id="verifyForm">
<jbpm:form>org/jbpm/examples/bpmn/usertask/taskform/verify_request.ftl</jbpm:form>
</rendering>
</userTask>
<sequenceFlow id="flow3" name="fromVerifyRequestToEnd"
sourceRef="verifyRequest" targetRef="theEnd">
<conditionExpression xsi:type="tFormalExpression">
${verificationResult == 'OK'}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4"
name="fromVerifyRequestToSendRejectionMessage" sourceRef="verifyRequest"
targetRef="sendRejectionMessage">
<conditionExpression xsi:type="tFormalExpression">
${verificationResult == 'Not OK'}
</conditionExpression>
</sequenceFlow>
<scriptTask id="sendRejectionMessage" name="Send rejection Message"
scriptLanguage="bsh">
<script>
<![CDATA[System.out.println("Vacation request refused!");]]>
</script>
</scriptTask>
<sequenceFlow id="flow5"
name="fromSendRejectionMessageToEnd" sourceRef="sendRejectionMessage"
targetRef="theEnd" />
<endEvent id="theEnd" name="End" />
注意:当你在安装demo时,自己都已经安装了。
也要注意,我们这里使用了脚本任务,为了快速的编写一些输出,
而不是发送真实的信息(图形显示了一个service task)。
也要注意,我们这里在任务分配中做了一些简略
(会在下一个版本进行修复)。
在这个实现使用的结构中覆盖了之前章节中的所有内容。
也要注意我们这里使用了任务表单功能,
这是一个自定义jBPM扩展,
可以为用户任务渲染元素。
<userTask id="verifyRequest" name="Verify Request"
implementation="other">
BPMN 2.0里任务表单的机制与jPDL里完全一样。
表单自身是一个Freemarker模板文件,
需要放在发布中。比如,这个 ‘verify_request.ftl’
看起来像下面这样:
<form action="${form.action}" method="POST" enctype="multipart/form-data"> <h3>Your employee, ${employee_name} would like to go on vacation</h3> Number of days: ${number_of_days}<br/> <hr> In case you reject, please provide a reason:<br/> <input type="textarea" name="reason"/><br/> <input type="submit" name="verificationResult" value="OK"> <input type="submit" name="verificationResult" value="Not OK"> </form>
注意,流程变量可以使用
KaTeX parse error: Expected 'EOF', got '&' at position 219: …rogramlisting">&̲lt;input type="…{verificationResult == ‘OK’}
流程现在可以发布了。你可以使用ant的发布任务来做这些事情(参考实例),
或者你可以指定你的jBPM配置到控制台的数据库。
为了用编程的方式发布你的流程,你需要把任务表单添加到你的发布中:
NewDeployment deployment = repositoryService.createDeployment();
deployment.addResourceFromClasspath(“org/jbpm/examples/bpmn/usertask/taskform/vacationrequest.bpmn.xml”);
deployment.addResourceFromClasspath(“org/jbpm/examples/bpmn/usertask/taskform/request_vacation.ftl”);
deployment.addResourceFromClasspath(“org/jbpm/examples/bpmn/usertask/taskform/verify_request.ftl”);
deployment.deploy();
你现在可以嵌入(或在单独的服务器中)这个业务流程,使用熟悉的jBPM API操作。
比如,流程实例现在可以使用
key来启动(比如,BPMN 2.0的流程id):
ProcessInstance pi = executionService.startProcessInstanceByKey(“vacationRequestProcess”);
任务列表可以这样获得:
Task requestTasktask = taskService.createTaskQuery().candidate(“peter”).uniqueResult();
当像jBPM控制器数据库发布时,你应该看到我们的新业务流程了。
在你启动一个新流程后,一个新任务应该在员工的任务列表中了。
当点击'view'以后,任务表单会被显示出来,
在这里填写未来会在流程中使用的变量。
在任务结束之后,经理会在他的任务列表中看到新的审核任务。
他现在可以通过或驳回请假申请,基于员工的输入。
因为数据库表结构没有变化,我们只是把BPMN 2.0添加到了jBPM PVM上面,
所有已存的报表都可以用于我们的新BPMN 2.0流程中。