流程引擎的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()
- 選擇你點選的行,右擊.然後選擇[顯示]
- 開啟一個瀏覽器,輸入http://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().這會關閉所有初始化的流程引擎