目录
Activiti与BPMN2.0规范(一)
BPMN2.0规范包含很多种模型。目前Activiti可以支持在大多数情况下常用的模型,并且在实现规范的基础上进行了功能和使用性扩展。在讲解的过程中首先会介绍BPMN2.0规范中的定义方式,如果Activiti添加了扩展属性、配置,也会介绍其含义及配置方法。
1、启动事件与结束事件
启动事件与结束事件作为BPMN2.0规范中很重要的一部分,分别负责一个流程的开始和结束。一个完整的流程可以分为两大类:启动事件、结束事件。每一个大类又可以根据功能不同分为多个小类。
所有的开始事件均以细线圆形为基础,其他事件都是在其基础上嵌入对应类型的图形来表示一种启动事件的。
所有的结束事件均以粗线圆形为基础,其他事件都是在其基础上嵌入对应类型的图形来表示一种结束事件的。
1.1、启动事件
每个流程总是以启动事件作为入口(可以是不同类型的启动事件)。启动事件在BPMN2.0规范中以一个细线圆形图形表示。启动事件又可以分为以下三种类型:
- 空启动事件
- 定时启动事件
- 异常启动事件
启动事件都是“捕获型”的,等待第三方触发后才可以启动。在Activiti中可以通过调用API触发启动事件,在Hello World示例中就是通过调用runtimeService.startProcessInstance()方法触发来启动一个流程实例的。
1.1.1、空启动事件
图4-1展示了空启动事件的图形化表示,对应的XML描述如下所示:
<startEvent id="startEvent" name="StartEvent"></startEvent>
startEvent表示一个启动事件,由于startEvent标签中没有任何其他的元素定义,所以称之为空启动事件,这也是其他启动事件的XML基本表示。
Activiti根据实际需求在空启动事件的基础上进行了扩展。下表列举了Activiti扩展的空启动事件属性。
属性名称 | 属性说明 | 示例 |
---|---|---|
activiti:formKey | Activiti扩展的formKey属性,可以用来指定空启动事件关联的表单文件。 | <startEvent id="startEvent" activiti:formKey="apply.form"/> |
activiti:initiator | Activiti扩展的initiator属性,可以用来记录启动流程的人的ID(也可以是用户的名称),启动流程之后此属性指定的变量就会自动设置当前人的名称 | <startEvent id="startEvent" activiti:initiator="startUserId" /> 在流程流转中就可以通过获取变量startUserId得到启动时操作人ID |
除了以上属性之外,还可以在空启动事件上定义表单属性,同样在用户任务上也适用。启动事件可以配置相关表单,引擎提供了外置表单和动态表单两种表单形式。为了后面更好地理解这两种表单,先简单介绍这两种表单的区别。
- 外置表单:表单的内容都存放在一个单独的“
.form
”文件中,可以是任何文本字符,一般用HTML书写,并且可以使用UEL占位符以达到动态填充的目的。启动事件中的activiti:formKey属性就是用来指定使用哪个表单文件(扩展名为.form)。 - 动态表单:此类型表单不再使用activiti:formKey属性指定表单文件,而是在流程定义文件中设置表单中元素集合,包含各种输入框、下拉框等。对应的XML标示如下:
<activiti:formProperty id="name" name="Name" type="string"></activiti:formProperty>
activiti:formProperty可以有多个,对应一个表单中的多个字段(Field)。在实际应用中可以通过引擎提供的API读取、提交这些表单元素。
1.1.2、定时启动事件
定时启动事件可以用于一次性定时启动、特定时间间隔后启动,在BPM2.0规范中用一个形象的钟表图形表示,如图4-3所示。
定时启动事件常用于定期循环流程或一次性流程,例如公司每个月要产生一次业绩报表,可以设定每个月1号系统自动生成报表并自动启动公司的业务处理流程。
对应的,定时启动事件的XML描述和图形方式类似,在基本的启动事件中嵌套了一个定时事件定义timerEventDefinition,对应的XML描述如下所示:
<startEvent id="timerStartEvent" name="TimerStartEventForReportProcess">
<!-- timerEventDefinition描述一个定时事件 -->
<timerEventDefinition>
<!-- timeCycle使用ISO 8601标志定义了一个循环定时器,表示从2012年2月1日起每一个月启动一次流程 -->
<timeCycle>R1/2012-02-01T00:00/PM1M</timeCycle>
</timerEventDefinition>
</startEvent>
定时启动事件的定时属性:
属性名称 | 属性说明 | 示例 |
---|---|---|
timeDate | 一次性定时启动,具体到一个日期,用ISO 8601格式设定 | <timeDate>2012-12-12T00:00:00</timeDate> 在这个指定的日期启动一次流程 |
timeDuration | 设置多长时间之后启动流程 | timeDuration>PT10M</timeDuration> 部署流程或者输出流的上一个任务完成10分钟之后启动流程 |
timeCycle | 周期性启动任务,用来设定循环的时间间隔,表示多长事件执行一次循环。同样还是ISO 8601表示 | <timeCycle>R3/PT10H</timeCycle> 循环3次,每次间隔10小时。在timeDuration的基础上添加了循环次数 |
1.1.3、异常启动事件
异常启动事件可以触发一个异常子流程,但是不能通过API(runtimeService.startProcessInstance()方法)
方式启动,它总是在另外一个流程抛出异常结束事件的时候被触发。
异常启动事件是“捕获型”,而异常结束事件是“抛出型”,和Java中的异常处理机制类似。异常结束事件对应throw,而异常启动事件是catch,并且执行catch后获取不同类型的异常。在BPMN2.0规范中catch需要两者的错误信息匹配才能决定是否触发异常启动事件。
异常启动事件的图形化表示是在空启动事件基础上嵌套了一个雷电符号,如图4-4所示。
相对于空启动事件来说异常启动事件要复杂一些,必须嵌套在一个事件子流程(EventSubProcess)中,如图4-5中的虚线部分所示:
<!-- 定义一个事件子流程,表示的就是图4-5中虚线的部分 -->
<subProcess id="eventsubprocess1" name="EventSubProcess" triggeredByEvent="true">
<startEvent id="errorstartevent1" name="ErrorStart">
<!-- 被事件子流程包裹的异常事件,并且指定异常代码为AIA001 -->
<errorEventDefinition errorRef="AIA001"></errorEventDefinition>
</startEvent>
<!-- 定义了一个scripTask当捕获到异常代码为AIA001时触发异常启动事件,输出catch an error -->
<scriptTask id="scripttask1" name="ScriptTask" scriptFormat="groovy">
<script><![CDATA[out:println "catch an error";]]></script>
</scriptTask>
<sequenceFlow id="flow3" name="" sourceRef="errorstartevent1" targetRef="scripttask1"></sequenceFlow>
<endEvent id="endevent3" name="End"></endEvent>
<sequenceFlow id="flow4" name="" sourceRef="scripttask1" targetRef="endevent3"></sequenceFlow>
</subProcess>
1.1.4、消息启动事件
消息启动事件可以通过一个消息名称触发,从而启动一个流程实例;还可以结合消息抛出事件一起使用,由流程自动根据消息的名称启动对应的流程。借助这个功能在实际应用中可以为不同的业务处理结果启动不同的流程。
消息启动事件的图形化表示是在空启动事件的基础上嵌入一个空心的信封图标,如图4-6所示:
对应的XML描述如下所示:
<process>
<startEvent id="messageStart">
<messageEventDefinition messageRef="reSendFile"/>
</startEvent>
</process>
<message id="reSendFile" name="重新发送文件"/>
可以通过Activiti提供的API触发消息启动事件,例如runtimeService.startProcessInstanceByMessage("重新发送文件")
可以启动一个包含消息名称为“重新发送文件”的流程,当然还可以使用runtimeService.startProcessInstanceByKey(...)
和runtimeService.startProcessInstanceById(..)
方法那样启动一个以消息启动事件为入口的流程。
1.2、结束事件
有始有终,结束事件和启动事件通常成对出现在流程定义中,当然在定义流程时也允许没有结束事件(但是开始事件不可缺少)。结束事件在BPMN2.0规范中用一个圆形表示,和启动事件类似但有一点不同是线条加粗了,如图4-7所示。结束事件可以细分为以下几种类型:
- 空结束事件
- 异常结束事件
- 取消结束事件
结束事件表示流程(包含子流程)的结束,和启动事件的触发型不同的是结束事件总是抛出型的,也就是当流程执行到结束事件时会抛出一个执行结果。
1.2.1、空结束事件
结束事件是抛出型的,空结束事件不处理抛出结果,也可以理解为抛出的是一个“空”,所以没有什么需要流程引擎处理的。对于空结束事件,正常结束后流程引擎就不会再执行其他的操作了,因为一切都已结束了,而且没有后续处理(结束事件不能再有输出流)。空结束事件的图形表示如图4-7所示。
空结束事件和空启动事件的XML描述一样简单,如代码所示:
<endEvent id="end" name="MyEndEvent"></endEvent>
1.2.2、异常结束事件
异常结束事件相对于空结束事件来说就好理解一些了,空结束事件的抛出结果是“空”,而异常结束事件是有抛出结果的。它定义了需要抛出的错误代码,如果找到了异常开始事件定义的异常代码,则会触发异常开始事件,否则按照空结束事件规则处理。有一点需要注意,异常结束事件的错误代码不能为空。
异常结束事件的图形表示是在空结束事件的基础上添加了一个雷
电符号,如图4-8所示。
异常结束事件的XML描述是在空结束事件中嵌套了一个异常事
件errorEventDefinition,如下所示:
<endEvent id="endevent1" name="ErrorEnd">
<!--
表示定义一个异常事件,当遇到异常结束事件时,引擎查询定义为errorRef属性值的异常启动事件并触发。
errorRef属性用来描述错误编号。
-->
<errorEventDefinition errorRef="AIA-001"></errorEventDefinition>
</endEvent>
1.2.3、终止结束事件
终止结束事件是结束事件中的一个比较特别的类别,它可以终止一个流程实例的执行,例如在付费流程中因为某些原因导致流程不能继续运行,最终的结果是取消本次付费,所以需要提前结束流程实例的执行,此时可以把某个输出流指向到终止结束事件。空启动事件结束的仅仅是一条输出流,而终止结束事件结束的是整个流程实例。
终止结束事件的图形表示是在空结束事件的基础上添加了一个实心的圆点,如图4-9所示。
<endEvent id="terminateendevent1" name="TerminateEndEvent">
<terminateEventDefinition></terminateEventDefinition>
</endEvent>
1.2.4、取消结束事件
取消结束事件可以取消一个事务子流程的执行,同时也只能在子流程中使用。当子流程执行过程中出现异常需要取消时,可以设置一个取消结束事件,当输出流指向到取消结束事件时流程将会中断执行。取
消结束事件还可以和取消边界事件配合使用针对取消操作做后续处理。
取消结束事件的图形表示是在空结束事件基础上嵌入一个“X”图形,如图4-10所示:
对应的XML描述如下所示:
<endEvent id="myCancelEndEvent">
<cancelEventDefinition/>
</endEvent>
2、顺序流
顺序流是两个模型之间的连接者,可以把顺序流比作人体的动脉,每一条连接到不同的器官,在BPMN2.0规范中每个输出流连接到不同的活动、事件。如果一个元素在流程执行期间被访问,流程会沿着该元素所有输出顺序流继续执行。这意味着BPMN2.0默认行为是并行的:多个输出顺序流会创建多条独立、并行的执行路径。
顺序流可以细分为两种:
- 标准顺序流
- 条件顺序流
2.1、标准顺序流
标准顺序流可以用来连接两个或多个模型建立关系,在BPMN2.0规范中如图4-11所示:
对应的XML描述如下所示:
<sequenceFlow sourceRef="StartEvent" targetRef="endevent1" id="flow"></sequenceFlow>
sequenceFlow表示一个顺序流,用sourceRef属性指定顺序流的源,用targetRef属性指定顺序流的目的模型。
2.2、条件顺序流
条件顺序流是在标准顺序流上添加了条件表达式,只有满足条件才能通过顺序流到达目标活动。BPMN2.0规范定义的条件顺序流图形如图4-12所示。
在Activiti Designer中设置条件顺序流没有图4-l2所示的图形,取而代之的是标准顺序流,在设计的时候不设置condition的就是标准顺序流,设置了condition的就变成了条件顺序流。
条件顺序流的XML描述是在顺序流中添加了条件表达式标签conditionExpression,并且在conditionExpression中设置UEL表达式用于计算逻辑值,如下所示:
<sequenceFlow sourceRef="StartEvent" targetRef="endevent1" id="flow2">
<!-- 定义一个条件表达式,并且使用xsi:type指定表达式的类型为tFormalExpression -->
<conditionExpression xsi:type="tFormalExpression">
<!-- 使用CDATA方式描述,在运行时交由引擎根据变量pass的值和true进行比较 -->
<![CDATA[${pass == true}]]>
</conditionExpression>
</sequenceFlow>
3、任务
任务是一个流程中最重要的组成部分,根据业务需求的不同也分为很多种类型:
- 用户任务
- 脚本任务
- WebService任务
- 业务规则任务
- 邮件任务
- Mule任务
- Camel任务
- 手动任务
- Java service任务
- Shell任务
所有的任务的图形化表示都是以一个矩形为基础,在左上角添加具体类型的图标。大多数任务都有单独的XML定义,用来描述一种特定任务类型,也有部分任务是基于
serviceTask扩展的。
3.1、用户任务
顾名思义,用户任务需要人来参与,因为它必须被人为触发(完成任务动作)。用户任务可以定义任务的名称(例如“领导审批”)、优先级、到期日和任务处理人(可以是人、组或者两者的组合)。图4-13展示了用户任务的图形:
用户任务使用userTask表示,下面代码中定义了一个基本的用领导审批户任务,把任务分配给ID为henryhan的用户办理。
<!-- 定义一个名为【领导审批】的用户任务 -->
<userTask id="leaderAudit" name="领导审批">
<!-- 表示把这个任务分配给一个人 -->
<humanPerformer>
<resourceAssignmentExpression>
<!-- 指定了用户任务分配给用户henryhan办理 -->
<expression>henryhan</expression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
用户任务除了可以分配给一个用户之外还可以分配给组或两者混合:
<userTask id="laederAudit2" name="领导审批2">
<!-- potenialOwner标签用来描述一个潜在的用户、组集合 -->
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>
<!--
把这个任务同时分配给henryhan、组leader和zumanager
如果不添加user或group关键字,那么默认为组(group)
-->
user(henryhan),group(leader),manager
</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
此外Activiti在BPMN2.0的基础上进行扩展,可以简化设置用户、组的方式,而且支持动态(运行时)获取用户、组分配给用户任务;还可以为用户任务设置创建、分配、完成监听。
属性名称 | 属性说明 | 示例 |
---|---|---|
activiti:assignee | 用来指定用户任务的处理人,代替了humanPerformer的功能 | <userTask id="leaderAudit" name="领导审批" activiti:assignee="henryhan"/> |
activiti:cadidateUsers | 用来指定用户任务的候选人,多个用逗号分开,代替了potentialOwner的功能 | <userTask id="leaderAudit" name="领导审批" activiti:cadidateUsers="henryhan"/> |
activiti:cadidateGroups | 用来指定候选组,多个用逗号分开,代替了potentialOwner的功能 | <userTask id="leaderAudit" name="领导审批" activiti:cadidateGroups="leader,manager"/> |
activiti:dueDate | 设置用户任务到期日,通常用变量代替而不是指定一个具体的日期,因为这样毫无意义 | <userTask id="leaderAudit" name="领导审批" activiti:dueDate="${overDate}"/> |
activiti:priority | 用户任务的优先级,取值区间[0,100] | <userTask id="leaderAudit" name="领导审批" activiti:priority="${priority}"/> |
使用Activiti扩展属性设置用户任务的办理人和候选人:
<userTask id="leaderAudit3" name="领导审批3"
activiti:candidateUsers="henryhan"
activiti:candidateGroups="group1, group2">
</userTask>
除了上表中的属性之外,Activiti还允许在用户任务上添加任务监听,监听的选项有create(创建任务)
、assignment(分配任务)
、complete(完成任务)
,如下所示:
<userTask id="leaderAudit4" name="领导审批4">
<extensionElements>
<activiti:taskListener event="assignment" class="pers.zhang.xx"/>
<activiti:taskListener event="create" class="pers.zhang.xx"/>
<activiti:taskListener event="complete" class="pers.zhang.xx"/>
</extensionElements>
</userTask>
除了可以通过class属性指定一个Java的class文件(jar包中的全路径)之外,还可以定义expression和delegateExpression以表达式方式处理监听。
3.2、脚本任务
脚本任务可以运行引擎依赖的语言之外的脚本语言,例如Activiti支持的Groovy、Javascript、Juel。在BPMN2.0规范中脚本任务的图形表示如图4-l4所示:
值得注意的是,脚本任务的代码需要符合JSR-2239(Java平台脚本,scripting for the java plateform)规范。
<!-- 定义两个Groovy脚本任务 -->
<scriptTask id="initvars" name="初始化变量" scriptFormat="groovy">
<script>
<![CDATA[
def name = "HenryYan";
execution.setVariable('name', name);
]]>
</script>
</scriptTask>
<scriptTask id="printvars" name="输出变量" scriptFormat="groovy">
<script>
<![CDATA[out:pritln name;]]>
</script>
</scriptTask>
Activiti除了默认支持的三种脚本语言Groovy、JavaScript、Juel,还允许使用其他的脚本语言,前提是把依赖的jar包设置到classpath中。
属性名称 | 属性说明 | 示例 |
---|---|---|
scriptFormat | 用来指定符合JSR-223规范的脚本语言的类型 | <scriptTask id="scripttask1" name="初始化变量" scriptFormat="groovy"/> |
activiti:resultvariable | Activiti在原BPMN2.0规范中的脚本任务基础上进行了扩展,可以把脚本处理的结果保存到一个变量中 | <scriptTask id="scripttask1" name="初始化变量" scriptFormat="juel" activiti:resultVariable="name"/> 注意activiti:resultVariable指定的变量名称需要在脚本中预先定义才能使用 |
3.3、Java Service任务
首先声明Java Service不属于BPMN2.0规范,而是Activiit扩展的专门应用于Java语言的serviceTask。其图形表示如图4-15所示:
Java Service任务允许定义一个实现了指定接口的Java类,
或者执行一个表达式;还可以像脚本任务一样把结果保存到一个变量中。
Java Service任务对应的XML如下所示:
<!-- 定义一个Java Service任务 class指定一个Java类,这个类需要实现JavaDelegate、ActivityBehavior中的一个接口 -->
<serviceTask id="myServiceTask" name="JavaServiceTask"
activiti:class="pers.zhang.XXX"></serviceTask>
在指定一个Java类的同时还可以配置执行Service时传人的变量,这样在执行Java类的时候可以读取预先设置的变量值。
除了使用activiti:class指定一个Java类之外,还可以使用其他的几种方式定义Java Service任务需要执行的Java对象,下表列举了Activiti扩展的Java Service任务的属性。
属性名称 | 属性说明 | 示例 |
---|---|---|
activiti:class | 实现了接口JavaDelegate或ActivityBehavior的Java类 | <serviceTask id="serviceTask1" activiti:class="pers.zhang.JavaServiceDelegate" /> |
activiti:expression | 可以使用UEL定义需要执行的任务内容,例如计算公式、调用Bean对象的方法;并且在执行任务的时候可以使用流程变量作为参数。Bean实例除了可以使用new创建之外,还可以使用Spring代理 | <serviceTask id="serviceTask1" activiti:expression="#{leaveService.back()}" /> 其中leaveService作为一个流程变量存在,和activiti:class一样需要实现JavaDelegate接口或ActivityBehavior接口,另外还实现了java.io.Serializable接口以便引擎可以序列化变量 |
activiti:delegateExpression | 功能和activiti:class类似,而且同样需要实现JavaDelegate或ActivityBehavior中一个接口,只不过这里不是指定一个具体的实现类,而是在运行时动态设置。和activiti:expression类似,Bean可以使用new创建也可以用spring代理 | <serviceTask id="serviceTask1" activiti:expression="${leaveBackDelegate}" /> 执行到此任务时引擎会从变量中查询名称为leaveBackDelegate的Bean对象,然后调用实现接口的方法 |
activiti:resultVariable | 此属性仅适用于activiti:expression类型的Java Service,可以把一个表达式的执行结果保存到activiti:resultVariable指定的变量名称中 | <serviceTask id="serviceTask1" activiti:expression="#{leaveService.back()}" activiti:resultVariable="backDate" /> 把leaveService.back()执行结果保存到变量backDate中 |
3.4、Web Service任务
通过Web Service任务可以调用外部的Web Service资源,
完成调用只需要一些必须的配置就可以,并且支持标准的Web Service和REST风格的Service。Web Service任务的图形表示如图4-16所示:
Web Service任务对应的XML描述如下所示:
<!-- ##WebService 设置Task为WebService类型 -->
<serviceTask id="webServiceTask" implementation="##WebService">
<!-- ioSpecification定义输入、输出参数 -->
<ioSpecification>
<dataInput itemSubjectRef="tns:prettyPrintCountRequestItem" id="dataInputOfServiceTask"/>
<dataOutput itemSubjectRef="tns:prettyPrintCountResponseItem" id="dataOutputOfServiceTask"/>
<inputSet>
<dataInputRefs>dataInputOfServiceTask</dataInputRefs>
</inputSet>
<outputSet>
<dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>
</outputSet>
</ioSpecification>
<!-- dataInputAssociation定义数据输入关系 -->
<dataInputAssociation>
<sourceRef>PrefixVariable</sourceRef>
<targetRef>prefix</targetRef>
</dataInputAssociation>
<dataInputAssociation>
<sourceRef>SuffixVariable</sourceRef>
<targetRef>suffix</targetRef>
</dataInputAssociation>
<!-- dataOutputAssociation定义数据输出关系 -->
<dataOutputAssociation>
<sourceRef>prettyPrint</sourceRef>
<targetRef>OutputVariable</targetRef>
</dataOutputAssociation>
</serviceTask>
3.5、业务规则任务
在企业应用中一般都会使用可维护的规则库来管理复杂多变的业务规则,可以把业务逻辑和规则分开维护,一旦规则有变动,只需要更改预设规则即可。业务规则任务可以根据流程变量的值处理预设的业务规则。
Activiti对业务规则提供了很好的支持,目前支持比较流行的JBoss规则引擎——Drools。只需把含有业务规则任务的流程文件和规则引擎文件“.drl”一同打包部署到系统中,同时把Drools的jar包添加到classpath即可实现Activiti驱动规则引擎。业务规则任务的图形表示如图4-17所示:
业务规则任务的XML描述如下所示:
<businessRuleTask id="businessruletask1" name="Bussiness rule task"
activiti:rules="rule1, rule2"
activiti:ruleVariablesInput="${message}"
activiti:exclude="false"
activiti:resultVariableName="rulesOutput">
</businessRuleTask>
在介绍Activiti的扩展属性之前,需要先明白规则引擎的作用,简单来说就是把业务数据交由规则引擎处理,规则引擎根据不同业务规则(各种条件的判断)计算得出最终结果,最后把结果返回给调用者,在BPMN2.0中的调用者为流程引擎。
Activiti在原有的业务规则规范上扩展了一些属性以便与Drools整合,下表列举了Activiti扩展的业务规则任务的属性。
属性名称 | 属性说明 | 示例 |
---|---|---|
activiti:rules | 在规则文件.dl中定义的规则名称,多个规则用逗号分隔。要执行规则文件中的全部规则,将该属性设置为空即可 | <businessRuleTask id="businessruletask1" activiti:rules="rulw1, rule2" /> |
activiti:ruleVariablesInput | 业务规则执行需要的数据源,使用${foo Var}方式定义,多个规则用逗号分隔 | <businessRuleTask id="businessruletask1" activiti:ruleVariablesInput="${message}" /> |
activiti:resultVariableName | 规则执行结果变量,变量的值为ruleVariablesInput定义的变量集合(ArrayList) | <businessRuleTask id="businessruletask1" activiti:resultVariableName="rulesOutputVaraibleList" /> |
activiti:execlude | 用来设置是否排除某些规则(rule),如果值为false,不排除按照activiti:rules规则执行;如果设置为true,则忽略activiti:rules指定的规则。如果设置为false的同时activiti:rules值为空,则不执行任何规则 | <businessRuleTask id="businessruletask1" activiti:rules="rule2" activiti:execlude="true" /> 此例中忽略规则rule2,仅执行rule1 |
3.6、邮件任务
邮件任务可以通过Activiti发送邮件,其中的邮件信息通过变量方式传递。邮件任务不属于BPMN2.0规范而是和Java Service任务类似,由Activiti扩展而来专门用于处理邮件任务,其图形表示如图4-18所示:
邮件任务是在serviceTask的基础上由Activiti添加了扩展属activiti:type="mail"
实现的。邮件任务的XML描述如下所示:
<serviceTask id="mailtask1" name="Mail Task" activiti:type="mail">
<extensionElements>
<activiti:field name="to" expression="henryyan@gmail.com"></activiti:field>
<activiti:field name="from" expression="aaaa@gmail.com"></activiti:field>
<activiti:field name="subject" expression="hello henryyan"></activiti:field>
<activiti:field name="charset" expression="UTF-8"></activiti:field>
<activiti:field name="html">
<activiti:expression>
<![CDATA[你好,我是${userName}。]]>
</activiti:expression>
</activiti:field>
</extensionElements>
</serviceTask>
发送邮件需要配置邮件服务器信息到流程引擎中,可以在activiti.cfg.xml定义的引擎属性中设置。下表列举了配置邮件服务器的属性。
属性名称 | 是否必须 | 描述 |
---|---|---|
mailServerHost | 否 | 邮件服务器的主机名(例如smtp.gmail.com,默认为localhost |
mailServerPort | 是,如果不是默认的端口 | SMTP通信端口,默认为25;如果使用SSL,则为465 |
mailServerDefaultFrom | 否 | 发件人email,如果不设置,默认为acitiviti@activiti.org |
mailServerUsername | 否,根据服务器是否需要认证设置 | 邮件服务认证账号,默认为空 |
mailServerPassword | 否,根据服务器是否需要认证设置 | 邮件服务认证密码,默认为空 |
XML中的邮件任务配置统一使用activiti:field元素来配置,name不同表示不同的参数,均支持变量方式指定值。下表列举了邮件任务的属性。
属性名称 | 属性说明 | 示例 |
---|---|---|
to | 必填。收件人,多个收件人由逗号分割的列表来定义 | <activiti:field name="to" expression="${to}" /> |
from | 邮件发送人地址。如果不设置,使用引擎的mailServerDefaultFrom属性指定的发件人 | <activiti:field name="from" expression="${from}" /> |
subject | 发件人email,如果不设置,默认为activiti@activiti.org | <activiti:field name="subject" expression="${subject}" /> |
cc | 抄送人列表 | <activiti:field name="cc" expression="${cc}" /> |
bcc | 密送人列表 | <activiti:field name="bcc" expression="${bcc}" /> |
charset | 邮件内容字符集,建议使用UTF-8防止中文乱码 | <activiti:field name="charset" expression="${charset}" /> |
text | 纯文本格式的邮件内容 | <activiti:field name="text" expression="${text}" /> |
html | html格式的邮件内容 | <activiti:field name="html"><activiti:expression><![CDATA[你好,我是${userName}。]]></activiti:expression></activiti:field> |
3.7、Camel任务
Camel是由Apache开源组织开发的基于Apache2协议的企业系统整合框架,是EIP(Enterprise Integration Patterns,企业集成模式)的实现,是用来解决消息路由的框架。类
似的产品还有Mule、Spring Intergretion等。
Camel任务是使用Java语言编写的,所以不包含在BPMN2.0规范中,而是由Activiti在serviceTask的基础上扩展的一个任务模型。Activiti对其提供了很好的集成支持。可以通过activiti:delegateExpression属性指定Camel的上下文,在运行时由引擎调用Camel路由处理任务。Camel任务的图形表示如图4-19所示:
Camel任务和邮件任务的XML类似,都是在serviceTask的基础上由Activiti扩展而来,下面列出了Camel的XML描述:
<!--
定义了一个Camel任务,通过属性activiti::delegateExpression指定一个Camel的上下文对象,
${camel}表示由Activiti调用名称为camel的Bean对象处理Camel任务,它可以定义在Activiti配
置文件中或由Spring代理。
-->
<serviceTask id="camelTask1" name="Camel Task" activiti:delegateExpression="${camel}"/>
3.8、Mule任务
Mule任务台是由MuleSoft开发的一款轻量级开源ESB(Enterprise System Bus,.企业系统总线)产品,和Camel任务功能类似,但是它们是基于不同的标准实现的,Camel任务是基于传统EIP实现的。在使用方式上Camel任务使用链式编程配置路由,而Mule使用非侵入(或者很少侵入)方式,通过可视化的DE工具使用流程图模式配置路由。
Mule任务和Camel任务一样,也不是BPMN2.0规范的一部分,Activiti针对Mule任务进行了整合,可以直接在流程中通过添加Task的方式调用Mule处理业务逻辑。
Activiti对Camel的扩展使用的是serviceTask,而对于Mule任务使用另外一种发送任务——sendTask
Mule任务的图形化表示使用一个黑色填充的信封表示,如图4-20所示:
下面列出了Mule任务的XML描述:
<!-- activiti:type="mule"指定任务类型 -->
<sendTask id="muleTask" activiti:type="mule">
<!-- 配置一系列调用Mule的参数 -->
<extensionElements>
<!-- 指定协议类型为vm,类似Java虚拟机,还可以指定其他的协议调用Mule,例如JMS -->
<activiti:field name="endpointUrl">
<activiti:string>vm:</activiti:string>
</activiti:field>
<!-- language指定了调用payloadExpression的语言,此处指定为JUEL。在实际使用时可以通过Activiti的任务变量动态设置。 -->
<activiti:field name="language">
<activiti:string>juel</activiti:string>
</activiti:field>
<!--payloadExpression用来指定Mule消息的payload名称。-->
<activiti:field name="payloadExpression">
<activiti:string>"hi"</activiti:string>
</activiti:field>
<!--result Variable用来保存Mule任务的执行结果,可以通过Activiti API获取 -->
<activiti:field name="resultVariable">
<activiti:string>resultVar</activiti:string>
</activiti:field>
</extensionElements>
</sendTask>
3.9、手动任务
手动任务是比较特殊的任务之一,它不做任何事情,仅用来定义BPM不能完成的任务,需要人工参与建模,流程引擎无需关心如何处理它,因为根本不需要处理,所以
Activiti把手动任务当做一个空任务来处理,当到达此任务时由引擎自动完成并转向到下一个任务。
手动任务的图形用一个手的图标表示,如图4-21示:
手动任务的XML描述如下:
<manualTask id="manualTask" name="Manual Task"/>
3.10、接收任务
接收任务是一个功能简单且单一的任务,在任务创建之后开始等待消息的到来,直到被触发才会完成任务。Activiti实际上把接收任务作为一个Java类型的接受任务,仅能通过RuntimeService
接口的signal()
方法发送信号(signal)触发接收任务。原理类似线程的等待和恢复执行,只不过线程是在内存中操作,而接受任务的状态保存在数据库中,在调用Activiti的API触发流程的接收任务后,引擎把当前流程由等待状态恢复为可执行状态。
接受任务的图形使用一个信封图标表示,如图4-22所示:
接受任务的XML描述如下:
<receiveTask id="reveiveTask" name="Receive Task"/>
3.11、Shell任务
Shell任务允许在流程运行过程中执行本地操作系统中的脚本、命令,是Activiti基于serviceTask扩展的一种任务(activiti:type=“shell’”)。在BPMN规范中没有对应的图形表示,Activiti添加了Shell任务的图形表示,如图4-23所示:
Shell任务的XML描述如下:
<serviceTask id="shellEcho" activiti:type="shell">
<extensionElements>
<activiti:field name="command" stringValue="echo"/>
<activiti:field name="arg1" stringValue="hello"/>
<activiti:field name="wait" stringValue="true"/>
<activiti:field name="outputVariable" stringValue="resultVar"/>
</extensionElements>
</serviceTask>
配置Shell任务的参数和邮件任务类似,使用activiti:field扩展元素定义。下表列举了xml中Shell任务的属性及其含义。
属性名称 | 是否必选 | 属性类型 | 属性说明 | 默认值 |
---|---|---|---|---|
command | 是 | String | 执行的脚本、命令 | |
arg0-5 | 否 | String | 参数,限制6个,从0-5;在执行的时候用空格分割多个参数 | |
wait | 否 | true/false | 是否等待脚本执行完成 | true |
redirectError | 否 | true/false | 合并错误输出至标准输出 | false |
cleanEnv | 否 | true/false | 是否继承当前的环境变量 | false |
outputVariable | 否 | String | 脚本、命令执行结果保存变量 | 空,不记录 |
errorCodeVariable | 否 | String | 脚本、命令执行失败后反馈的错误编码保存变量 | 空,为绑定错误 |
directory | 否 | String | 在哪个目录执行脚本、命令 | 当前项目目录 |
3.12、多实例
多实例允许业务流程中某一个任务甚至子流程可以重复执行多次,在实际应用中一个申请由多人审批是多实例的典型应用场景。多个实例可以选择顺序执行,还可以选择并行执行多实例任务或子流程。
多实例图形化描述是在原任务的基础上添加了3个垂直线(顺序执行)和3个平行线(并行执行)。图4-24和图4-25分别展示了顺序执行、并行执行的不同图形化描述。
多实例支持的任务类型如下:
- 用户任务
- 脚本任务
- Java Service任务
- Web Service任务
- 业务规则任务
- 邮件任务
- 手动任务
- 接收任务
- 子流程(嵌入式)
- 子流程(调用活动)
在BPMN2.0规范中规定了多实例的几个属性变量,可以通过execution.getVariable()
获取变量。
- nrOfInstances:实例的总数。
- nrOfActiveInstances:当前活动的(未完成的)实例数量。对于按照顺序执行的多实例,该值总是为1。
- nrOfCompletedInstances:已经完成的实例数量。
- loopCounter:多实例运行过程中,for-each循环中当前的索引值。
<userTask id="aaa" name="领导审批">
<!-- 定义了一个多实例活动,isSequential为true按照顺序执行,false时并行执行 -->
<multiInstanceLoopCharacteristics isSequential="true">
<!--
指定多实例的循环次数,在并行执行的时候引擎会一次创建loopCardinality元素值数量的实例,
在顺序执行时只有当一个实例完成之后才会创建下一个实例
-->
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
决定启动多少个流程实例可以从两个角度设置:固定数量、任务参与人列表。下表列举了多实例的配置元素及属性:
- loopCardinality:实例的数量。可以用常量设置,也可以使用表达式计算设置
<userTask id="aaa" name="领导审批">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<!-- 此示例约定一次创建3个任务,也可以使用UEL表达式计算动态设置 -->
<loopCardinality>
${allTaskCounter - completedTaskCounter}
</loopCardinality>
- loopDataInputRef:任务参与人列表。以参与人集合的数量决定创建多少个实例。和loopCardinality是互斥的关系
<userTask id="aaa" name="领导审批">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>
assigneeList
</loopDataInputRef>
<inputDataItem name="assignee" />
</multiInstanceLoopCharacteristics>
</userTask>
<!-- assigneeList作为一个运行时变量存在,实际上是包含了一系列用户Id的数组 -->
- inputDataItem:需要配合loopDataInputRef使
用,用来指定在loop-DataInputRef中定义的办理人集合在循环过程中单个变量的名称,例如指定userTask的activiti:assignee属性表示用户任务的办理人由变量assignee的值决定
<userTask activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assigneeList</loopDataInputRef>
<inputDataItem name="assignee"/>
</multiInstanceLoopCharacteristics>
</userTask>
- completionCondition:多实例循环结束条件。当满足一定条件时退出多实例循环。例如在投票表决场景中当通过率达到70%时结束任务,剩余未办理的任务不再执行
<userTask id="bbb" name="领导审批">
<multiInstanceLoopCharacteristics isSequential="false">
<completionCondition>
${nrOfCompletedInstances / nrOfInstances >= 0.7}
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
- isSequential:是否按照顺序创建任务。值为true时表示按照顺序执行,只有在第一个任务处理完成之后才会继续创建第二个任务,以此类推;值为false时表示不按照顺序执行,而是一次创建多个任务(数量由其他的条件决定),这样多个任务可以同时办理,也就是并行处理
<userTask id="ccc" name="任务分发" activiti:assignee="henryyan">
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<!-- 此示例在运行时一次创建3个任务并分配给用户henryyan办理。当多个领导办理会签任务时需要设置为false -->
另外Activiti还基于BPMN2.0规范以扩展形式简化了配置,下面列举了Activiti扩展的多实例属性。
- activiti:collection:用来简化BPMN2.0规范中的loopDataInputRef元素:
<multiInstanceLoopCharacteristics
isSequential="false"
activiti:collection="assigneeList">
</multiInstanceLoopCharacteristics>
- activiti:elementVariable:用来简化BPMN2.0规范的inputDataItem元素:
<multiInstanceLoopCharacteristics
isSequential="false"
activiti:collection="assigneeList"
activiti:elementVariable="assignee">
</multiInstanceLoopCharacteristics>