Activiti工作流框架中任务流程元素详解!使用任务元素进行任务的调度和执行

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本篇文章对工作流Activiti框架中的流程元素的任务流程元素的基本概念以及使用方式进行了详尽的说明。包括用户任务,脚本任务,Java服务任务,WebSevice任务和业务规则任务。通过对工作流Activiti框架的各种类型的任务的使用方式的描述,可以在项目中集成使用工作流Activiti框架时进行更多的扩展任务操作。

任务

用户任务

描述
  • 用户任务用来设置必须由人员完成的工作
  • 当流程执行到用户任务,会创建一个新任务,并把这个新任务加入到分配人或群组的任务列表中
图形标记
  • 用户任务显示成一个普通任务(圆角矩形),左上角有一个小用户图标

在这里插入图片描述

XML内容
  • XML中的用户任务定义:id属性是必须的,name属性是可选的:
<userTask id="theTask" name="Important task" />
  • 用户任务可以设置描述,添加documentation元素可以定义描述:
<userTask id="theTask" name="Schedule meeting" >
  <documentation>
          Schedule an engineering meeting for next week with the new hire.
  </documentation>
  • 描述文本可以通过标准的java方法来获取:
task.getDescription()
持续时间
  • 任务可以用一个字段来描述任务的持续时间
  • 可以使用查询API来对持续时间进行搜索,根据在时间之前或之后进行搜索

    • Activiti提供了一个节点扩展,在任务定义中设置一个表达式,这样在任务创建时就可以设置初始持续时间
    • 表达式应该是:

      • java.util.Date
      • java.util.String(ISO8601格式),ISO8601持续时间(比如PT50M)
      • null
  • 在流程中使用上述格式输入日期,或在前一个服务任务中计算一个时间.这里使用了持续时间,持续时间会基于当前时间进行计算,再通过给定的时间段累加: 使用"PT30M"作为持续时间,任务就会从现在开始持续30分钟
<userTask id="theTask" name="Important task" activiti:dueDate="${dateVariable}"/>
  • 任务的持续时间也可以通过TaskService修改,或在TaskListener中通过传入的DelegateTask参数修改
用户分配
  • 用户任务可以直接分配给一个用户,通过humanPerformer元素定义
  • humanPerformer定义需要一个resourceAssignmentExpression来实际定义用户.目前只支持formalExpressions
<process ... >

  ...

  <userTask id='theTask' name='important task' >
    <humanPerformer>
      <resourceAssignmentExpression>
        <formalExpression>kermit</formalExpression>
      </resourceAssignmentExpression>
    </humanPerformer>
  </userTask>
  • 只有一个用户可以作为任务的执行者分配用户
  • 在activiti中,用户叫做执行者
  • 拥有执行者的用户不会出现在其他人的任务列表中,只能出现执行者的个人任务列表中
  • 直接分配给用户的任务可以通过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元素定义的任务可以通过TaskService获取:
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");

这会获取所有kermit为候选人的任务,表达式中包含user(kermit).这也会获得所有分配包含kermit这个成员的群组(比如,group(management),前提是kermit是这个组的成员,并且使用了activiti的账号组件).用户所在的群组是在运行阶段获取的, 它们可以通过IdentityService进行管理

  • 如果没有显式指定设置的是用户还是群组,引擎会默认当做群组处理
  • 下面的设置与使用group(accountancy)一样:
<formalExpression>accountancy</formalExpression>
Activiti对任务分配的扩展
  • 当分配不复杂时,用户和组的设置非常麻烦.为避免复杂性,可以使用用户任务的自定义扩展
  • assignee属性: 直接把用户任务分配给指定用户(和使用humanPerformer 效果完全一样)
<userTask id="theTask" name="my task" activiti:assignee="kermit" />
  • candidateUsers属性: 为任务设置候选人(和使用potentialOwner效果完全一样,不需要像使用potentialOwner通过user(kermit)声明,这个属性只能用于人员)
<userTask id="theTask" name="my task" activiti:candidateUsers="kermit, gonzo" />
  • candidateGroups属性: 为任务设置候选组(和使用potentialOwner效果完全一样,不需要像使用potentialOwner通过group(management)声明,这个属性只能用于群组)
<userTask id="theTask" name="my task" activiti:candidateGroups="management, accountancy" />
  • candidateUsers和candidateGroups可以同时设置在同一个用户任务中
  • Activiti中虽然有账号管理组件和IdentityService ,账号组件不会检测设置的用户是否存在. Activiti允许与其他已存的账户管理方案集成
  • 使用创建事件的任务监听器 来实现自定义的分配逻辑:
<userTask id="task1" name="My task" >
  <extensionElements>
    <activiti:taskListener event="create" class="org.activiti.MyAssignmentHandler" />
  </extensionElements>
</userTask>
  • DelegateTask会传递给TaskListener的实现,通过它可以设置执行人,候选人和候选组
public class MyAssignmentHandler implements TaskListener {

  public void notify(DelegateTask delegateTask) {
    // Execute custom identity lookups here

    // and then for example call following methods:
    delegateTask.setAssignee("kermit");
    delegateTask.addCandidateUser("fozzie");
    delegateTask.addCandidateGroup("management");
    ...
  }

}
  • 使用spring时,使用表达式把任务监听器设置为spring代理的bean,让这个监听器监听任务的创建事件
  • 示例:执行者会通过调用ldapService这个spring bean的findManagerOfEmployee方法获得.流程变量emp会作为参数传递给bean
<userTask id="task" name="My Task" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>
  • 可以用来设置候选人和候选组:
<userTask id="task" name="My Task" activiti:candidateUsers="${ldapService.findAllSales()}"/>
  • 方法返回类型只能为String(候选人)Collection < String >(候选组):
public class FakeLdapService {

  public String findManagerForEmployee(String employee) {
    return "Kermit The Frog";
  }

  public List<String> findAllSales() {
    return Arrays.asList("kermit", "gonzo", "fozzie");
  }

}

脚本任务

描述
  • 脚本任务是一个自动节点
  • 当流程到达脚本任务,会执行对应的脚本
图形标记
  • 脚本任务显示为标准BPMN 2.0任务(圆角矩形),左上角有一个脚本小图标

在这里插入图片描述

XML内容
  • 脚本任务定义需要指定scriptscriptFormat
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
  <script>
    sum = 0
    for ( i in inputArray ) {
      sum += i
    }
  </script>
</scriptTask>

scriptFormat的值必须兼容JSR-223(java平台的脚本语言).默认Javascript会包含在JDK中,不需要额外的依赖.如果要使用其他的脚本引擎,必须要是JSR-223引擎兼容的.还需要把对应的jar添加到classpath下, 并使用合适的名称:activiti单元测试经常使用groovy

  • groovy脚本引擎放在groovy-all.jar中,在2.0版本之前,脚本引擎是groovy jar的一部分.使用需要添加依赖:
<dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>2.x.x<version>
</dependency>
脚本变量
  • 到达脚本任务的流程可以访问的所有流程变量,都可以在脚本中使用
<script>
    sum = 0
    for ( i in inputArray ) {
      sum += i
    }
</script>
  • 也可以在脚本中设置流程变量,直接调用execution.setVariable("variableName", variableValue)

    • 默认,不会自动保存变量(activiti 5.12之前)
    • 可以在脚本中自动保存任何变量,只要把scriptTaskautoStoreVariables属性设置为true
    • 最佳实践是不要使用,而是显式调用execution.setVariable()
<scriptTask id="script" scriptFormat="JavaScript" activiti:autoStoreVariables="false">

参数默认为false: 如果没有为脚本任务定义设置参数,所有声明的变量将只存在于脚本执行的阶段

  • 在脚本中设置变量: 这些命名已经被占用,不能用作变量名- out, out:print, lang:import, context, elcontext.
<script>
    def scriptVar = "test123"
    execution.setVariable("myVar", scriptVar)
</script>
脚本结果
  • 脚本任务的返回值可以通过制定流程变量的名称,分配给已存在或者一个新流程变量,需要使用脚本任务定义的'activiti:resultVariable'属性
  • 任何已存在的流程变量都会被脚本执行的结果覆盖
  • 如果没有指定返回的变量名,脚本的返回值会被忽略
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="juel" activiti:resultVariable="myVar">
  <script>#{echo}</script>
</scriptTask>

脚本的结果-表达式 #{echo} 的值会在脚本完成后,设置到myVar变量中

Java服务任务

描述
  • Java服务任务用来调用外部Java类
图形标记
  • Java服务任务显示为圆角矩形,左上角有一个齿轮小图标

在这里插入图片描述

XML内容
  • 声明Java调用逻辑有四种方式:

    • 实现JavaDelegate或者ActivityBehavior
    • 执行解析代理对象的表达式
    • 调用一个方法表达式
    • 调用一个值表达式
  • 执行一个在流程执行中调用的类,需要在activiti:class属性中设置全类名:
<serviceTask id="javaService"
             name="My Java Service Task"
             activiti:class="org.activiti.MyJavaDelegate" />
  • 使用表达式调用一个对象,对象必须遵循一些规则,并使用activiti:delegateExpression属性进行创建:
<serviceTask id="serviceTask" activiti:delegateExpression="${delegateExpressionBean}" />

delegateExpressionBean是一个实现了JavaDelegate接口的bean,定义在实例的spring容器中
要执行指定的UEL方法表达式, 需要使用activiti:expression:

<serviceTask id="javaService"
             name="My Java Service Task"
             activiti:expression="#{printer.printMessage()}" />

方法printMessage()会调用名为printer对象的方法

  • 为表达式中的方法传递参数:
<serviceTask id="javaService"
             name="My Java Service Task"
             activiti:expression="#{printer.printMessage(execution, myVar)}" />

调用名为printer对象上的方法printMessage.第一个参数是DelegateExecution, 在表达式环境中默认名称为execution. 第二个参数传递的是当前流程的名为myVar的变量
要执行指定的UEL方法表达式, 需要使用activiti:expression:

<serviceTask id="javaService"
             name="My Java Service Task"
             activiti:expression="#{split.ready}" />

ready属性的getter方法:getReady() 会作用于名为split的bean上.这个对象会被解析为流程对象spring环境中的对象

实现
  • 要在流程执行中实现一个调用的类,这个类需要实现org.activiti.engine.delegate.JavaDelegate接口,并在execute方法中提供对应的业务逻辑.当流程执行到特定阶段,会指定方法中定义好的业务逻辑,并按照默认BPMN 2.0中的方式离开节点
  • 示例:

    • 创建一个java类的例子,对流程变量中字符串转换为大写
    • 这个类需要实现org.activiti.engine.delegate.JavaDelegate接口,要求实现execute(DelegateExecution) 方法,包含的业务逻辑会被引擎调用
    • 流程实例信息:流程变量和其他信息,可以通过DelegateExecution接口访问和操作
public class ToUppercase implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
    String var = (String) execution.getVariable("input");
    var = var.toUpperCase();
    execution.setVariable("input", var);
  }

}
  • serviceTask定义的class只会创建一个java类的实例

    • 所有流程实例都会共享相同的类实例,并调用execute(DelegateExecution)
    • 类不能使用任何成员变量,必须是线程安全的,必须能模拟在不同线程中执行.影响着属性注入的处理方式
  • 流程定义中引用的类(activiti:class)不会在部署时实例化

    • 只有当流程第一次执行到使用类的时候,类的实例才会被创建
    • 如果找不到类,会抛出一个ActivitiException
    • 这个原因是部署环境(更确切是的classpath)和真实环境往往是不同的:当使用ant或业务归档上传到Activiti Explorer来发布流程,classpath没有包含引用的类
  • 内部实现类也可以提供实现org.activiti.engine.impl.pvm.delegate.ActivityBehavior接口的类

    • 实现可以访问更强大的ActivityExecution,它可以影响流程的流向
    • 注意: 这应该尽量避免.只有在高级情况下并且确切知道要做什么的情况下,再使用ActivityBehavior接口
属性注入
  • 为代理类的属性注入数据. 支持如下类型的注入:

    • 固定的字符串
    • 表达式
  • 如果有效的话,数值会通过代理类的setter方法注入,遵循java bean的命名规范(比如fistName属性对应setFirstName(Xxx)方法)
  • 如果属性没有对应的setter方法,数值会直接注入到私有属性中

    • 一些环境的SecurityManager不允许修改私有属性,要把想注入的属性暴露出对应的setter方法来
    • 无论流程定义中的数据是什么类型,注入目标的属性类型都应该是 org.activiti.engine.delegate.Expression
  • 示例:

    • 把一个常量注入到属性中
    • 属性注入可以使用class属性
    • 在声明实际的属性注入之前,需要定义一个extensionElements的XML元素
<serviceTask id="javaService"
    name="Java service invocation"
    activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
    <extensionElements>
      <activiti:field name="text" stringValue="Hello World" />
  </extensionElements>
</serviceTask>

ToUpperCaseFieldInjected类有一个text属性,类型是org.activiti.engine.delegate.Expression. 调用text.getValue(execution) 时,会返回定义的字符串Hello World

  • 可以使用长文字(比如,内嵌的email),使用activiti:string子元素:
<serviceTask id="javaService"
    name="Java service invocation"
    activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
  <extensionElements>
    <activiti:field name="text">
        <activiti:string>
          Hello World
      </activiti:string>
    </activiti:field>
  </extensionElements>
</serviceTask>
  • 可以使用表达式,实现在运行期动态解析注入的值
  • 这些表达式可以使用流程变量spring定义的bean.
  • 服务任务中的java类实例会在所有流程实例中共享:

    • 为了动态注入属性的值,可以在org.activiti.engine.delegate.Expression中使用值和方法表达式
    • 会使用传递给execute方法的DelegateExecution参数进行解析
