項目實踐之工作流引擎基本文檔!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().這會關閉所有初始化的流程引擎