項目實踐之工作流引擎基本文檔!Activiti工作流框架中流程引擎API和服務詳解
流程引擎的API和服務
- 流程引擎API(ProcessEngine API)是與Activiti打交道的最常用方式
- Activiti從ProcessEngine開始.在ProcessEngine中,可以獲得很多包括工作流或者BPM方法的服務
- ProcessEngine和服務類都是線程安全的.可以在整個服務器中僅保持它們的一個引用就可以
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
ProcessEngines.getDefaultProcessEngine():
- 會在第一次調用時,初始化並創建一個流程引擎,以後再調用就會返回相同的流程引擎
- 使用對應的方法可以創建和關閉所有流程引擎:ProcessEngines.init()和ProcessEngines.destroy()
- ProcessEngines會掃描所有activiti.cfg.xml 和 activiti-context.xml 文件
- 對於activiti.cfg.xml文件,流程引擎會使用Activiti的經典方式構建:
- ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine()
- 對於activiti-context.xml文件,流程引擎會使用Spring方法構建:先創建一個Spring的環境,然後通過環境獲得流程引擎
- 所有服務都是無狀態的.這意味着可以在多節點集群環境下運行Activiti,每個節點都指向同一個數據庫,不用擔心哪個機器實際執行前端的調用.無論在哪裡執行服務都沒有問題
RepositoryService
- 負責靜態信息
- 是使用Activiti引擎時最先接觸的服務,提供了管理和控制發佈包和流程定義的操作
- 流程定義是BPMN 2.0流程的java實現.它包含了一個流程每個環節的結構和行為
- 發佈包是Activiti引擎的打包單位.一個發佈包可以包含多個BPMN 2.0 xml文件和其他資源
- 開發者可以自由選擇把任意資源包含到發佈包中
- 既可以把一個單獨的BPMN 2.0 xml文件放到發佈包里,也可以把整個流程和相關資源都放在一起
- 可以通過RepositoryService來部署這種發佈包.發佈一個發佈包,意味着把它上傳到引擎中,所有流程都會在保存進數據庫之前分析解析好
- 從這點來說,系統知道這個發佈包的存在,發佈包中包含的流程就已經可以啟動了
- RepositoryService可以查詢引擎中的發佈包和流程定義
- RepositoryService暫停或激活發佈包,對應全部和特定流程定義.暫停意味着它們不能再執行任何操作了,激活是對應的反向操作
- RepositoryService獲得多種資源,例如包含在發佈包里的文件,引擎自動生成的流程圖
- RepositoryService獲得流程定義的pojo版本,可以用來通過java解析流程,而不必通過xml
RuntimeService
- 負責啟動一個流程定義的新實例
- 流程定義定義了流程各個節點的結構和行為
- 流程實例就是這樣一個流程定義的實例
- 對每個流程定義來說,同一時間會有很多實例在執行
- RuntimeService可以用來獲取和保存流程變量,這些數據是特定於某個流程實例的,並會被很多流程中的節點使用
- Runtimeservice可以查詢流程實例和執行,執行對應BPMN 2.0中的'token',基本上執行指向流程實例當前在哪裡
- RuntimeService可以在流程實例等待外部觸發時使用,可以用來繼續流程實例.流程實例可以有很多暫停狀態,而服務提供了多種方法來'觸發'實例, 接受外部觸發後,流程實例就會繼續向下執行
TaskService
- 任務是由系統中真實人員執行的,它是Activiti這類BPMN引擎的核心功能之一, 所有與任務有關的功能都包含在TaskService中
- 在TaskService中,查詢分配給用戶或組的任務
- 在TaskService中,創建獨立運行任務,這些任務與流程實例無關
- 在TaskService中,手工設置任務的執行者,或者這些用戶通過何種方式與任務關聯
- 在TaskService中,認領並完成一個任務:
- 認領意味着一個人期望成為任務的執行者,即這個用戶會完成這個任務
- 完成意味着「做這個任務要求的事情」,通常來說會有很多種處理形式
IdentityService
- 可以管理,創建,更新,刪除,查詢..群組和用戶
- Activiti執行時並沒有對用戶進行檢查.任務可以分配給任何人,但是引擎不會校驗系統中是否存在這個用戶.這是Activiti引擎也可以使用外部服務:ldap,活動目錄...
HistoryService
- HistoryService提供了Activiti引擎的所有歷史數據
- 在執行流程時,引擎會根據配置保存很多數據:流程實例啟動時間,任務的參與者,完成任務的時間,每個流程實例的執行路徑..., 這個服務主要通過查詢功能來獲得這些數據
FormService
- FormService是一個可選服務,即使不使用它,Activiti也可以完美運行,不會損失任何功能
- FormService提供了啟動表單和任務表單兩個概念
- 啟動表單會在流程實例啟動之前展示給用戶
- 任務表單會在用戶完成任務時展示
- Activiti支持在BPMN 2.0流程定義中設置這些表單.這個服務以一種簡單的方式將數據暴露出來,是可選的,表單也不一定要嵌入到流程定義中
ManagementService
- 在使用Activiti的定製環境中基本上不會用到
- ManagementService可以查詢數據庫的表和表的元數據
- ManagementService提供了查詢和管理異步操作的功能
- Activiti的異步操作用途很多:定時器,異步操作,延遲暫停,激活..
異常策略
- Activiti中的基礎異常為org.activiti.engine.ActivitiException, 一個非檢查異常
- 這個異常可以在任何時候被API拋出,特定方法拋出的特定的異常
/**
* Called when the task is successfully executed.
* @param taskId the id of the task to complete, cannot be null.
* @throws ActivitiObjectNotFoundException when no task exists with the given id.
*/
void complete(String taskId);
當傳入一個不存在的任務的id時,就會拋出異常.taskId不能為null,如果傳入null,就會拋出ActivitiIllegalArgumentException
- 應該避免過多的異常繼承,子類只用於特定的場合
- 流程引擎和API調用的其他場合不使用子類異常,拋出一個普通的ActivitiExceptions
ActivitiWrongDbException: 當Activiti引擎發現數據庫版本號和引擎版本號不一致時拋出
ActivitiOptimisticLockingException: 對同一數據進行並發方法並出現樂觀鎖時拋出
ActivitiClassLoadingException: 當無法找到需要加載的類或在加載類時出現了錯誤-JavaDelegate,TaskListener
ActivitiObjectNotFoundException: 當請求或操作的對應不存在時拋出
ActivitiIllegalArgumentException: 這個異常表示調用Activiti API時傳入了一個非法的參數,可能是引擎配置中的非法值,或提供了一個非法值,或流程定義中使用的非法值
ActivitiTaskAlreadyClaimedException: 當任務已經被認領了,再調用taskService.claim(...)就會拋出
查詢 API
- 在Activiti流程引擎中查詢數據有兩種方式:
- 查詢API
- 原生查詢
- 查詢API: 查詢API提供了完全類型安全的API,可以自定義添加查詢條件和精確的排序條件,所有條件都以AND組合
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("kermit")
.processVariableValueEquals("orderId", "0815")
.orderByDueDate().asc()
.list();
- 原生查詢:
- 需要更強大的查詢時:使用OR條件或者能使用查詢API實現的條件.
- 可以編寫自己的SQL查詢. 返回類型由你使用的查詢對象決定,數據會映射到正確的對象上:任務,流程實例,執行..
- 查詢作用在數據庫上,必須使用數據庫中定義的表名和列名,要了解內部數據結構
- 使用原生查詢時,表名可以通過API獲得,可以盡量減少對數據庫的依賴
List<Task> tasks = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
.parameter("taskName", "gonzoTask")
.list();
long count = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
+ managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
.count();
表達式
- Activiti使用UEL處理表達式.UEL即統一表達式語言, 是EE6規範的一部分.為了在所有運行環境都支持最新UEL的所有功能,使用JUEL的修改版本
- 表達式可以用在很多場景下:
- Java服務任務
- 執行監聽器
- 任務監聽器
- 條件流
- 雖然有兩重表達式:值表達式和方法表達式, Activiti進行了抽象,所以兩者可以同樣使用在需要表達式的場景中
- Value expression: 解析為值,默認
${myVar}
${myBean.myProperty}
所有流程變量都可以使用,所有spring bean(spring環境中)也可以使用在表達式中
- Method expression: 調用一個方法,使用或不使用參數
${printer.print()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}
當調用一個無參數的方法時,記得在方法名後添加空的括號,以區分值表達式
傳遞的參數可以是字符串也可以是表達式,它們會被自動解析
- 這些表達式支持解析原始類型:
- bean
- list
- 數組
- map
- 包括比較
- 在流程實例中,表達式中可以使用一些默認對象:
- execution: DelegateExecution,提供外出執行的額外信息
- task: DelegateTask,提供當前任務的額外信息 ,只對任務監聽器的表達式有效
- authenticatedUserId: 當前登錄的用戶id.如果沒有用戶登錄,這個變量就不可用
單元測試
- 業務流程是軟件項目的一部分,它也應該和普通的業務流程一樣進行測試:使用單元測試
- 因為Activiti是一個嵌入式的java引擎,所以為業務流程編寫單元測試和寫普通單元測試完全一樣
- Activiti支持JUnit 3和4進行單元測試
- 使用JUnit 3時, 必須集成org.activiti.engine.test.ActivitiTestCase. 它通過保護的成員變量提供ProcessEngine和服務,
- 在測試的setup()中,默認會使用classpath下的activiti.cfg.xml初始化流程引擎
- 要使用不同的配置文件,可以重寫getConfigurationResource() 方法
- 如果配置文件相同的話,對應的流程引擎會被靜態緩存,就可以用於多個單元測試
- 繼承了ActivitiTestCase, 可以在測試方法上使用org.activiti.engine.test.Deployment註解.測試執行前,與測試類在同一個包下的,格式為testClassName.testMethod.bpmn20.xml的資源文件,會被部署.測試結束後,發佈包也會被刪除,包括所有相關的流程實例,任務…Deployment註解也可以直接設置資源的位置
public class MyBusinessProcessTest extends ActivitiTestCase {
@Deployment
public void testSimpleProcess() {
runtimeService.startProcessInstanceByKey("simpleProcess");
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
- 要想在使用JUnit 4編寫單元測試時獲得同樣的功能
- 可以使用org.activiti.engine.test.ActivitiRule. 通過它,可以通過getter方法獲得流程引擎和各種服務
- 使用這個Rule也會啟用org.activiti.engine.test.Deployment註解
- 它會在classpath下查找默認的配置文件,如果配置文件相同的話,對應的流程引擎會被靜態緩存,就可以用於多個單元測試
public class MyBusinessProcessTest {
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment
public void ruleUsageExample() {
RuntimeService runtimeService = activitiRule.getRuntimeService();
runtimeService.startProcessInstanceByKey("ruleUsage");
TaskService taskService = activitiRule.getTaskService();
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
調試單元測試
- 使用內存數據庫H2進行單元測試,在調試環境監視Activiti的數據庫:
- 在單元測試里設置了一個斷點:
- 用調試模式運行單元測試,右擊單元測試,選擇[運行為]和[單元測試],測試會停在我們的斷點上, 然後我們就可以監視測試的變量,它們顯示在調試面板里
- 要監視Activiti的數據,打開[顯示]窗口(如果找不到,打開[窗口]-[顯示視圖]-[其他],選擇[顯示]並點擊[代碼已完成],org.h2.tools.Server.createWebServer(“-web”).start()
- 選擇你點擊的行,右擊.然後選擇[顯示]
- 打開一個瀏覽器,輸入//localhost:8082, 輸入內存數據庫的JDBC URL(默認為jdbc:h2:mem:activiti),點擊連接按鈕
- 可以看到Activiti的數據,通過它們可以了解單元測試時,如何以及為什麼這樣運行的
Web中的流程引擎
- ProcessEngine是線程安全的,可以在多線程下共享
- 在web應用中, 意味着可以在容器啟動時創建流程引擎, 在容器關閉時關閉流程引擎
- 編寫一個ServletContextListener 在普通的Servlet環境下初始化和銷毀流程引擎:
public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ProcessEngines.init();
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ProcessEngines.destroy();
}
}
contextInitialized方法會執行ProcessEngines.init() 這會查找classpath下的activiti.cfg.xml文件,根據配置文件創建一個ProcessEngine(比如,多個jar中都包含配置文件)如果classpath中包含多個配置文件,確認它們有不同的名字
- 需要使用流程引擎時,可以通過
ProcessEngines.getDefaultProcessEngine()
或
ProcessEngines.getProcessEngine("myName");
- ContextListener中的contextDestroyed方法會執行ProcessEngines.destroy().這會關閉所有初始化的流程引擎