Flowable实战(八)BPMN2.0 任务

  任务是流程中最重要的组成部分。Flowable提供了多种任务类型,以满足实际需求。

  常用任务类型有:

  • 用户任务
  • Java Service任务
  • 脚本任务
  • 业务规则任务
  • 执行监听器
  • 任务监听器
  • 多实例

  集成扩展的任务类型有:

  • 手动任务
  • Java接收任务
  • Shell任务
  • 补偿处理器
  • Web Service任务
  • 邮件任务
  • Http任务
  • Camel任务
  • Mule任务

  任务的图形都是以一个圆角矩形为基础,在左上角添加具体类型的图标。

一、常用的任务类型

1.1 用户任务

1.1.1 描述

  “用户任务(user task)”指需要人工执行的任务。当流程执行到达用户任务时,流程实例会停止等待,直到用户触发完成任务动作。

1.1.2 图示

  用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。

1.1.3 XML表示

  用户任务在XML中如下定义。其中id是必须属性,name是可选属性。

    

1.1.4 到期日期

  每个任务都可以设置到期日期(due date)。

  可以指定固定时间或相对时间,比如,当dueDate为“PT30M”时,表示到达任务30分钟后到期。

  到期日期必须符合java.util.Date或java.util.String(ISO8601格式)。

  实际应用,我们指定为变量值。

    

  任务的到期日期可以使用TaskService,或者在TaskListener中使用传递的DelegateTask修改。

1.1.5 任务指派

  • 指派确定的办理人
    
  • 指派潜在办理人
    
  • 指派潜在办理组
    

  更多任务指派的内容,已在“用户和组”的篇章中介绍,这里不再赘述。

1.2 Java Service任务

1.2.1 描述

  Java Service任务(Java service task)用于调用Java类。Java Service不属于BPMN2.0规范,而是Flowable的自定义扩展。

1.2.2 图示

  服务任务用左上角有一个小齿轮图标的圆角矩形表示。

1.2.3 XML表示

  有三种方法声明如何调用Java逻辑,下面分别介绍:

  • 调用固定的类

  使用flowable:class属性提供全限定类名(fully qualified classname),指定流程执行时调用的类,该类必须实现JavaDelegate或ActivityBehavior接口。

    
  • 调用动态类

  使用flowable:delegateExpression属性提供委托对象(delegation object)的表达式。该功能和flowable:class类似,同样需要实现JavaDelegate或ActivityBehavior接口,只不过这里不是指定一个具体的实现类,而是查询指定名称的Bean对象。

    

  myDelegateExpressionBean是一个实现了JavaDelegate接口的bean,定义在Spring容器中。

  • 调用类的指定方法或属性值

  使用flowable:expression属性指定类的方法或属性值。同样的,该类需要实现JavaDelegate或ActivityBehavior接口。

    

  将在名为printer的对象上调用printMessage方法(不带参数)。当然也可以为表达式中使用的方法传递变量。

  属性值示例:

    

  会调用名为printer的bean的ready参数的getter方法,getReady(不带参数)。该值会被解析为执行的流程变量。

1.2.4 具体实现实例

  下面是一个Java类的示例,用于将流程变量String改为大写。这个类通过实现org.flowable.engine.delegate.JavaDelegate接口,可以在流程执行中被调用。

  同时,需要重写execute(DelegateExecution)方法实现业务逻辑。这个方法就是引擎将调用的方法。另外,通过该方法中的DelegateExecution参数可以访问流程实例的各种信息。

    public class ToUppercase implements JavaDelegate {
      public void execute(DelegateExecution execution) {
        String var = (String) execution.getVariable("input");
        var = var.toUpperCase();
        execution.setVariable("input", var);
      }
    }

  如果实现org.flowable.engine.impl.delegate.ActivityBehavior接口,可以访问更强大的引擎功能,例如,可以影响流程的控制流程。但注意这并不是好的实践,需要避免这么使用。

1.2.5 任务的返回值

  服务执行的返回值(仅对使用表达式的服务任务),可以通过为服务任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。 如果指定为已存在的流程变量,则流程变量的值会被服务执行的返回值覆盖。 如果不指定结果变量名,则服务任务的返回值将被忽略。

    

  在上例中,服务执行的结果(调用'doSomething()'方法的返回值),在服务执行完成后,会设置为名为'myVar'的流程变量。

