專案實踐之工作流引擎基本文件!Activiti工作流框架中流程引擎API和服務詳解

攻城獅Chova 發表於 2021-05-28

流程引擎的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().這會關閉所有初始化的流程引擎