<serviceTask id="javaService" name="Java service invocation"
  activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected">

  <extensionElements>
    <activiti:field name="text1">
      <activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression>
    </activiti:field>
    <activiti:field name="text2">
       <activiti:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</activiti:expression>
    </activiti:field>
  </ extensionElements>
</ serviceTask>
  • 示例:

    • 注入表达式,并使用在当前传入的DelegateExecution解析:
public class ReverseStringsFieldInjected implements JavaDelegate {

  private Expression text1;
  private Expression text2;

  public void execute(DelegateExecution execution) {
    String value1 = (String) text1.getValue(execution);
    execution.setVariable("var1", new StringBuffer(value1).reverse().toString());

    String value2 = (String) text2.getValue(execution);
    execution.setVariable("var2", new StringBuffer(value2).reverse().toString());
  }
}
  • 可以把表达式设置成一个属性,而不是子元素:

    • 因为java类实例会被重用,注入只会发生一次,当服务任务调用第一次的时候发生注入
    • 当代码中的属性改变了,值也不会重新注入,把它们看作是不变的,不用修改它们
服务任务结果
  • 服务流程返回的结果(使用表达式的服务任务)可以分配给已经存在的或新的流程变量
  • 通过指定服务任务定义的activiti:resultVariable属性来实现

    • 指定的流程变量会被服务流程的返回结果覆盖
    • 如果没有指定返回变量名,就会忽略返回结果
<serviceTask id="aMethodExpressionServiceTask"
    activiti:expression="#{myService.doSomething()}"
    activiti:resultVariable="myVar" />

服务流程的返回值(在myService上调用doSomething() 方法的返回值,myService可能是流程变量,也可能是spring的bean),在服务执行完成之后,会设置到名为myVar的流程变量里

处理异常

执行自定义逻辑时,常常需要捕获对应的业务异常,在流程内部进行处理

  • 抛出BPMN Errors:

    • 在服务任务或脚本任务的代码里抛出BPMN error:

      • 要从JavaDelegate,脚本,表达式和代理表达式中抛出名为BpmnError的特殊ActivitiExeption
      • 引擎会捕获这个异常,把它转发到对应的错误处理中:边界错误事件或错误事件子流程
public class ThrowBpmnErrorDelegate implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
    try {
      executeBusinessLogic();
    } catch (BusinessException e) {
      throw new BpmnError("BusinessExceptionOccured");
    }
  }

}

构造参数是错误代码,会被用来决定哪个错误处理器会来响应这个错误
这个机制只用于业务失败,应该被流程定义中设置的边界错误事件或错误事件子流程处理. 技术上的错误应该使用其他异常类型,通常不会在流程里处理

  • 异常顺序流:

内部实现类在一些异常发生时,让流程进入其他路径

<serviceTask id="javaService"
  name="Java service invocation"
  activiti:class="org.activiti.ThrowsExceptionBehavior">
</serviceTask>

<sequenceFlow id="no-exception" sourceRef="javaService" targetRef="theEnd" />
<sequenceFlow id="exception" sourceRef="javaService" targetRef="fixException" />

这里的服务任务有两个外出顺序流:分别叫exceptionno-exception. 异常出现时会使用顺序流的ID来决定流向

public class ThrowsExceptionBehavior implements ActivityBehavior {

  public void execute(ActivityExecution execution) throws Exception {
    String var = (String) execution.getVariable("var");

    PvmTransition transition = null;
    try {
      executeLogic(var);
      transition = execution.getActivity().findOutgoingTransition("no-exception");
    } catch (Exception e) {
      transition = execution.getActivity().findOutgoingTransition("exception");
    }
    execution.take(transition);
  }

}
JavaDelegate使用Activiti服务
  • 需要在Java服务任务中使用Activiti服务的场景: 比如,通过RuntimeService启动流程实例,而callActivity不满足需求
  • org.activiti.engine.delegate.DelegateExecution允许通过 org.activiti.engine.EngineServices接口直接获得这些服务:
public class StartProcessInstanceTestDelegate implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
    RuntimeService runtimeService = execution.getEngineServices().getRuntimeService();
    runtimeService.startProcessInstanceByKey("myProcess");
  }

}
  • 所有activiti服务的API都可以通过这个接口获得
  • 使用这些API调用出现的所有数据改变,都是在当前事务中
  • 在例如spring和CDI这样的依赖注入环境也会起作用,无论是否启用了JTA数据源
  • 示例: 下面的代码功能与上面的代码一致,这是RuntimeService是通过依赖注入获得,而不是通过org.activiti.engine.EngineServices接口
@Component("startProcessInstanceDelegate")
public class StartProcessInstanceTestDelegateWithInjection {

    @Autowired
    private RuntimeService runtimeService;

    public void startProcess() {
      runtimeService.startProcessInstanceByKey("oneTaskProcess");
    }

}
  • 因为服务调用是在当前事务里,数据的产生或改变,在服务任务执行完之前,还没有提交到数据库.所以API对于数据库数据的操作,意味着未提交的操作在服务任务的API调用中都是不可见的

WebService任务

描述
  • WebService任务可以用来同步调用一个外部的WebService
图形标记
  • WebService任务与Java服务任务显示效果一样(圆角矩形,左上角有一个齿轮小图标)

在这里插入图片描述

XML内容
  • 要使用WebService需要导入操作和类型,可以使用import标签来指定WebService的WSDL
<import importType="http://schemas.xmlsoap.org/wsdl/"
        location="http://localhost:63081/counter?wsdl"
        namespace="http://webservice.activiti.org/" />

声明告诉activiti导入WSDL定义,但没有创建itemDefinitionmessage

  • 假设想调用一个名为prettyPrint的方法,必须创建为请求和响应信息对应的messageitemDefinition
<message id="prettyPrintCountRequestMessage" itemRef="tns:prettyPrintCountRequestItem" />
<message id="prettyPrintCountResponseMessage" itemRef="tns:prettyPrintCountResponseItem" />

<itemDefinition id="prettyPrintCountRequestItem" structureRef="counter:prettyPrintCount" />
<itemDefinition id="prettyPrintCountResponseItem" structureRef="counter:prettyPrintCountResponse" />
  • 在申请服务任务之前,必须定义实际引用WebService的BPMN接口和操作
  • 基本上,定义接口和必要的操作.对每个操作都会重用上面定义的信息作为输入和输出
  • 示例:

    • 定义了counter接口和prettyPrintCountOperation操作:
  <interface name="Counter Interface" implementationRef="counter:Counter">
        <operation id="prettyPrintCountOperation" name="prettyPrintCount Operation"
                        implementationRef="counter:prettyPrintCount">
                <inMessageRef>tns:prettyPrintCountRequestMessage</inMessageRef>
                <outMessageRef>tns:prettyPrintCountResponseMessage</outMessageRef>
        </operation>