1.2.6 异常处理

  当执行自定义逻辑时,通常需要捕获并在流程中处理特定的业务异常。Flowable提供了多种方式。

1.2.6.1 抛出BPMN错误

  可以在服务任务或脚本任务的用户代码中抛出BPMN错误。可以在Java委托、脚本、表达式与委托表达式中,抛出特殊的FlowableException:BpmnError。引擎会捕获这个异常,并将其转发至合适的错误处理器,如错误边界事件或错误事件子流程。

    public class ThrowBpmnErrorDelegate implements JavaDelegate {
      public void execute(DelegateExecution execution) throws Exception {
        try {
          executeBusinessLogic();
        } catch (BusinessException e) {
          throw new BpmnError("BusinessExceptionOccurred");
        }
      }
    }

  构造函数的参数是错误代码。错误代码决定了处理这个错误的错误处理器。

  这个机制只应该用于业务错误,需要通过流程中定义的错误边界事件或错误事件子流程处理。技术错误应该通过其他异常类型表现,并且通常不在流程内部处理。

1.2.6.2 异常映射

  可以使用mapException扩展,直接将Java异常映射至业务异常(错误)。单映射是最简单的形式:

    
      
        com.example.SomeException
      
    

  在上面的代码中,如果服务任务抛出org.flowable.SomeException的实例,引擎会捕获该异常,并将其转换为带有给定errorCode的BPMN错误。然后就可以像普通BPMN错误完全一样地处理。其他的异常没有映射,仍将抛出至API调用处。

  也可以在单行中使用includeChildExceptions属性,映射特定异常的所有子异常。

    
      
        com.example.SomeException
      
    

  上面的代码中,Flowable会将SomeException的任何直接或间接的子类,转换为带有指定错误代码的BPMN错误。 当未指定includeChildExceptions时,视为“false”。

1.2.6.3 默认映射

  默认映射最常用。默认映射是一个不指定类的映射,可以匹配任何Java异常:

    
      
        
      
    

  除了默认映射,会按照从上至下的顺序检查映射,使用第一个匹配的映射。只在所有映射都不能成功匹配时使用默认映射。 只有第一个不指定类的映射会作为默认映射。默认映射忽略includeChildExceptions

1.2.6.4 异常顺序流

  还有种推荐用法,在发生异常时,将流程执行路由至另一条路径。下面是一个例子。

    
    

    
    

  服务任务有两条出口顺序流,命名为exceptionno-exception。在发生异常时,使用顺序流ID控制流程流向:

    public class ThrowsExceptionBehavior implements ActivityBehavior {

      public void execute(DelegateExecution execution) {
        String var = (String) execution.getVariable("var");

        String sequenceFlowToTake = null;
        try {
          executeLogic(var);
          sequenceFlowToTake = "no-exception";
        } catch (Exception e) {
          sequenceFlowToTake = "exception";
        }
        DelegateHelper.leaveDelegate(execution, sequenceFlowToTake);
      }

    }

1.3 脚本任务

1.3.1 描述

  脚本任务(script task)是自动执行的活动。当流程执行到达脚本任务时,会执行相应的脚本。

1.3.2 图示

  脚本任务用左上角有一个小“脚本”图标的标准BPMN 2.0任务(圆角矩形)表示。

1.3.3 XML表示

  脚本任务使用scriptscriptFormat元素定义。

    
      
    

  默认情况下,JavaScript包含在每一个JDK中,因此不需要添加任何JAR文件。如果想使用其它脚本引擎,则需要在classpath中添加相应的jar,并使用适当的名字。例如,Flowable单元测试经常使用Groovy。Groovy脚本引擎与groovy-all JAR捆绑在一起。添加如下依赖:

    
        org.codehaus.groovy
        groovy-all
        2.x.x
    

1.3.4 脚本中的变量

  到达脚本引擎的执行中,所有的流程变量都可以在脚本中使用。在这个例子里,脚本变量'inputArray'实际上就是一个流程变量(一个integer的数组)。

    

  在脚本中设置变量的例子:

    
注意:下列名字是保留字, 不能用于变量名: out,out:print,lang:import,context,elcontext

