任务
用户任务:
用户任务,用来对那些需要人参与完成的工作进行建模。当流程执行到这样的用户任务时,会在被分配到该任务的用户或用户组的任务列表中创建新的任务。
用户任务中可以包含描述。事实上,任何BPMN 2.0中的元素都可以有描述。描述是通过添加documentation元素来定义。
<userTaskid="theTask"name="Schedule meeting"> <documentation> Schedule an engineering meeting for next week with the new hire. </documentation>
描述文本可以以标准java 的方式从任务中获得:task.getDescription()
到期时间
每个任务都含有一个表明该任务到期时间的字段。Query API可以用来查询在某个时间点的前或后任务是否过期。
有个Activity的扩展,允许在任务定义中指定一个表达式来设置在创建任务时任务初始的超期时间。该表达式结果必须是
java.util.Date 或null。例如,你可以使用由流程中之前表单输入或在之前Service Task计算出来的日期。
<userTaskid="theTask"name="Important task"activiti:dueDate="${dateVariable}"/>
用户分配
用户任务可以直接分配给用户。这是通过定义humanPerformer子元素来完成的。
<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。一定要注意需要对formal表达式中的每个元素进行定义以指名是用户还是用户组(流程引擎是猜测不到的)。
<userTask id='theTask'name='important task'> <potentialOwner> <resourceAssignmentExpression> <formalExpression>user(kermit), group(management)</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask>
使用potential owner定义的任务可以按照如下方式获取(或类似于在有代理者任务中使用Ta skQuery):
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");
这将获得所有kermit作为候选用户的任务,也就是,formal表达式包含的user(kermit)。这也会获得所有分配给kermit所在组(例如,group(management),如果kermit是那个组的成员,并且使用了identity 组件)的任务。用户组是在运行时解析的,并且用户组可以通过IdentityService 管理。如果不给文本字符串指定是用户还是用户组,流程引擎默认认为是用户组
Activiti对于任务分配的扩展
用户和用户组的分配在那些分配并不复杂的情况下显然是很麻烦的。为了避免这种复杂性,用户任务上的自定义扩展就变得可能了。
assignee属性:这个自定义扩展允许将用户任务直接分配给用户。
<userTask id="theTask" name="my task" activiti:assignee="kermit"/> 这与上面使用humanPerformer效果是一样的。
candidateUsers属性:这个自定义扩展可以使用户成为任务的候选者。
<userTaskid="theTask"name="my task"activiti:candidateUsers="kermit, gonzo"/> 这与上面使用potentialOwner效果是一样的。注意不要求使用像在potential owner中使用的user(kermit)声明,因为该属性只用于用户。
candidateGroups属性:这个自定义扩展允许为任务定义一组候选者。
<userTask id="theTask"name="my task"activiti:candidateGroups="management, accountancy"/> 这与上面使用potentialOwner效果是一样的。注意不要求使用像在potential owner中使用的group(management)声明,因为该属性只用于组。
candidateUsers和candidateGroups可以定义在同一用户任务上。
使用Spring时可能会使用到上面章节中介绍的自定义分配属性,并利用带表达式的任务监听器监听create事件将处理委托给Spring的bean。下面的例子中,代理人是通过调用ldapServiceSpring bean中的方法findManagerOfEmployee来设置
的。传递的emp参数,是个流程变量
<userTaskid="task"name="My Task"activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/> 这对于候选用户和候选组的情况也是类似的: <userTaskid="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"); }
}
脚本任务
脚本任务是自动的活动。当流程执行到脚本任务时,执行相应的脚本。
通过指定script和scriptFormat来定义脚本任务。
<scriptTask id="theScriptTask"name="Execute script"scriptFormat="groovy"> <script> sum = 0 for ( i in inputArray ) { sum += i } </script> </scriptTask>
scriptFormat属性的值必须是JSR-223(Java平台脚本,scripting for the Java plateform)所兼容的名称。默认Groovy jar随Activiti的分发文件被一起分发了。如果你想要使用其他(JSR-223兼容的)脚本引擎,只要将相应的jar 添加到类路径下,然后使用恰当的名称就行了。
脚本中的变量
所有那些进入脚本任务的执行路径能访问到的流程变量都可以在脚本中使用。该例子中,脚本变量’inputArray’实际上是个(整形数组类型的)流程变量。
<script> sum = 0 for ( i in inputArray ) { sum += i } </script>
也可以使用赋值语句在脚本中设置流程变量。在上面的例子中,在脚本任务执行完成后’sum’变量将作为流程变量存储起来。要避免这种行为,可以使用本地脚本变量。在Groovy中,需要使用关键字’def’:’def sum = 0’。那样,流程变量就不会被存储了。
另一种方法是使用当前的execution来设置变量,它是被称为’execution’的保留变量。 <script> def scriptVar = "test123" execution.setVariable("myVar", scriptVar) </script>
注意:以下名称被保留,不能用来做为变量的名称:out、out:print、lang:import、context、elcontext。
脚本的结果
通过给脚本任务定义的’activity:resultVariable’属性指定一个字符串来表示流程变量名,就可以将脚本任务的返回值分配给一个现有的或新的流程变量。流程变量现值会被脚本执行结果值所重写。不指定结果变量名时,会忽略脚本的结果值。
<scriptTask id="theScriptTask"name="Execute script"scriptFormat="juel"activiti:resultVariable="myVar"> <script>#{echo}</script> </scriptTask>
上面的例子中,在脚本执行完成后,脚本执行的结果(表达式’#{echo}’的结果值)被设置到名称为’myVar’的流程变量中。
Java服务任务
Java服务任务用来调用外部Java类。
有4种方式来声明如何调用Java的逻辑:
指定实现了JavaDelegate或ActivitiBehavior的类
计算结果为代理对象的表达式
调用方法表达式
计算值表达式
要指定在流程执行期间被调用的类,需要使用’activity:class’属性来提供完全限定的类名。 <serviceTask id="javaService" name="My Java Service Task" activiti:class="org.activiti.MyJavaDelegate"/>
也可以使用解析结果为对象的表达式。这个对象必须遵循与使用activiti:class属性创建对象时一样的规则(见下文)。 <serviceTask id="serviceTask" activiti:delegateExpression="${delegateExpressionBean}"/> 这里,delegateExpressionBean是一个定义在Spring容器中的实现了JavaDelegate接口的bean。
使用属性activiti:expression指定一个会被计算的UEL方法表达式。 <serviceTask id="javaService" name="My Java Service Task" activiti:expression="#{printer.printMessage()}"/> 会调用printer对象上的方法printMessage(不带参数)。
也可以向表达式的方法中传递参数。 <serviceTask id="javaService" name="My Java Service Task" activiti:expression="#{printer.printMessage(execution, myVar)}"/> 会调用printer对象上的方法printMessage。传递的第一个参数是DelegateException,其在表达式上下文默认以execution来使用。传递的第二个参数是当前execution中名为myVar变量的值。
使用属性activiti:expression来指定一个会被计算的UEL值表达式。 <serviceTask id="javaService" name="My Java Service Task" activiti:expression="#{split.ready}"/> 会调用名称为split的bean上属性ready的getter方法,getReady(不带参数)。命名对象是在流程执行中的流程变量和( 如果适用)Spring上下文中被解析的。
要实现一个可以在流程执行期间中调用的类,该类需要实现org.activiti.engine.delegate.JavaDelegate接口,在execute方法中提供必要的逻辑。当流程执行到此步,会执行定义在该方法中的逻辑,然后以BPMN 2.0的默认方式离开该活动。
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上的java类的一个实例。所有流程实例共享同一个用于调用execute(DelegateExecution)的类实例。这意味着,该类中一定不要使用成员变量,并且必须是线程安全的,因为可能会在不同的线程中同时执行该方法。
流程定义中引用的类(即使用activiti:class)在部署时不会被实例化。只有当流程第一次执行到使用到该类的时候,才创建该类的实例。如果找不到该类,会抛出ActivitiException。这是由于部署的环境(特别是类路径)与实际运行的环境往往是不同的。比如,在使用ant或在Activiti Explorer中使用业务归档文件来部署流程时,类路径不包含参照的类。
[内部的:非公布的实现类] 可能会提供一个实现了org.activiti.engine.impl.pvm.delegate.ActivityBehavior接口的类。接下来实现类就能够访问更强大的ActivityExecution了,比如,它可以影响流程的控制流。但要注意这个不是一个很好的做法,应该尽量避免这样做。所以,对于高级的用例,如果你真正知道你要做什么,建议使用接口ActivityBehavior。
字段的注入
可以向代理类的字段注入值。支持以下注入形式:
固定字符串值
表达式
如果可以的话,是通过遵循Java Bean的命名规范的代理类中(例如,字段firstName的setter方法是setFirstName(…))的public setter方法将值注入的。如果字段不存在可用的setter方法,将设置代理类的private成员变量。在一些情况下,SecurityManagers是不允许修改private字段的,所以给你要进行注入的字段公布public setter方法会更加安全。不管流程定义中值声明成什么类型,注入目标类上的setter或private字段的类型必须是org.activiti.engine.delegate.Expression。
下面的代码片段展示了如何向字段中注入常量。使用’class’属性进行字段注入。注意,在实际的字段注入声明的前面,需要声明’extensionElements’ XML元素,这是BPMN 2.0 XML模式的要求。
<serviceTask id="javaService" name="Java service invocation" activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected"> <extensionElements> <activiti:fieldname="text"stringValue="Hello World"/> </extensionElements> </serviceTask>
类ToUpperCaseFieldInjected有一个类型为org.activiti.engine.delegate.Expressiontext的text字段。当调用text.getValue(execution)时,返回配置的’Hello world’字符串。
或者,对于长文本(例如,一行e-mail),可以使用子元素’activiti:string’:
<serviceTask id="javaService" name="Java service invocation" activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected"> <extensionElements> <activiti:fieldname="text"> <activiti:string> Hello World </activiti:string> </activiti:field> </extensionElements> </serviceTask>
要注入运行时动态解析的值,可以使用表达式。表达式中可以使用流程变量,或Spring定义的bean(如果使用了Spring)。如服务任务实现中所描述的,所有流程实例共享一个定义在服务任务中的Java类实例。要想达到字段上值的动态注入,可以将值表达式和方法表达式注入到org.activiti.engine.delegate.Expression,它会使用execute方法中传进来的DelegateExecution对org.activiti.engine.delegate.Expression进行运算/调用。
<serviceTask id="javaService"name="Java service invocation" activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected"> <extensionElements> <activiti:fieldname="text1"> <activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression> </activiti:field> <activiti:fieldname="text2"> <activiti:expression> Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name} </activiti:expression> </activiti:field> </extensionElements> </serviceTask>
下面的示例类使用了被注入的表达式,并使用了当前DelegateExecution对这些表达式进行解析。完整代码以及测试可以在org.activiti.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection中找到。
publicclass 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()); } }
或者,你也可以以属性的方式设置表达式,而不是使用子元素,这样可以使XML显得不那么冗长。
<activiti:fieldname="text1"expression="${genderBean.getGenderString(gender)}"/> <activiti:fieldname="text1"expression="Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}"/>
由于该java类的实例是可重用的,所以注入只在serviceTask第一次被调用时发生。一旦在代码中修改过了这些字段,其值不会再被重新注入了,因此你应该把它们看作是不可变的,并且对它们不要做任何的修改。
服务任务的结果
通过为服务任务定义的’activiti:resultVariable’属性指定一个字符串表示的流程变量名,可以将服务执行(服务任务仅使用了表达式)的返回值分配给一个现有的或一个新的流程变量。服务执行的返回值会重写流程变量的当前值。如果没有指定结果变量名,服务执行的结果值会被忽略。
<serviceTask id="aMethodExpressionServiceTask" activiti:expression="#{myService.doSomething()}" activiti:resultVariable="myVar"/>
上面示例中,服务执行完成后,服务执行的结果(调用流程变量或Spring bean中名为’myService’的对象上方法’doSomething()’的返回值)被设置到了叫’myVar’的流程变量。
处理异常
在执行自定义的逻辑时,常常需要捕获某种异常。一个常见的用例是一旦某条路径上发生异常,将流程导向另一条路径。下面的例子展示了这是如何做到的。
<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"/>
这里,服务任务有两条输出流,分别是exception和no-exception。一旦发生异常,顺序流的id 属性用来引导顺序流。
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); }