</interface>

然后定义WebService任务,使用WebService实现,并引用WebService操作

<serviceTask id="webService"
        name="Web service invocation"
        implementation="##WebService"
        operationRef="tns:prettyPrintCountOperation">
WebService任务IO规范
  • 每个WebService任务可以定义任务的输入输出IO规范
<ioSpecification>
        <dataInput itemSubjectRef="tns:prettyPrintCountRequestItem" id="dataInputOfServiceTask" />
        <dataOutput itemSubjectRef="tns:prettyPrintCountResponseItem" id="dataOutputOfServiceTask" />
        <inputSet>
                <dataInputRefs>dataInputOfServiceTask</dataInputRefs>
        </inputSet>
        <outputSet>
                <dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>
        </outputSet>
</ioSpecification>
WebService任务数据输入关联
  • 指定数据输入关联有两种方式:

    • 使用表达式
    • 使用简化方式
  • 使用表达式指定数据输入关联: 需要定义来源和目的item,并指定每个item属性之间的对应关系:
<dataInputAssociation>
        <sourceRef>dataInputOfProcess</sourceRef>
        <targetRef>dataInputOfServiceTask</targetRef>
        <assignment>
                <from>${dataInputOfProcess.prefix}</from>
                <to>${dataInputOfServiceTask.prefix}</to>
        </assignment>
        <assignment>
                <from>${dataInputOfProcess.suffix}</from>
                <to>${dataInputOfServiceTask.suffix}</to>
        </assignment>
</dataInputAssociation>

分配item的前缀和后缀

  • 使用简化方式指定数据输入关联: sourceRef元素是activiti的变量名,targetRef元素是item定义的一个属性:
<dataInputAssociation>
        <sourceRef>PrefixVariable</sourceRef>
        <targetRef>prefix</targetRef>
</dataInputAssociation>
<dataInputAssociation>
        <sourceRef>SuffixVariable</sourceRef>
        <targetRef>suffix</targetRef>
</dataInputAssociation>

PrefixVariable变量的值分配给prefix属性,把SuffixVariable变量的值分配给suffix属性

WebService任务数据输出关联
  • 指定数据输出关联有两种方式:

    • 使用表达式
    • 使用简化方式
  • 使用表达式指定数据输出关联: 需要定义目的变量和来源表达式
<dataOutputAssociation>
        <targetRef>dataOutputOfProcess</targetRef>
        <transformation>${dataOutputOfServiceTask.prettyPrint}</transformation>
</dataOutputAssociation>

方法和数据输入关联完全一样

  • 使用简化方式指定数据输出关联: sourceRef元素是item定义的一个属性,targetRef元素是activiti的变量名
<dataOutputAssociation>
        <sourceRef>prettyPrint</sourceRef>
        <targetRef>OutputVariable</targetRef>
</dataOutputAssociation>

方法和数据输入关联完全一样

业务规则任务

描述
  • 业务规则任务用来同步执行一个或多个规则
  • Activiti使用drools规则引擎执行业务规则:

    • 包含业务规则的.drl文件必须和流程定义一起发布
    • 流程定义里包含了执行这些规则的业务规则任务
    • 流程使用的所有.drl文件都必须打包在流程BAR文件里
  • 如果想要自定义规则任务的实现: 想用不同方式使用drools,或者使用完全不同的规则引擎.你可以使用BusinessRuleTask上的class表达式属性
图形标记
  • 业务规则任务是一个圆角矩形,左上角使用一个表格小图标进行显示

在这里插入图片描述

XML内容
  • 要执行部署流程定义的BAR文件中的一个或多个业务规则,需要定义输入和输出变量:

    • 对于输入变量定义,可以使用逗号分隔的一些流程变量
    • 输出变量定义只包含一个变量名,会把执行业务规则后返回的对象保存到对应的流程变量中
  • 注意: 结果变量会包含一个对象列表,如果没有指定输出变量名称,默认会使用 org.activiti.engine.rules.OUTPUT
<process id="simpleBusinessRuleProcess">

  <startEvent id="theStart" />
  <sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" />

  <businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
      activiti:resultVariable="rulesOutput" />

  <sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" />

  <endEvent id="theEnd" />

</process>
  • 业务规则任务也可以配置成只执行部署的.drl文件中的一些规则.这时要设置逗号分隔的规则名,只会执行rule1和rule2:
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
      activiti:rules="rule1, rule2" />
  • 定义哪些规则不用执行:除了rule1和rule2以外,所有部署到流程定义同一个BAR文件中的规则都会执行:
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
      activiti:rules="rule1, rule2" exclude="true" />
  • 可以用一个选项修改BusinessRuleTask的实现:
<businessRuleTask id="businessRuleTask" activiti:class="${MyRuleServiceDelegate}" />

BusinessRuleTask的功能和ServiceTask一样,但是使用BusinessRuleTask的图标来表示 在这里要执行业务规则

邮件任务

  • Activiti强化了业务流程,支持自动邮件任务:

    • 可以发送邮件给一个或多个参与者,包括支持cc,bcc,HTML内容等等
    • 邮件任务不是BPMN 2.0规范定义的官方任务,Activiti中邮件任务是用专门的服务任务实现的
邮件服务器配置
  • Activiti引擎要通过支持SMTP功能的外部邮件服务器发送邮件
  • 为了实际发送邮件,引擎穾知道如何访问邮件服务器.下面的配置可以设置到activiti.cfg.xml配置文件中:
属性 是否必须 描述
mailServerHost 邮件服务器的主机名(比如:mail.mycorp.com).默认为localhost
mailServerPort
如果没有使用默认端口
邮件服务器上的SMTP传输端口.默认为25
mailServerDefaultFrom 如果用户没有指定发送邮件的邮件地址,默认设置的发送者的邮件地址。默认为activiti@activiti.org
mailServerUsername 如果服务器需要 一些邮件服务器需要认证才能发送邮件.默认不设置
mailServerPassword 如果服务器需要 一些邮件服务器需要认证才能发送邮件.默认不设置
mailServerUseSSL 如果服务器需要 一些邮件服务器需要ssl交互.默认为false
mailServerUseTLS 如果服务器需要 一些邮件服务器(比如gmail)需要支持TLS.默认为false
定义一个邮件任务
  • 邮件任务是一个专用的服务任务, 这个服务任务的type设置为mail
<serviceTask id="sendMail" activiti:type="mail">
  • 邮件任务是通过属性注入进行配置的.所有这些属性都可以使用EL表达式,可以在流程执行中解析. 下面的属性都可以设置:
属性 是否必须 描述
to 邮件的接受者.可以使用逗号分隔多个接受者
from 邮件发送者的地址.如果不提供,会使用默认配置的地址
subject 邮件的主题
cc 邮件抄送人.可以使用逗号分隔多个接收者
bcc 邮件暗送人.可以使用逗号分隔多个接收者
charset 可以修改邮件的字符集,对很多非英语语言是必须设置的
html 作为邮件内容的HTML
text 邮件的内容.,在需要使用原始文字(非富文本)的邮件时使用.可以与html一起使用,对于不支持富文本的邮件客户端.客户端会降级到仅显示文本的方式
htmlVar 使用对应的流程变量作为e-mail的内容.和html的不同之处是内容中包含的表达式会在mail任务发送之前被替换掉
textVar 使用对应的流程变量作为e-mail的纯文本内容.和text的不同之处是内容中包含的表达式会在mail任务发送之前被替换掉
ignoreException 处理邮件失败时,是否忽略异常,不抛出ActivitiException,默认为false
exceptionVariableName 当设置了ignoreException=true处理email时不抛出异常,可以指定一个变量名来存储失败信息
实例
  • 邮件任务的使用示例:
<serviceTask id="sendMail" activiti:type="mail">
  <extensionElements>
    <activiti:field name="from" stringValue="order-shipping@thecompany.com" />
    <activiti:field name="to" expression="${recipient}" />
    <activiti:field name="subject" expression="Your order ${orderId} has been shipped" />
    <activiti:field name="html">
      <activiti:expression>
        <![CDATA[
          <html>
            <body>
              Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},<br/><br/>

              As of ${now}, your order has been <b>processed and shipped</b>.<br/><br/>

              Kind regards,<br/>

              TheCompany.
            </body>
          </html>
        ]]>
      </activiti:expression>
    </activiti:field>
  </extensionElements>
</serviceTask>

在这里插入图片描述

Mule任务

  • Mule任务可以向Mule发送消息,用来强化Activiti的集成能力
  • Mule任务不是BPMN 2.0规范定义的官方任务,Activiti中Mule任务是用专门的服务任务实现的
定义Mule任务
  • Mule任务是一个专用的服务任务, 服务任务的type设置为mule
<serviceTask id="sendMule" activiti:type="mule">
  • Mule任务是通过属性注入进行配置的.属性使用EL表达式, 可以在流程执行中解析
属性 是否必须 描述
endpointUrl 需要调用的Mule终端
language 要使用解析荷载表达式(payloadExpression)属性的语言
payloadExpression 作为消息荷载的表达式
resultVariable 将要保存调用结果的变量名称
实例
  • Mule任务的使用示例:
 <extensionElements>
    <activiti:field name="endpointUrl">
      <activiti:string>vm://in</activiti:string>
    </activiti:field>
    <activiti:field name="language">
      <activiti:string>juel</activiti:string>
    </activiti:field>
    <activiti:field name="payloadExpression">
      <activiti:string>"hi"</activiti:string>
    </activiti:field>
    <activiti:field name="resultVariable">
      <activiti:string>theVariable</activiti:string>
    </activiti:field>
  </extensionElements>

Camel任务

  • Camel任务可以从Camel发送和接收消息,用来强化activiti的集成功能
  • Camel任务不是BPMN 2.0规范定义的官方任务,Camel任务时由专用的服务任务实现的
  • 使用Camel任务功能,要把Activiti Camel包含到项目中
定义Camel任务
  • Camel任务是一个专用的服务任务, 服务任务的type设置为camel
<serviceTask id="sendCamel" activiti:type="camel">
  • 流程定义只需要在服务任务中定义Camel类型
  • 集成逻辑都会代理给Camel容器
  • 默认Activiti引擎会在spring容器中查找camelContext bean.camelContext定义了camel容器加载的路由规则
  • 路由规则是既可以从指定的java包下加载, 也可以通过spring配置直接定义路由规则
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
  <packageScan>
    <package>org.activiti.camel.route</package>
  </packageScan>
</camelContext>
  • 定义多个Camel环境bean,并且使用不同的bean名称. 可以重载CamelTask的定义
<serviceTask id="serviceTask1" activiti:type="camel">
        <extensionElements>
                <activiti:field name="camelContext" stringValue="customCamelContext" />
        </extensionElements>
</serviceTask>
Camel调用
  • 为了激活一个特定的Camel路由:
  • 需要一个Spring环境,包含SimpleCamelCallRoute的路由的类文件,放在packageScan标签的扫描目录下
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
        <packageScan>
                <package>org.activiti.camel.examples.simpleCamelCall</package>
        </packageScan>
</camelContext>
  • 路由的定义:
public class SimpleCamelCallRoute extends RouteBuilder {

  @Override
  public void configure() throws Exception {

          from("activiti:SimpleCamelCallProcess:simpleCall").to("log: org.activiti.camel.examples.SimpleCamelCall");
  }
}

这个规则用于打印消息体

  • 终端的格式包含三部分:

    • 终端URL: 引用activiti终端
    • SimpleCamelCallProcess: 流程名
    • simpleCall: 流程中的Camel服务
  • 配置好规则后,可以让Camel进行使用.工作流如下:
<process id="SimpleCamelCallProcess">
        <startEvent id="start"/>
        <sequenceFlow id="flow1" sourceRef="start" targetRef="simpleCall"/>
                
        <serviceTask id="simpleCall" activiti:type="camel"/>
                
        <sequenceFlow id="flow2" sourceRef="simpleCall" targetRef="end"/>
        <endEvent id="end"/>
</process>

serviceTask部分,注明服务的类型是Camel, 目标规则名为simpleCall. 这与上面的Activiti终端相匹配.初始化流程后,会看到一个空的日志

乒乓实例
  • Camel和Activiti之间需要交互,向Camel发送和接收数据
  • 发送一个字符串,把变量里的消息发送给Camel,Camel进行一些处理,然后返回结果:
@Deployment
public void testPingPong() {
  Map<String, Object> variables = new HashMap<String, Object>();

  variables.put("input", "Hello");
  Map<String, String> outputMap = new HashMap<String, String>();
  variables.put("outputMap", outputMap);

  runtimeService.startProcessInstanceByKey("PingPongProcess", variables);
  assertEquals(1, outputMap.size());
  assertNotNull(outputMap.get("outputValue"));
  assertEquals("Hello World", outputMap.get("outputValue"));
}
  • 变量input是Camel规则的实际输入 ,outputMap会记录camel返回的结果
<process id="PingPongProcess">
  <startEvent id="start"/>
  <sequenceFlow id="flow1" sourceRef="start" targetRef="ping"/>
  <serviceTask id="ping" activiti:type="camel"/>
  <sequenceFlow id="flow2" sourceRef="ping" targetRef="saveOutput"/>
  <serviceTask id="saveOutput"  activiti:class="org.activiti.camel.examples.pingPong.SaveOutput" />
  <sequenceFlow id="flow3" sourceRef="saveOutput" targetRef="end"/>
  <endEvent id="end"/>
</process>
  • SaveOuput这个serviceTask, 会把Output变量的值从上下文保存到OutputMap
  • 变量提交给Camel的方法是由CamelBehavior控制的.配置一个期望的Camel操作:
<serviceTask id="serviceTask1" activiti:type="camel">
  <extensionElements>
    <activiti:field name="camelBehaviorClass" stringValue="org.activiti.camel.impl.CamelBehaviorCamelBodyImpl" />
  </extensionElements>
</serviceTask>
  • 如果特别指定一个行为,就会使用org.activiti.camel.impl.CamelBehaviorDefaultImpl. 这个行为会把变量复制成名称相同的Camel属性
  • 在返回时,无论选择什么行为,如果camel消息体是一个map,每个元素都会复制成一个变量.否则整个对象会复制到指定名称为camelBody的变量中
@Override
public void configure() throws Exception {
  from("activiti:PingPongProcess:ping").transform().simple("${property.input} World");
} 

在这个规则中,字符串world会被添加到input属性的后面,结果会写入消息体
这时可以检查javaServiceTask中的camelBody变量,复制到outputMap中,并在testcase进行判断

  • 在启动的所有camel规则中 ,流程实例ID会复制到Camel的名为PROCESS_ID_PROPERTY的属性中,后续可以用来关联流程实例和Camel规则,也可以在camel规则中直接使用
  • Activiti中可以使用三种不同Camel的行为: 可以通过在规则URL中指定来实现覆盖
from("activiti:asyncCamelProcess:serviceTaskAsync2?copyVariablesToProperties=true").

Activiti变量如何传递给camel:

行为 URL 描述
CamelBehaviorDefaultImpl copyVariablesToProperties 把Activiti变量复制为Camel属性
CamelBehaviorCamelBodyImpl copyCamelBodyToBody 只把名为"camelBody"Activiti变量复制成Camel的消息体
CamelBehaviorBodyAsMapImpl copyVariablesToBodyAsMap 把Activiti的所有变量复制到一个map里,作为Camel的消息体

Camel的变量如何返回给Activiti,只能配置在规则URL中:

URL 描述
默认 如果Camel消息体是一个map,把每个元素复制成Activiti的变量.否则把整个Camel消息体作为Activiti的camelBody变量
copyVariablesFromProperties 把Camel属性以相同名称复制为Activiti变量
copyCamelBodyToBodyAsString 和默认一样,但是如果camel消息体不是map时,先把它转换成字符串,再设置为camelBody
copyVariablesFromHeader 额外把Camel头部以相同名称复制成Activiti变量
异步乒乓实例
  • 同步的乒乓实例,流程会等到Camel规则返回之后才会停止
  • 某些情况下,需要Activiti工作流继续运行,就要使用camelServiceTask的异步功能
  • 通过设置camelServiceTaskasync属性来启用这个功能
<serviceTask id="serviceAsyncPing" activiti:type="camel" activiti:async="true"/>

Camel规则会被Activiti的jobExecutor异步执行
当在Camel规则中定义了一个队列,Activiti流程会在camelServiceTask执行时继续运行
camel规则以完全异步的方式执行

  • 可以使用一个receiveTask等待camelServiceTask的返回值,流程实例会等到接收一个来自camel的signal:
<receiveTask id="receiveAsyncPing" name="Wait State" />
  • 在Camel中可以发送一个signal给流程实例,通过对应的Activiti终端发送消息:
from("activiti:asyncPingProcess:serviceAsyncPing").to("activiti:asyncPingProcess:receiveAsyncPing");
  • 在Activiti终端中,会使用冒号分隔的三个部分:

    • 常量字符串activiti
    • 流程名称
    • 接收任务名
Camel规则中实例化工作流
  • 一般情况下,Activiti工作流会先启动,然后在流程中启动Camel规则
  • 在已经启动的Camel规则中启动一个工作流,会触发一个receiveTask
  • 十分类似,除了最后的部分.实例规则如下:
from("direct:start").to("activiti:camelProcess");

url有两个部分:

  • 常量字符串activiti
  • 流程的名称

流程已经部署完成,并且是可以启动的

手工任务

描述
  • 手工任务定义了BPMN引擎外部的任务
  • 表示工作需要某人完成,而引擎不需要知道, 也没有对应的系统和UI接口
  • 对于BPMN引擎而言,手工任务是直接通过的活动,流程到达它之后会自动向下执行
图形标记
  • 手工任务显示为普通任务(圆角矩形),左上角是一个手型小图标

在这里插入图片描述

XML内容
<manualTask id="myManualTask" name="Call client for more information" />

Java接收任务

描述
  • 接收任务是一个简单任务,会等待对应消息的到达
  • 当流程达到接收任务,流程状态会保存到存储里.意味着流程会等待在这个等待状态,直到引擎接收了一个特定的消息,触发流程穿过接收任务继续执行
图形标记
  • 接收任务显示为一个任务(圆角矩形),右上角有一个消息小标记.消息是白色的(黑色图标表示发送语义)

在这里插入图片描述

XML内容
<receiveTask id="waitState" name="wait" /> 
  • 要在接收任务等待的流程实例继续执行,可以调用runtimeService.signal(executionId), 传递接收任务上流程的id:
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
  .processInstanceId(pi.getId())
  .activityId("waitState")
  .singleResult();
assertNotNull(execution);

runtimeService.signal(execution.getId());

Shell任务

描述
  • Shell任务可以执行Shell脚本和命令
  • Shell任务不是BPMN 2.0规范定义的官方任务,没有对应的图标
定义Shell任务
  • Shell任务是一个专用的服务任务,这个服务任务的type设置为shell
<serviceTask id="shellEcho" activiti:type="shell">
  • Shell任务使用属性注入进行配置,所有属性都可以包含EL表达式, 会在流程执行过程中解析
属性 是否必须 类型 描述 默认值
command String 执行的shell命令
arg0-5 String 参数0至5
wait true/false 是否需要等待到shell进程结束 true
redirectError true/false 把标准错误打印到标准流中 false
cleanEnv true/false Shell进行不继承当前环境 false
outputVariable String 保存输出的变量名 不会记录输出结果
errorCodeVariable String 包含结果错误代码的变量名 不会注册错误级别
directory String Shell进程的默认目录 当前目录
应用实例
  • 执行shell脚本cmd /c echo EchoTest, 等到它结束,再把输出结果保存到resultVar中:
<serviceTask id="shellEcho" activiti:type="shell" >
  <extensionElements>
    <activiti:field name="command" stringValue="cmd" />
    <activiti:field name="arg1" stringValue="/c" />
    <activiti:field name="arg2" stringValue="echo" />
    <activiti:field name="arg3" stringValue="EchoTest" />
    <activiti:field name="wait" stringValue="true" />
    <activiti:field name="outputVariable" stringValue="resultVar" />
  </extensionElements>
</serviceTask>           

