任務
用戶任務:
用戶任務,用來對那些需要人參與完成的工作進行建模。當流程執行到這樣的用戶任務時,會在被分配到該任務的用戶或用戶組的任務列表中創建新的任務。
用戶任務中可以包含描述。事實上,任何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); }