Activiti工作流框架中任務流程元素詳解!使用任務元素進行任務的調度和執行
任務
用戶任務
用戶任務用來設置必須由人員完成的工作
當流程執行到用戶任務,會創建一個新任務,并把這個新任務加入到分配人或群組的任務列表中
用戶任務顯示成一個普通任務(圓角矩形),左上角有一個小用戶圖標
XML中的用戶任務定義:id屬性是必須的,name屬性是可選的:
用戶任務可以設置描述,添加documentation元素可以定義描述:
描述文本可以通過標準的java方法來獲取:
task.getDescription()
任務可以用一個字段來描述任務的持續時間
可以使用查詢API來對持續時間進行搜索,根據在時間之前或之后進行搜索
Activiti提供了一個節點擴展,在任務定義中設置一個表達式,這樣在任務創建時就可以設置初始持續時間
表達式應該是:
java.util.Date
java.util.String(ISO8601格式),ISO8601持續時間(比如PT50M)
null
在流程中使用上述格式輸入日期,或在前一個服務任務中計算一個時間.這里使用了持續時間,持續時間會基于當前時間進行計算,再通過給定的時間段累加: 使用"PT30M"作為持續時間,任務就會從現在開始持續30分鐘
任務的持續時間也可以通過TaskService修改,或在TaskListener中通過傳入的DelegateTask參數修改
用戶任務可以直接分配給一個用戶,通過humanPerformer元素定義
humanPerformer定義需要一個resourceAssignmentExpression來實際定義用戶.目前只支持formalExpressions
只有一個用戶可以作為任務的執行者分配用戶
在activiti中,用戶叫做執行者
擁有執行者的用戶不會出現在其他人的任務列表中,只能出現執行者的個人任務列表中
直接分配給用戶的任務可以通過TaskService獲取:
List
任務也可以加入到人員的候選任務列表中.需要使用potentialOwner元素
用法和humanPerformer元素類似**,需要指定表達式中的每個項目是人員還是群組**
使用potentialOwner元素定義的任務可以通過TaskService獲取:
List
這會獲取所有kermit為候選人的任務,表達式中包含user(kermit).這也會獲得所有分配包含kermit這個成員的群組(比如,group(management),前提是kermit是這個組的成員,并且使用了activiti的賬號組件).用戶所在的群組是在運行階段獲取的, 它們可以通過IdentityService進行管理
如果沒有顯式指定設置的是用戶還是群組,引擎會默認當做群組處理
下面的設置與使用group(accountancy)一樣:
當分配不復雜時,用戶和組的設置非常麻煩.為避免復雜性,可以使用用戶任務的自定義擴展
assignee屬性: 直接把用戶任務分配給指定用戶(和使用humanPerformer 效果完全一樣)
candidateUsers屬性: 為任務設置候選人(和使用potentialOwner效果完全一樣,不需要像使用potentialOwner通過user(kermit)聲明,這個屬性只能用于人員)
candidateGroups屬性: 為任務設置候選組(和使用potentialOwner效果完全一樣,不需要像使用potentialOwner通過group(management)聲明,這個屬性只能用于群組)
candidateUsers和candidateGroups可以同時設置在同一個用戶任務中
Activiti中雖然有賬號管理組件和IdentityService ,賬號組件不會檢測設置的用戶是否存在. Activiti允許與其他已存的賬戶管理方案集成
使用創建事件的任務- 來實現自定義的分配邏輯:
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
可以用來設置候選人和候選組:
方法返回類型只能為String(候選人) 或Collection < String >(候選組):
public class FakeLdapService { public String findManagerForEmployee(String employee) { return "Kermit The Frog"; } public List
腳本任務
腳本任務是一個自動節點
當流程到達腳本任務,會執行對應的腳本
腳本任務顯示為標準BPMN 2.0任務(圓角矩形),左上角有一個腳本小圖標
腳本任務定義需要指定script和scriptFormat
scriptFormat的值必須兼容JSR-223(java平臺的腳本語言).默認Javascript會包含在JDK中,不需要額外的依賴.如果要使用其他的腳本引擎,必須要是JSR-223引擎兼容的.還需要把對應的jar添加到classpath下, 并使用合適的名稱:activiti單元測試經常使用groovy
groovy腳本引擎放在groovy-all.jar中,在2.0版本之前,腳本引擎是groovy jar的一部分.使用需要添加依賴:
到達腳本任務的流程可以訪問的所有流程變量,都可以在腳本中使用
也可以在腳本中設置流程變量,直接調用execution.setVariable(“variableName”, variableValue)
默認,不會自動保存變量(activiti 5.12之前)
可以在腳本中自動保存任何變量,只要把scriptTask的autoStoreVariables屬性設置為true
最佳實踐是不要使用,而是顯式調用execution.setVariable()
參數默認為false: 如果沒有為腳本任務定義設置參數,所有聲明的變量將只存在于腳本執行的階段
在腳本中設置變量: 這些命名已經被占用,不能用作變量名- out, out:print, lang:import, context, elcontext.
腳本任務的返回值可以通過制定流程變量的名稱,分配給已存在或者一個新流程變量,需要使用腳本任務定義的’activiti:resultVariable’屬性
任何已存在的流程變量都會被腳本執行的結果覆蓋
如果沒有指定返回的變量名,腳本的返回值會被忽略
腳本的結果-表達式 #{echo} 的值會在腳本完成后,設置到myVar變量中
Java服務任務
Java服務任務用來調用外部Java類
Java服務任務顯示為圓角矩形,左上角有一個齒輪小圖標
聲明Java調用邏輯有四種方式:
實現JavaDelegate或者ActivityBehavior
執行解析代理對象的表達式
調用一個方法表達式
調用一個值表達式
執行一個在流程執行中調用的類,需要在activiti:class屬性中設置全類名:
使用表達式調用一個對象,對象必須遵循一些規則,并使用activiti:delegateExpression屬性進行創建:
delegateExpressionBean是一個實現了JavaDelegate接口的bean,定義在實例的spring容器中
要執行指定的UEL方法表達式, 需要使用activiti:expression:
方法printMessage()會調用名為printer對象的方法
為表達式中的方法傳遞參數:
調用名為printer對象上的方法printMessage.第一個參數是DelegateExecution, 在表達式環境中默認名稱為execution. 第二個參數傳遞的是當前流程的名為myVar的變量
要執行指定的UEL方法表達式, 需要使用activiti:expression:
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元素
ToUpperCaseFieldInjected類有一個text屬性,類型是org.activiti.engine.delegate.Expression. 調用text.getValue(execution) 時,會返回定義的字符串Hello World
可以使用長文字(比如,內嵌的email),使用activiti:string子元素:
可以使用表達式,實現在運行期動態解析注入的值
這些表達式可以使用流程變量或spring定義的bean.
服務任務中的java類實例會在所有流程實例中共享:
為了動態注入屬性的值,可以在org.activiti.engine.delegate.Expression中使用值和方法表達式
會使用傳遞給execute方法的DelegateExecution參數進行解析
示例:
注入表達式,并使用在當前傳入的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屬性來實現
指定的流程變量會被服務流程的返回結果覆蓋
如果沒有指定返回變量名,就會忽略返回結果
服務流程的返回值(在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"); } } }
構造參數是錯誤代碼,會被用來決定哪個錯誤處理器會來響應這個錯誤
這個機制只用于業務失敗,應該被流程定義中設置的邊界錯誤事件或錯誤事件子流程處理. 技術上的錯誤應該使用其他異常類型,通常不會在流程里處理
異常順序流:
內部實現類在一些異常發生時,讓流程進入其他路徑
這里的服務任務有兩個外出順序流:分別叫exception和no-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); } }
需要在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服務任務顯示效果一樣(圓角矩形,左上角有一個齒輪小圖標)
要使用WebService需要導入操作和類型,可以使用import標簽來指定WebService的WSDL
聲明告訴activiti導入WSDL定義,但沒有創建itemDefinition和message
假設想調用一個名為prettyPrint的方法,必須創建為請求和響應信息對應的message和itemDefinition
在申請服務任務之前,必須定義實際引用WebService的BPMN接口和操作
基本上,定義接口和必要的操作.對每個操作都會重用上面定義的信息作為輸入和輸出
示例:
定義了counter接口和prettyPrintCountOperation操作:
然后定義WebService任務,使用WebService實現,并引用WebService操作
每個WebService任務可以定義任務的輸入輸出IO規范
指定數據輸入關聯有兩種方式:
使用表達式
使用簡化方式
使用表達式指定數據輸入關聯: 需要定義來源和目的item,并指定每個item屬性之間的對應關系:
分配item的前綴和后綴
使用簡化方式指定數據輸入關聯: sourceRef元素是activiti的變量名,targetRef元素是item定義的一個屬性:
PrefixVariable變量的值分配給prefix屬性,把SuffixVariable變量的值分配給suffix屬性
指定數據輸出關聯有兩種方式:
使用表達式
使用簡化方式
使用表達式指定數據輸出關聯: 需要定義目的變量和來源表達式
方法和數據輸入關聯完全一樣
使用簡化方式指定數據輸出關聯: sourceRef元素是item定義的一個屬性,targetRef元素是activiti的變量名
方法和數據輸入關聯完全一樣
業務規則任務
業務規則任務用來同步執行一個或多個規則
Activiti使用drools規則引擎執行業務規則:
包含業務規則的.drl文件必須和流程定義一起發布
流程定義里包含了執行這些規則的業務規則任務
流程使用的所有.drl文件都必須打包在流程BAR文件里
如果想要自定義規則任務的實現: 想用不同方式使用drools,或者使用完全不同的規則引擎.你可以使用BusinessRuleTask上的class或表達式屬性
業務規則任務是一個圓角矩形,左上角使用一個表格小圖標進行顯示
要執行部署流程定義的BAR文件中的一個或多個業務規則,需要定義輸入和輸出變量:
對于輸入變量定義,可以使用逗號分隔的一些流程變量
輸出變量定義只包含一個變量名,會把執行業務規則后返回的對象保存到對應的流程變量中
注意: 結果變量會包含一個對象列表,如果沒有指定輸出變量名稱,默認會使用 org.activiti.engine.rules.OUTPUT
業務規則任務也可以配置成只執行部署的.drl文件中的一些規則.這時要設置逗號分隔的規則名,只會執行rule1和rule2:
定義哪些規則不用執行:除了rule1和rule2以外,所有部署到流程定義同一個BAR文件中的規則都會執行:
可以用一個選項修改BusinessRuleTask的實現:
BusinessRuleTask的功能和ServiceTask一樣,但是使用BusinessRuleTask的圖標來表示 在這里要執行業務規則
郵件任務
Activiti強化了業務流程,支持自動郵件任務:
可以發送郵件給一個或多個參與者,包括支持cc,bcc,HTML內容等等
郵件任務不是BPMN 2.0規范定義的官方任務,Activiti中郵件任務是用專門的服務任務實現的
Activiti引擎要通過支持SMTP功能的外部郵件服務器發送郵件
為了實際發送郵件,引擎穾知道如何訪問郵件服務器.下面的配置可以設置到activiti.cfg.xml配置文件中:
郵件任務是一個專用的服務任務, 這個服務任務的type設置為mail
郵件任務是通過屬性注入進行配置的.所有這些屬性都可以使用EL表達式,可以在流程執行中解析. 下面的屬性都可以設置:
郵件任務的使用示例:
As of ${now}, your order has been processed and shipped.
Kind regards,
TheCompany.