执行监听器

  • 执行监听器可以在流程定义中发生了某个事件时执行外部Java代码或执行表达式
  • 执行监听器可以捕获的事件有:

    • 流程实例的启动和结束
    • 选中一条连线
    • 节点的开始和结束
    • 网关的开始和结束
    • 中间事件的开始和结束
    • 开始时间结束或结束事件开始
  • 下面的流程定义定义了3个流程监听器:
 <process id="executionListenersProcess">

    <extensionElements>
      <activiti:executionListener class="org.activiti.examples.bpmn.executionlistener.ExampleExecutionListenerOne" event="start" />
    </extensionElements>

    <startEvent id="theStart" />
    <sequenceFlow sourceRef="theStart" targetRef="firstTask" />

    <userTask id="firstTask" />
    <sequenceFlow sourceRef="firstTask" targetRef="secondTask">
    <extensionElements>
      <activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
    </extensionElements>
    </sequenceFlow>

    <userTask id="secondTask" >
    <extensionElements>
      <activiti:executionListener expression="${myPojo.myMethod(execution.event)}" event="end" />
    </extensionElements>
    </userTask>
    <sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />

    <userTask id="thirdTask" />
    <sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />

    <endEvent id="theEnd" />

  </process>
  • 第一个流程监听器监听流程开始. 监听器是一个外部Java类(例如ExampleExecutionListenerOne),需要实现org.activiti.engine.delegate.ExecutionListener接口.当事件发生时(end事件),会调用notify(ExecutionListenerExecution execution) 方法:
public class ExampleExecutionListenerOne implements ExecutionListener {

  public void notify(ExecutionListenerExecution execution) throws Exception {
    execution.setVariable("variableSetInExecutionListener", "firstValue");
    execution.setVariable("eventReceived", execution.getEventName());
  }
}

也可以使用实现org.activiti.engine.delegate.JavaDelegate接口的代理类(代理类可以在结构中重用,比如serviceTask的代理)

  • 第二个流程监听器在连线执行时调用. 注意这个listener元素不能定义event, 因为连线只能触发take事件,为连线定义的监听器的event属性会被忽略
  • 第三个流程监听器在节点secondTask结束时调用. 使用expression代替class来在事件触发时执行或调用
<activiti:executionListener expression="${myPojo.myMethod(execution.eventName)}" event="end" />

流程变量可以处理和使用
流程实现对象有一个保存事件名称的属性,在方法中使用execution.eventName获的事件名称

  • 流程监听器也支持delegateExpression, 和服务任务相同
<activiti:executionListener event="start" delegateExpression="${myExecutionListenerBean}" />
  • 脚本流程监听器org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener可以为某个流程监听事件执行一段脚本
<activiti:executionListener event="start" class="org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener" >
  <activiti:field name="script">
    <activiti:string>
      def bar = "BAR";  // local variable
      foo = "FOO"; // pushes variable to execution context
      execution.setVariable("var1", "test"); // test access to execution instance
      bar // implicit return value
    </activiti:string>
  </activiti:field>
  <activiti:field name="language" stringValue="groovy" />
  <activiti:field name="resultVariable" stringValue="myVar" />
<activiti:executionListener>
流程监听器的属性注入
  • 流程监听器时,可以配置class属性,使用属性注入.这和使用服务任务属性注入相同
  • 使用属性注入的流程监听器的流程示例:
<process id="executionListenersProcess">
    <extensionElements>
      <activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener" event="start">
        <activiti:field name="fixedValue" stringValue="Yes, I am " />
        <activiti:field name="dynamicValue" expression="${myVar}" />
      </activiti:executionListener>
    </extensionElements>

    <startEvent id="theStart" />
    <sequenceFlow sourceRef="theStart" targetRef="firstTask" />

    <userTask id="firstTask" />
    <sequenceFlow sourceRef="firstTask" targetRef="theEnd" />

    <endEvent id="theEnd" />
  </process>     
public class ExampleFieldInjectedExecutionListener implements ExecutionListener {

  private Expression fixedValue;

  private Expression dynamicValue;

  public void notify(ExecutionListenerExecution execution) throws Exception {
    execution.setVariable("var", fixedValue.getValue(execution).toString() + dynamicValue.getValue(execution).toString());
  }
}

ExampleFieldInjectedExecutionListener类串联了两个注入的属性(一个是固定的,一个是动态的),把他们保存到流程变量var

@Deployment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
  Map<String, Object> variables = new HashMap<String, Object>();
  variables.put("myVar", "listening!");

  ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);

  Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
  assertNotNull(varSetByListener);
  assertTrue(varSetByListener instanceof String);

  // Result is a concatenation of fixed injected field and injected expression
  assertEquals("Yes, I am listening!", varSetByListener);
}

任务监听器

  • 任务监听器可以在发生对应的任务相关事件时执行自定义Java逻辑或表达式
  • 任务监听器只能添加到流程定义中的用户任务中. 必须定义在BPMN 2.0 extensionElements的子元素中,并使用activiti命名空间, 因为任务监听器是activiti独有的结构
<userTask id="myTask" name="My Task" >
  <extensionElements>
    <activiti:taskListener event="create" class="org.activiti.MyTaskCreateListener" />
  </extensionElements>
</userTask>
  • 任务监听器支持的属性:

    • event(必选): 任务监听器会被调用的任务类型

      • create: 任务创建并设置所有属性后触发
      • assignment: 任务分配给一些人时触发.当流程到达userTask,assignment事件会在create事件之前发生(当获得create时间时,我们想获得任务的所有属性,包括执行人)
      • complete: 当任务完成,并尚未从运行数据中删除时触发
      • delete: 只在任务删除之前发生,在通过completeTask正常完成时,也会执行
    • class: 必须调用的代理类, 类要实现org.activiti.engine.delegate.TaskListener接口

       public class MyTaskCreateListener implements TaskListener {
      
      public void notify(DelegateTask delegateTask) {
      // Custom logic goes here
      }
      
      }

    可以使用属性注入把流程变量或执行传递给代理类
    代理类的实例是在部署时创建的,所有流程实例都会共享同一个实例

    • expression: 指定事件发生时执行的表达式.无法同时与class属性一起使用. 可以把DelegateTask对象和事件名称(task.eventName)作为参数传递给调用的对象

      <activiti:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
      • delegateExpression: 指定一个表达式,解析一个实现了TaskListener接口的对象,与服务任务一致

         <activiti:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
  • 脚本任务监听器org.activiti.engine.impl.bpmn.listener.ScriptTaskListener可以为任务监听器事件执行脚本
<activiti:taskListener event="complete" class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" >
  <activiti:field name="script">
    <activiti:string>
      def bar = "BAR";  // local variable
      foo = "FOO"; // pushes variable to execution context
      task.setOwner("kermit"); // test access to task instance
      bar // implicit return value
    </activiti:string>
  </activiti:field>
  <activiti:field name="language" stringValue="groovy" />
  <activiti:field name="resultVariable" stringValue="myVar" />
<activiti:taskListener>

多实例(循环)