1.3.5 脚本任务的结果

  脚本任务的返回值,可以通过为脚本任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。如果指定为已存在的流程变量,则流程变量的值会被脚本执行的结果值覆盖。如果不指定结果变量名,则脚本结果值将被忽略。

    
      
    

  在上面的例子中,脚本执行的结果(解析表达式'#{echo}'的值),将在脚本完成后,设置为名为'myVar'的流程变量。

1.4 业务规则任务

1.4.1 描述

  在企业应用中,推荐做法是使用可维护的规则库来管理复杂多变的业务规则,将业务代码和规则分开维护,一旦规则有变动,只需修改预设规则即可,而不会影响到业务代码。

  业务规则任务可以根据流程变量的值处理预设的业务规则。Flowable支持目前最流行的规则引擎——Drools。只需把含有业务规则任务的流程文件和规则引擎文件“.drl”一同打包部署到系统中,同时添加Drools的jar包,即可实现Flowable驱动规则引擎。

1.4.2 图示

  业务规则任务显示为带有表格图标的圆角矩形。

1.4.3 XML表示

  要执行业务规则,需要定义输入与结果变量。输入变量可以用流程变量的列表定义,使用逗号分隔。输出变量只能有一个变量名,如果没有指定结果变量名,默认为org.flowable.engine.rules.OUTPUT。

    
      
      

      

      

      
    

  也可以将业务规则任务配置为只执行部署的.drl文件中的一组规则。要做到这一点,需要指定规则名字的列表,用逗号分隔。

    

  这样只会执行rule1与rule2。

  也可以定义需要从执行中排除的规则列表。

    

  这个例子中,除了rule1与rule2之外,其它所有与流程定义一起部署的规则都会被执行。

注意:集成Drools的业务规则任务,是企业应用中的重要内容,需要重点掌握。

1.5 执行监听器

1.5.1 描述

  执行监听器(execution listener)可以在流程执行中发生特定的事件时,执行外部Java代码或计算表达式。可以被捕获的事件有:

  • 流程实例的启动和结束。
  • 流程执行转移。
  • 活动的启动和结束。
  • 网关的启动和结束。
  • 中间事件的启动和结束。
  • 启动事件的结束,和结束事件的启动。

1.5.2 XML表示

  下面的流程定义包含了三个执行监听器:



  
    
  

  
  

  
  
    
      
    
  

  
    
      
    
  
  

  
  

  

  第一个执行监听器将在流程启动时收到通知。这个监听器是一个外部Java类(ExampleExecutionListenerOne),并且需要实现org.flowable.engine.delegate.ExecutionListener接口。当该事件发生时(这里是start事件),会调用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.flowable.engine.delegate.JavaDelegate接口的委托类。这些委托类也可以用于其他的结构,如服务任务的委托。

  第二个执行监听器在流程执行转移时被调用。请注意listener元素并未定义event,因为在转移上只会触发take事件。当监听器定义在转移上时,event属性的值将被忽略。

最后一个执行监听器在secondTask活动结束时被调用。监听器声明中没有使用class,而是定义了expression。这个表达式将在事件触发时计算/调用。

    

  与其他表达式一样,可以使用与解析execution变量。

1.5.3 执行监听器上的字段注入

  使用通过class属性配置的执行监听器时,可以使用字段注入。

  下面的代码片段展示了一个简单的示例流程,带有一个使用了字段注入的执行监听器。


  
    

      
      

    
  

  
  

  
  

  

  ExampleFieldInjectedExecutionListener类将连接两个字段(一个是固定值-fixedValue,另一个是动态值-dynamicValue),并将其存储在'var'流程变量中。

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

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

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

      // 结果为固定注入字段及注入表达式的连接
      assertEquals("Yes, I am listening!", varSetByListener);
    }

1.6 任务监听器

1.6.1 描述

  任务监听器(task listener)用于在特定的任务相关事件发生时,执行自定义的Java逻辑或表达式。

1.6.2 XML表示

  任务监听器只能在流程定义中作为用户任务的子元素。请注意,任务监听器是一个Flowable自定义结构,因此也需要作为BPMN 2.0 extensionElements,放在flowable命名空间下。

    
      
        
      
    

