事件(Event)
事件用来表明流程的生命周期中发生了什么事. 事件总是画成一个圆圈.
在BPMN 2.0中, 事件有两大分类: 捕获(catching)或 触发(throwing) 事件.
捕获(Catching): 当流程执行到事件, 它会等待被触发. 触发的类型是由内部图表或XML中的类型声明来决定的. 捕获事件与触发事件在显示方面是根据内部图表是否被填充来区分的(白色的).
触发(Throwing): 当流程执行到事件, 会触发一个事件. 触发的类型是由内部图表或XML中的类型声明来决定的. 触发事件与捕获事件在显示方面是根据内部图表是否被填充来区分的(被填充为黑色).
事件定义
事件定义, 决定了事件的语义. 如果没有事件定义, 这个事件就不做什么特别的事情.
没有设置事件定义的开始事件不会在启动流程时做任何事情. 如果给开始事件添加了一个事件定义(比如定时器事件定义)我们就声明了开始流程的事件 "类型 " (这时定时器事件监听器会在某个时间被触发).
定时器事件定义
定时器事件是根据指定的时间触发的事件。可以用于开始事件, 中间事件或边界事件.
timeDate: ISO 8601格式指定一个确定的时间, 触发事件的时间.示例:
<timerEventDefinition>
<timeDate>2011-03-11T12:13:14</timeDate>
</timerEventDefinition>
timeDuration: 指定定时器之前要等待多长时间, timeDuration可以设置为timerEventDefinition的子元素. 使用ISO 8601规定的格式 (由BPMN 2.0规定)。示例(等待10天)。
<timerEventDefinition>
<timeDuration>P10D</timeDuration>
</timerEventDefinition>
timeCycle: 指定重复执行的间隔, 可以用来定期启动流程实例, 或为超时时间发送多个提醒. timeCycle元素可以使用两种格式, 第一种是
ISO 8601标准的格式. 示例(重复3次,每次间隔10小时):
<timerEventDefinition>
<timeCycle>R3/PT10H</timeCycle>
</timerEventDefinition>
另外, 你可以使用cron表达式指定timeCycle, 下面的例子是从整点开始, 每5分钟执行一次:
0 0/5 * * * ?
注意: 第一个数字表示秒,而不是像通常Unix cron中那样表示分钟.
你可以在定时器事件定义中使用表达式, 这样你就可以通过流程变量来影响那个定时器定义. 流程定义必须包含ISO 8601(或cron)格式的字符串, 以匹配对应的时间类型.
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>${duration}</timeDuration>
</timerEventDefinition>
</boundaryEvent>
注意: 计时器,只有当启用了异步执行(flowable.cfg.xml中的asyncExecutorActivate需要设置为true, 因为异步执行默认情况下禁用的).
开始事件
开始事件用来指明流程在哪里开始. 开始事件的类型(消息到达时开始还是等待特定时间间隔 等等), 定义了流程如何启动, 这通过事件中不同的小图表来展示. 在XML中, 这些类型是通过声明不同的子元素来区分的.
开始事件都是捕获事件: 最终这些事件都是(一直)等待着, 直到对应的触发时机出现.
在启动事件中, 可以指定以下Flowable特定属性:
initiator: 当流程启动时, 保存经过身份验证的用户标识的变量名称. 示例如下:
<startEvent id="request" flowable:initiator="initiator" />
经过身份验证的用户必须使用IdentityService.setAuthenticatedUserId(String)
try-finally
块中的方法进行设置, 如下所示:
try {
identityService.setAuthenticatedUserId("bono");
runtimeService.startProcessInstanceByKey("someProcessKey");
} finally {
identityService.setAuthenticatedUserId(null);
}
空开始事件
空开始事件技术上意味着没有指定启动流程实例的触发条件. 这就是说引擎不能预计什么时候流程实例会启动. 空开始事件用于, 当流程实例要通过API启动的场景, 通过调用startProcessInstanceByXXX
方法.
ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();
图形表示法
空开始事件显示成一个圆圈,没有内部图表(没有触发类型)
XML表示
空开始事件的XML结构是普通的开始事件定义, 没有任何子元素(其他开始事件类型都有一个子元素来声明自己的类型)
<startEvent id="start" name="my start event" />
计时器开始事件
定时开始事件用来在指定的时间创建流程实例. 它可以同时用于只启动一次流程和应该在特定时间间隔启动多次的流程.
注意: 子流程不能使用定时开始事件.
注意: 定时开始事件在流程发布后就会开始计算时间. 不需要调用startProcessInstanceByXXX
, 虽然也可以调用启动流程的方法, 但是那会导致调用startProcessInstanceByXXX
时启动过多的流程
注意: 当包含定时开始事件的新版本流程部署时, 对应的上一个定时器就会被删除. 这是因为通常不希望自动启动旧版本流程的流程实例.
图形表示法
定时开始事件显示为了一个圆圈, 内部是一个表.
XML内容
定时开始事件的XML内容是普通开始事件的声明, 包含一个定时定义子元素.请参考定时定义查看配合细节.
示例: 流程会启动4次, 每次间隔5分钟, 从2011年3月11日 12:13开始计时.
<startEvent id="theStart">
<timerEventDefinition>
<timeCycle>R4/2011-03-11T12:13/PT5M</timeCycle>
</timerEventDefinition>
</startEvent>
例如: 进程将在选定的日期开始一次
<startEvent id="theStart">
<timerEventDefinition>
<timeDate>2011-03-11T12:13:14</timeDate>
</timerEventDefinition>
</startEvent>
顺序流
顺序流是连接两个流程节点的连线. 流程执行完一个节点后, 会沿着节点的所有外出顺序流继续执行.
图形标记
顺序流显示为从起点到终点的箭头. 箭头总是指向终点.
XML内容
顺序流需要流程范围内唯一的id, 以及对起点与终点元素的引用.
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
条件顺序流
可以为顺序流定义一个条件. 离开一个BPMN 2.0节点时, 默认会计算外出顺序流的条件. 如果条件结果为true, 就会选择外出顺序流继续执行. 当多条顺序流被选中时, 就会创建多条分支, 流程会继续以并行方式继续执行.
注意: 上面的讨论仅涉及BPMN 2.0节点(和事件), 不包括网关. 网关会用特定的方式处理顺序流中的条件, 这与网关类型相关.
图形标记
条件顺序流显示为一个正常的顺序流, 不过在起点有一个菱形. 条件表达式也会显示在顺序流上.
XML内容
条件顺序流定义为一个正常的顺序流, 包含conditionExpression
子元素.
注意目前只支持tFormalExpressions
, 如果没有设置xsi:type=""
, 就会默认值支持目前支持的表达式类型.
<sequenceFlow id="flow" sourceRef="theStart" targetRef="theTask">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>
</sequenceFlow>
目前, conditionalExpressions
只能与UEL一起使用.
下面的例子引用了流程变量的数据, 通过getter调用JavaBean.
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.price > 100 && order.price < 250}]]>
</conditionExpression>
这个例子调用一个解析为布尔值的方法
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${order.isStandardOrder()}]]>
</conditionExpression>
网关
网关用来控制流程的流向.
图形标记
网关显示成菱形图形, 内部有一个小图标. 图标表示网关的类型.
独占网关
独占网关(也叫异或(XOR)网关), 用来在流程中实现决策.
当流程执行到这个网关, 所有外出顺序流都会被处理一遍. 其中条件解析为true的顺序流(或者没有设置条件, 概念上在顺序流上定义了一个'true')会被选中,让流程继续运行.
注意这里的外出顺序流与BPMN 2.0通常的概念是不同的. 通常情况下, 所有条件结果为true的顺序流都会被选中, 以并行方式执行, 但独占网关只会选择一条顺序流执行. 就是说, 虽然多个顺序流的条件结果为true, 那么XML中的第一个顺序流(也只有这一条)会被选中, 并用来继续运行流程. 如果没有选中任何顺序流, 会抛出一个异常.
图形标记
独占网关显示成一个普通网关()比如,菱形图形), 内部是一个“X”图标, 表示异或(XOR)语义. 注意, 没有内部图标的网关, 默认为独占网关. BPMN 2.0规范不允许在同一个流程定义中同时使用没有X和有X的菱形图形.
XML内容
独占网关的XML内容是很直接的: 用一行定义了网关, 条件表达式定义在外出顺序流中. 参考条件顺序流获得这些表达式的可用配置.
参考下面模型实例
它对应的XML内容如下:
<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
<conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
<conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
<conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>
任务
用户任务
用户任务用来设置必须由人员完成的工作. 当流程执行到用户任务, 会创建一个新任务, 并把这个新任务加入到分配人或群组的任务列表中.
图形标记
用户任务显示成一个普通任务(圆角矩形), 左上角有一个小用户图标.
XML内容
XML中的用户任务定义如下. id属性是必须的. name属性是可选的.
<userTask id="theTask" name="Important task" />
用户任务也可以设置描述.实际上所有BPMN 2.0元素都可以设置描述.
添加documentation元素可以定义描述.
<userTask id="theTask" name="Schedule meeting" >
<documentation>
Schedule an engineering meeting for next week with the new hire.
</documentation>
...
描述文本可以用标准的Java方式从任务中获取:
task.getDescription()
持续时间
任务可以用一个字段来描述任务的持续时间. 可以使用查询API来对持续时间进行搜索, 根据在时间之前或之后进行搜索.
我们提供了一个节点扩展, 在任务定义中设置一个表达式, 这样在任务创建时就可以为它设置初始持续时间. 表达式应该是java.util.Date
, java.util.String (ISO8601格式)
, ISO8601持续时间(比如PT50M)或null.
例如: 你可以在流程中使用上述格式输入日期, 或在前一个服务任务中计算一个时间. 这里使用了持续时间, 持续时间会基于当前时间进行计算, 再通过给定的时间段累加. 比如, 使用"PT30M"作为持续时间, 任务就会从现在开始持续30分钟.
<userTask id="theTask" name="Important task" flowable:dueDate="${dateVariable}"/>
任务的持续时间也可以通过TaskService修改或在TaskListener中通过传入的DelegateTask参数修改.
用户分配
用户任务可以直接分配给一个用户. 这可以通过humanPerformer元素定义. humanPerformer定义需要一个 resourceAssignmentExpression来实际定义用户. 当前, 只支持formalExpressions.
<process >
...
<userTask id='theTask' name='important task' >
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>kermit</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
只有一个用户可以坐拥任务的执行者分配给用户. 在Flowable中, 用户叫做执行者. 拥有执行者的用户不会出现在其他人的任务列表中, 只能出现执行者的个人任务列表中.
直接分配给用户的任务可以通过TaskService像下面这样获取:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
任务也可以加入到人员的候选任务列表中. 这时, 需要使用potentialOwner 元素. 用法和humanPerformer元素类似. 注意它需要指定表达式中的每个项目是人员还是群组 .
<process >
...
<userTask id='theTask' name='important task' >
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
使用potentialOwner元素定义的任务, 可以像下面这样获取(使用TaskQuery的发那个发与查询设置了执行者的任务类似):
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");
任务分配扩展
当分配不复杂时, 用户和组的设置非常麻烦. 为避免复杂性,可以使用用户任务的自定义扩展.
assignee属性: 这个自定义扩展可以直接把用户任务分配给指定用户.
<userTask id="theTask" name="my task" flowable:assignee="kermit" />
它和使用上面定义的humanPerformer效果完全一样.
candidateUsers属性: 这个自定义扩展可以为任务设置候选人.
<userTask id="theTask" name="my task" flowable:candidateUsers="kermit, gonzo" />
它和使用上面定义的potentialOwner效果完全一样. 注意它不需要像使用potentialOwner通过user(kermit)声明, 因为这个属性只能用于人员.
candidateGroups属性: 这个自定义扩展可以为任务设置候选组.
<userTask id="theTask" name="my task" flowable:candidateGroups="management, accountancy" />
它和使用上面定义的potentialOwner效果完全一样. 注意它不需要像使用potentialOwner通过group(management)声明, 因为这个属性只能用于群组.
candidateUsers 和 candidateGroups 可以同时设置在同一个用户任务中.
Java服务任务
java服务任务用来调用外部java类.
图形标记
服务任务显示为圆角矩形, 左上角有一个齿轮小图标.
XML内容
有4钟方法来声明java调用逻辑:
- 实现JavaDelegate或ActivityBehavior
- 执行解析代理对象的表达式
- 调用一个方法表达式
- 调用一直值表达式
执行一个在流程执行中调用的类, 需要在'flowable:class'属性中设置全类名.
<serviceTask id="javaService"
name="My Java Service Task"
flowable:class="org.flowable.MyJavaDelegate" />
也可以使用表达式调用一个对象. 对象必须遵循一些规则, 并使用flowable:class
属性进行创建.
<serviceTask id="serviceTask" flowable:delegateExpression="${delegateExpressionBean}" />
这里delegateExpressionBean
是一个bean, 它实现了JavaDelegate在Spring容器中定义的接口.
要指定应评估的UEL方法表达式, 请使用属性flowable:expression
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{printer.printMessage()}" />
printMessage将在名为的命名对象上调用方法(不带参数)printer.
也可以使用表达式中使用的方法传递参数:
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{printer.printMessage(execution, myVar)}" />
这会调用名为printer对象上的方法printMessage. 第一个参数是DelegateExecution, 在表达式环境中默认名称为execution. 第二个参数传递的是当前流程的名为myVar的变量.
要指定执行的UEL值表达式, 需要使用flowable:expression
属性.
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{split.ready}" />
ready属性的getter方法, getReady(无参数), 会作用于名为split的bean上. 这个对象会被解析为流程对象和(如果合适)spring环境中的对象.