描述
  • 多实例节点是在业务流程中定义重复环节的方法
  • 多实例和循环是一样的:它可以根据给定的集合,为每个元素执行一个环节甚至一个完整的子流程,既可以顺序依次执行也可以并发同步执行
  • 多实例是在一个普通的节点上添加了额外的属性定义(所以叫做'多实例特性),这样运行时节点就会执行多次
  • 可以设置成多实例节点的节点:

    • User Task
    • Script Task
    • Java Service Task
    • WebService Task
    • Business Rule Task
    • Email Task
    • Manual Task
    • Receive Task
    • (Embedded) Sub-Process [(嵌入子)流程]
    • Call Activity [调用子流程]
  • 网关和事件不能设置多实例
  • 每个上级流程为每个实例创建分支时都要提供下列变量:

    • nrOfInstances: 实例总数
    • nrOfActiveInstances: 当前活动,还没完成的实例数量. 顺序执行的多实例,值一直为1
    • nrOfCompletedInstances: 已经完成实例的数目
  • 通过execution.getVariable(Xx) 方法获得这些变量
  • 每个创建的分支都会有分支级别的本地变量(例如其他实例不可见,不会保存到流程实例级别):

    • loopCounter- 特定实例的在循环的索引值
    • 使用activitielementIndexVariable属性修改loopCounter的变量名
图形标记
  • 多实例的节点,会在节点底部显示三条短线.三条竖线表示实例会并行执行. 三条横线表示顺序执行

在这里插入图片描述

XML内容

  • 要把一个节点设置为多实例,节点xml元素必须设置一个multiInstanceLoopCharacteristics子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
 ...
</multiInstanceLoopCharacteristics>

isSequential属性表示节点是进行顺序执行还是并行执行

  • 实例的数量会在进入节点时计算一次:

    • 一种方法是使用loopCardinality子元素

可以使用loopCardinality子元素中直接指定一个数字

<multiInstanceLoopCharacteristics isSequential="false|true">
   <loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
   也可以使用**loopCardinality**子元素中结果为整数的表达式
```xml
<multiInstanceLoopCharacteristics isSequential="false|true">
   <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
```
  • 另一个方法是通过loopDataInputRef子元素,设置一个类型为集合的流程变量名.对于集合中的每个元素,都会创建一个实例.也可以通过inputDataItem子元素指定集合

    <userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}">
        <multiInstanceLoopCharacteristics isSequential="false">
             <loopDataInputRef>assigneeList</loopDataInputRef>
             <inputDataItem name="assignee" />
          </multiInstanceLoopCharacteristics>
    </userTask>

    假设assigneeList变量包含这些值[kermit, gonzo, foziee],三个用户任务会同时创建.每个分支都会拥有一个用名为assignee的流程变量,这个变量会包含集合中的对应元素,上面是用来设置用户任务的分配者

  • loopDataInputRefinputDataItem的缺点:

    • 名字难于记忆
    • 根据BPMN 2.0格式定义 ,不能包含表达式
  • 在activiti中可以在multiInstanceCharacteristics中设置collectionelementVariable
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true"
     activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
</userTask>
  • 多实例节点在所有实例都完成时才会结束
  • 可以指定一个表达式在每个实例结束时执行,如果表达式返回true,所有其它的实例都会销毁,多实例节点也会结束.流程会继续执行. 表达式必须定义在completionCondition子元素中
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false"
     activiti:collection="assigneeList" activiti:elementVariable="assignee" >
    <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
  </multiInstanceLoopCharacteristics>
</userTask>

assigneeList集合的每个元素都会创建一个并行的实例,当60%的任务完成时,其他任务就会删除,流程继续执行

边界事件和多实例
  • 多实例是一个普通节点,可以在边缘使用边界事件
  • 对于中断型边界事件,当捕获事件时,所有激活的实例都会销毁

-
子流程的所有实例都会在定时器触发时销毁,无论有多少实例,也不论内部节点没有完成

补偿处理器

描述
  • 如果一个节点用来补偿另一个节点的业务, 可以声明为一个补偿处理器
  • 补偿处理器不包含普通的流,只在补偿事件触发时执行
  • 补偿处理器不能包含进入和外出顺序流
  • 补偿处理器必须使用直接关联分配给一个补偿边界事件
图形标记
  • 节点是补偿处理器,补偿事件图标会显示在中间底部区域
  • 补偿处理器图形示例:一个服务任务,附加了一个补偿边界事件,并分配了一个补偿处理器.注意cancel hotel reservation服务任务中间底部区域显示的补偿处理器图标

在这里插入图片描述

XML内容
  • 声明作为补偿处理器的节点,需要把isForCompensation设置为true:
<serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="...">
</serviceTask>
相关文章
|
3月前
|
缓存 前端开发
ProFlow 流程编辑器框架问题之创建一个自定义节点如何解决
ProFlow 流程编辑器框架问题之创建一个自定义节点如何解决
42 1
|
5月前
|
DataWorks 监控 安全
DataWorks产品使用合集之如何使用节点依赖关系来配置任务之间的执行顺序
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
59 2
|
4月前
|
数据采集 DataWorks 安全
DataWorks产品使用合集之如何通过接口单独触发业务流程里面的某些节点运行
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
5月前
|
存储 算法 Swift
Swift开发——循环执行方式
Swift语言中的循环主要包括`for-in`和`while`结构。`for-in`适用于遍历数字区间、字符串和字典,支持使用`stride`函数定制步进。字典遍历时,可以用二元元组`(k, v)`访问键值对。`while`循环有标准形式和`repeat-while`形式,确保至少执行一次循环体。程序示例展示了`for-in`和不同`while`结构的用法,包括计算阶乘、奇数和、加密字符串以及最大公约数和最小公倍数。
39 0
Swift开发——循环执行方式
|
5月前
|
DataWorks API 调度
DataWorks产品使用合集之在调度配置配置了节点的上游节点输出,没办法自动生成这个flow的依赖,该怎么操作
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
6月前
|
前端开发
基于jeecgboot的flowable流程增加节点自动跳过功能
基于jeecgboot的flowable流程增加节点自动跳过功能
449 2
|
6月前
|
XML 中间件 数据库
基于jeecgboot的flowable流程支持定时边界事件
基于jeecgboot的flowable流程支持定时边界事件
126 0
|
6月前
修正flowable流程支持节点表单字段条件判断
修正flowable流程支持节点表单字段条件判断
91 0
|
6月前
|
Web App开发 API
在机器人流程自动化(RPA)中,判断网页或元素是否加载完成是一个重要的步骤
【2月更文挑战第24天】在机器人流程自动化(RPA)中,判断网页或元素是否加载完成是一个重要的步骤
357 6
|
JavaScript 测试技术 Python
WebUI自动化测试中隐藏的元素如何操作?三种元素等待方式如何理解?
WebUI自动化测试中隐藏的元素如何操作?三种元素等待方式如何理解?
78 0