1.6.3 任务监听器属性:

1.6.3.1 event

  触发任务监听器的任务事件类型,必填项。可用的事件有:

  • create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。
  • assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。这顺序看起来不太自然,但是有实际原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
  • complete(完成):当任务已经完成,从运行时数据中删除前触发。
  • delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
1.6.3.2 class

  需要调用的委托类。这个类必须实现org.flowable.engine.delegate.TaskListener接口。

    public class MyTaskCreateListener implements TaskListener {
      public void notify(DelegateTask delegateTask) {
        // 这里是要实现的业务逻辑
      }
    }

  也可以使用字段注入,为委托类传递流程变量或执行。请注意委托类的实例在流程部署时创建(与Flowable中其它的委托类一样),这意味着该实例会在所有流程实例执行中共享。

1.6.3.3 expression

  指定在事件发生时要执行的表达式(不能与class属性一起使用)。可以为被调用的对象传递DelegateTask对象与事件名(使用task.eventName)作为参数。

    
1.6.3.4 delegateExpression

  指定一个能够解析为TaskListener接口实现类的对象的表达式。

    

1.7 多实例

1.7.1 描述

  多实例活动(multi-instance activity)是在业务流程中,为特定步骤定义重复的方式。在编程概念中,多实例类似for each结构:可以为给定集合中的每一条目,顺序或并行地,执行特定步骤,甚至是整个子流程。

  网关和事件不能设置为多实例。

  按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:

  • nrOfInstances:实例总数。
  • nrOfActiveInstances:当前活动的(即未完成的)实例数量。对于顺序多实例,这个值总为1。
  • nrOfCompletedInstances:已完成的实例数量。

  可以调用execution.getVariable(x)方法获取这些值。

  另外,每个被创建的执行,都有局部变量(对其他执行不可见,也不存储在流程实例级别):

  • loopCounter:给定实例在for-each循环中的index

1.7.2 图示

  如果一个活动是多实例,将通过在该活动底部的三条短线表示。三条竖线代表实例会并行执行,而三条横线代表顺序执行。

Flowable实战(八)BPMN2.0 任务_第1张图片

1.7.3 XML表示

  要将活动变成多实例,该活动的XML元素必须有multiInstanceLoopCharacteristics子元素

    
     ...
    

  isSequential属性代表了活动的实例为顺序还是并行执行。

  有4种不同方法可以配置数量。

1.7.3.1 指定数字

  通过loopCardinality子元素,直接指定数字:

    
      5
    
1.7.3.2 表达式

  使用解析为正整数的表达式:

    
      ${nrOfOrders-nrOfCancellations}
    
1.7.3.3 指定集合

  另一个定义实例数量的方法,是使用loopDataInputRef子元素,指定一个集合型流程变量的名字。对集合中的每一项,都会创建一个实例。可以使用inputDataItem子元素,将该项设置给该实例的局部变量。在下面的XML示例中展示:

    
      
        assigneeList
        
      
    

  假设变量assigneeList包含[kermit, gonzo, fozzie]。上面的代码会创建三个并行的用户任务。每一个执行都有一个名为assignee的(局部)流程变量,含有集合中的一项,并在这个例子中被用于指派用户任务。

  loopDataInputRefinputDataItem的缺点是名字很难记,并且由于BPMN 2.0概要的限制,不能使用表达式。Flowable通过在multiInstanceCharacteristics上提供collectionelementVariable属性解决了这些问题:

    
      
      
    

  请注意collection属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,这个字符串都会被当做变量名,在流程变量中用于获取实际的集合。

  例如,下面的代码片段会让引擎查找存储在assigneeList流程变量中的集合:

    
      
      
    
1.7.3.4 条件型数量

  多实例活动在所有实例都完成时结束。然而,也可以指定一个表达式,在每个实例结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例活动,继续执行流程。这个表达式必须通过completionCondition子元素定义。

    
      
        ${nrOfCompletedInstances/nrOfInstances >= 0.6 }
      
    

  在这个例子里,会为assigneeList集合中的每个元素创建并行实例。当60%的任务完成时,其他的任务将被删除,流程继续运行。

二、集成扩展的任务类型

  Flowable还有很多集成扩展型的任务,这类任务并不常用,初学读者可以略过,在需要时再回头查阅。

2.1 手动任务

2.1.1 描述

  手动任务(manual task)用来定义在BPM引擎不能完成的任务。对于引擎来说,手动任务将当做一个空任务来处理,在流程执行到达手动任务时,自动继续执行流程。

2.1.2 图示

  手动任务用左上角有一个小“手”图标的标准BPMN 2.0任务(圆角矩形)表示。

2.1.3 XML表示

    

2.2 Java接收任务

2.2.1 描述

  接收任务(receive task),是等待特定消息到达的简单任务。当流程执行到达接收任务时,将保持等待状态,直到引擎接收到特定的消息,触发流程穿过接收任务继续执行。

2.2.2 图示

  接收任务用左上角有一个消息图标的标准BPMN 2.0任务(圆角矩形)表示。消息图标是白色的(对应的黑色消息图标代表发送的含义)。

2.2.3 XML表示

    

2.2.4 使用方法

  要使流程实例从接收任务的等待状态中继续执行,需要使用到达接收任务的执行id,调用runtimeService.signal(executionId)。下面的代码片段展示了如何操作:

    ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
    Execution execution = runtimeService.createExecutionQuery()
      .processInstanceId(pi.getId())
      .activityId("waitState")
      .singleResult();

    runtimeService.trigger(execution.getId());

2.3 Shell任务

2.3.1 描述

  Shell任务(Shell task)可以运行Shell脚本与命令。请注意Shell任务不是BPMN 2.0规范的“官方”任务(因此也没有专用图标)。

2.3.2 定义Shell任务

  Shell任务实现为特殊的服务任务,将服务任务的type定义为'shell'进行设置。

    

2.3.3 Shell任务参数

  Shell任务通过字段注入配置。这些参数的值可以使用EL表达式,将在流程执行运行时解析。可以设置下列参数:

参数 必填? 类型 描述 默认值
command String 要执行的Shell命令。
arg0-5 String 参数0至参数5
wait true/false 是否等待Shell进程终止。 true
redirectError true/false 是否将标准错误(standard error)并入标准输出(standard output)。 false
cleanEnv true/false 是否避免Shell进程继承当前环境。 false
outputVariable String 保存输出的变量名 不会记录输出。
errorCodeVariable String 保存结果错误码的变量名 不会记录错误码。
directory String Shell进程的默认目录 当前目录

2.3.4 使用示例

  下面的XML代码片段是使用Shell任务的例子。将会运行"cmd /c echo EchoTest" Shell脚本,等待其结束,并将其结果存入resultVar

    
      
        
        
        
        
        
        
      
    

2.4 补偿处理器

2.4.1 描述

  如果要使用一个活动补偿另一个活动的影响,可以将其声明为补偿处理器(compensation handler)。补偿处理器不在正常流程中执行,而只在流程抛出补偿事件时才会执行。

  补偿处理器不得有入口或出口顺序流。

  补偿处理器必须通过单向的连接,关联一个补偿边界事件。

2.4.2 图示

  如果一个活动是补偿处理器,则会在其下部中间显示补偿事件图标。下面摘录的流程图展示了一个带有补偿边界事件的服务任务,并关联至一个补偿处理器。请注意补偿处理器图标显示在"cancel hotel reservation(取消酒店预订)"服务任务的下部中间。

Flowable实战(八)BPMN2.0 任务_第2张图片

2.4.3 XML表示

  要将一个活动声明为补偿处理器,需要将isForCompensation属性设置为true:

    
    

2.5 集成类任务

  • Web Service任务:调用外部的Web Service资源。
  • 邮件任务:用于发送邮件。
  • Http任务:用于发出Http请求。
  • Camel任务:集成消息路由框架Camel。
  • Mule任务:集成企业系统总线框架Mule。

  上面的集成类任务在后续篇章中会详细介绍集成内容,此处了解即可。

三、小结

  本篇介绍了BPMN2.0规范下及Flowable自定义扩展的任务类型,Flowable提供的多种任务类型基本覆盖企业应用的需求。但还有不少问题需要我们关注,比如脚本任务中的脚本安全和多实例中的线程安全。

你可能感兴趣的:(工作流npmbpmn)