Activiti中工作流的生命週期詳細解析!一個BPMN流程示例帶你認識專案中流程的生命週期

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

BPMN 2.0介紹

  • 業務流程模型註解(BusinessProcess Modeling Notation - BPMN)是業務流程模型的一種標準圖形註解.這個標準是由物件管理組(Object Management Group - OMG)維護的
  • BPMN規範的2.0版本允許新增精確的技術細節在BPMN的圖形和元素中,同時制定BPMN元素的執行語法.通過使用XML語言來指定業務流程的可執行語法,BPMN規範已經演變為業務流程的語言,可以執行在任何相容BPMN2的流程引擎中,同時依然可以使用強大的圖形註解
  • 簡單來說,BPMN即圖示與標籤的結合
    在這裡插入圖片描述在這裡插入圖片描述在這裡插入圖片描述
    在這裡插入圖片描述

定義一個流程

  • 建立一個新的XML檔案並命名,確認檔案字尾為 .bpmn20.xml.bpmn, 否則引擎無法釋出
  • BPMN 2.0根節點是definitions節點. 這個元素中,可以定義多個流程定義(不過建議每個檔案只包含一個流程定義, 可以簡化開發過程中的維護難度)
  • 一個空的流程定義如下所示:注意definitions元素最少也要包含xmlns和 targetNamespace的宣告
    • targetNamespace可以是任意值,它用來對流程例項進行分類
<definitions
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:activiti="http://activiti.org/bpmn"
  targetNamespace="Examples">

  <process id="myProcess" name="My First Process">
    ..
  </process>

</definitions>
  • 可以選擇新增線上的BPMN 2.0格式位置:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
                    http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd
  • process元素有兩個屬性:
  • id: 這個屬性是必須的,對應著Activiti ProcessDefinition物件的key屬性.id可以用來啟動流程定義的流程例項,通過RuntimeServicestartProcessInstanceByKey方法
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");

注意: 它和startProcessInstanceById方法不同:這個方法期望使用Activiti引擎在釋出時自動生成的id.可以通過呼叫processDefinition.getId() 方法獲得這個值,生成的id的格式為 key:version, 最大長度限制為64個字元, 如果在啟動時丟擲了一個ActivitiException: 說明生成的id太長了,需要限制流程的key的長度

  • name: 這個屬性是可選的, 對應ProcessDefinitionname屬性.引擎自己不會使用這個屬性,是用來在使用者介面顯示便於閱讀的名稱

BPMN流程示例前提

  • 已經安裝Activiti並且能夠執行Activiti Demo
  • 使用了獨立執行的H2伺服器
  • 修改db.properties,設定其中的jdbc.url=jdbc:h2:tcp://localhost/activiti,然後啟動獨立伺服器

目標

  • 學習Activiti和一些基本的BPMN 2.0概念
  • 最終結果是一個簡單的Java SE程式可以釋出流程定義,通過Activiti引擎API操作流程
  • 使用一些Activiti相關的工具,構建自己的業務流程web應用

用例

  • 每個月都要給公司領導一個金融報表,由會計部門負責
  • 當報表完成時,一個上級領導需要審批文件,然後才能發給所有領導

流程圖

  • 流程的圖形化BPMN 2.0標記:
    在這裡插入圖片描述
    空開始事件(左側圓圈),後面是兩個使用者任務:製作月度財報和驗證月度財報,最後是空結束事件(右側粗線圓圈)

XML內容

  • 在業務流程的XML中很容易找到流程的主要元素:
    • (空)開始事件是流程的入口
    • 使用者任務是流程中與操作者相關的任務宣告:
      • 第一個任務分配給accountancy組
      • 第二個任務分配給management組
    • 當流程達到空結束事件就會結束
    • 這些元素都使用連線連線,這些連線擁有sourcetarget屬性,定義了連線的方向
<definitions id="definitions"
  targetNamespace="http://activiti.org/bpmn20"
  xmlns:activiti="http://activiti.org/bpmn"
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

        <process id="financialReport" name="Monthly financial report reminder process">

          <startEvent id="theStart" />

          <sequenceFlow id='flow1' sourceRef='theStart' targetRef='writeReportTask' />

          <userTask id="writeReportTask" name="Write monthly financial report" >
            <documentation>
              Write monthly financial report for publication to shareholders.
            </documentation>
            <potentialOwner>
              <resourceAssignmentExpression>
                <formalExpression>accountancy</formalExpression>
              </resourceAssignmentExpression>
            </potentialOwner>
          </userTask>

          <sequenceFlow id='flow2' sourceRef='writeReportTask' targetRef='verifyReportTask' />

          <userTask id="verifyReportTask" name="Verify monthly financial report" >
            <documentation>
              Verify monthly financial report composed by the accountancy department.
              This financial report is going to be sent to all the company shareholders.
            </documentation>
            <potentialOwner>
              <resourceAssignmentExpression>
                <formalExpression>management</formalExpression>
              </resourceAssignmentExpression>
            </potentialOwner>
          </userTask>

          <sequenceFlow id='flow3' sourceRef='verifyReportTask' targetRef='theEnd' />

          <endEvent id="theEnd" />

        </process>

</definitions>

啟動一個流程例項

  • 建立好業務流程的流程定義,就可以建立流程例項
  • 一個流程例項對應了特定月度財報的建立和審批,所有流程例項都共享同一個流程定義
  • 為了使用流程定義建立流程例項,首先要釋出業務流程:
    • 流程定義會儲存到持久化的資料儲存裡,是為Activiti引擎特別配置的.所以部署好業務流程,在引擎重啟後還能找到流程定義
    • BPMN 2.0流程檔案會解析成記憶體物件模型, 可以通過Activiti API操作
  • 通過下面的API釋出流程,所有與Activiti引擎的互動都是通過services
Deployment deployment = repositoryService.createDeployment()
  .addClasspathResource("FinancialReportProcess.bpmn20.xml")
  .deploy();
  • 啟動一個新流程例項,使用我們定義在流程定義裡的id(對應XML檔案中的process元素).注意這裡的id對於Activiti來說,應該叫做key,一般在流程模型中使用的ID,在Activiti中都是Key:比如任務ID
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");
  • 這樣建立一個流程例項:
    • 首先進入開始事件
    • 開始事件之後,它會沿著所有的外出連線執行,到達第一個任務(“製作月度財報”)
    • Activiti會把一個任務儲存到資料庫裡.這時,分配到這個任務的使用者或群組會被解析,也會儲存到資料庫裡
    • 需要注意,Activiti引擎會繼續執行流程的環節,除非遇到一個 等待狀態:比如使用者任務
    • 在等待狀態下,當前的流程例項的狀態會儲存到資料庫中.直到使用者決定完成任務才能改變這個狀態
    • 這時,引擎會繼續執行,直到遇到下一個等待狀態,或流程結束
    • 如果中間引擎重啟或崩潰,流程狀態也會安全的儲存在資料庫裡
  • 任務建立之後,startProcessInstanceByKey會在到達使用者任務這個等待狀態之後才會返回.這時,任務分配給了一個組,這意味著這個組是執行這個任務的候選組
  • 現在將所有東西都放在一起,來建立一個簡單的java程式:
    • 建立一個Java專案,把Activiti的jar和依賴放到classpath下:這些都可以在Activiti釋出包的libs目錄下找到
    • 在呼叫Activiti服務之前,我們必須構造一個ProcessEngine,可以讓我們訪問服務
    • 這裡我們使用[單獨執行]的配置,這會使用demo安裝時的資料庫來構建ProcessEngine
public static void main(String[] args) {

  // Create Activiti process engine
  ProcessEngine processEngine = ProcessEngineConfiguration
    .createStandaloneProcessEngineConfiguration()
    .buildProcessEngine();

  // Get Activiti services
  RepositoryService repositoryService = processEngine.getRepositoryService();
  RuntimeService runtimeService = processEngine.getRuntimeService();

  // Deploy the process definition
  repositoryService.createDeployment()
    .addClasspathResource("FinancialReportProcess.bpmn20.xml")
    .deploy();

  // Start a process instance
  runtimeService.startProcessInstanceByKey("financialReport");
}

任務列表

  • 可以通過TaskService來獲得任務,新增以下邏輯:
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();
  • 注意傳入的使用者必須是accountancy組的一個成員,要和流程定義中相對應:
<potentialOwner>
  <resourceAssignmentExpression>
    <formalExpression>accountancy</formalExpression>
  </resourceAssignmentExpression>
</potentialOwner>
  • 也可以使用群組名稱,通過任務查詢API來獲得相關的結果.在程式碼中新增如下邏輯:
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
  • 因為配置的ProcessEngine使用了與demo相同的資料,可以登入到Activiti Explorer.預設,accountancy(會計)組裡沒有任何人:
    • 登入
    • 點選組
    • 建立一個新組
    • 點選使用者
    • 把組分配給fozzie
    • 使用fozzie/fozzie登入
  • 就可以啟動我們的業務流程了,選擇Processes頁,在[月度財報]的[操作]列點選[啟動流程]
    在這裡插入圖片描述
  • 流程會執行到第一個使用者任務.因為我們以kermit登入,在啟動流程例項之後,就可以看到有了一個新的待領任務.選擇任務頁來檢視這條新任務.注意即使流程被其他人啟動,任務還是會被會計組裡的所有人作為一個候選任務看到
    -

領取任務

  • 現在一個會計要認領這個任務
  • 認領以後,這個使用者就會成為任務的執行人,任務會從會計組的其他成員的任務列表中消失.認領任務的程式碼:
taskService.claim(task.getId(), "fozzie");
  • 任務會進入認領任務人的個人任務列表:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
  • 在Activiti Explorer UI中,點選認領按鈕,會執行相同的操作.任務會移動到登入使用者的個人任務列表.你也會看到任務的執行人已經變成當前登陸的使用者:
    在這裡插入圖片描述

完成任務

  • 現在會計可以開始進行財報的工作
  • 報告完成後,他可以完成任務,意味著任務所需的所有工作都完成
taskService.complete(task.getId());
  • 對於Activiti引擎:
    • 需要一個外部資訊來讓流程例項繼續執行
    • 任務會把自己從執行庫中刪除
    • 流程會沿著單獨一個外出連線執行,移動到第二個任務(審批報告)
    • 與第一個任務相同的機制會使用到第二個任務上,不同的是任務是分配給management組
  • 在demo中:
    • 完成任務是通過點選任務列表中的完成按鈕
    • 因為Fozzie不是會計,我們先從Activiti Explorer登出
    • 然後使用kermit登陸(經理),第二個任務會進入未分配任務列表

結束流程

  • 審批任務像之前一樣查詢和領取.
  • 完成第二個任務會讓流程執行到結束事件,就會結束流程例項
  • 流程例項和所有相關的執行資料都會從資料庫中刪除
  • 登入Activiti Explorer就可以進行驗證,可以看到儲存流程執行資料的表中已經沒有資料:
    在這裡插入圖片描述
  • 可以使用historyService判斷流程是否已經結束:
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());

原始碼

  • 考慮到你可能會在Activiti Explorer UI中啟動一些流程例項,這樣,它會獲得多個任務,而不是一個,所以程式碼可以一直正常執行:
public class TenMinuteTutorial {

  public static void main(String[] args) {

    // Create Activiti process engine
    ProcessEngine processEngine = ProcessEngineConfiguration
      .createStandaloneProcessEngineConfiguration()
      .buildProcessEngine();

    // Get Activiti services
    RepositoryService repositoryService = processEngine.getRepositoryService();
    RuntimeService runtimeService = processEngine.getRuntimeService();

    // Deploy the process definition
    repositoryService.createDeployment()
      .addClasspathResource("FinancialReportProcess.bpmn20.xml")
      .deploy();

    // Start a process instance
    String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();

    // Get the first task
    TaskService taskService = processEngine.getTaskService();
    List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
    for (Task task : tasks) {
      System.out.println("Following task is available for accountancy group: " + task.getName());

      // claim it
      taskService.claim(task.getId(), "fozzie");
    }

    // Verify Fozzie can now retrieve the task
    tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
    for (Task task : tasks) {
      System.out.println("Task for fozzie: " + task.getName());

      // Complete the task
      taskService.complete(task.getId());
    }

    System.out.println("Number of tasks for fozzie: "
            + taskService.createTaskQuery().taskAssignee("fozzie").count());

    // Retrieve and claim the second task
    tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
    for (Task task : tasks) {
      System.out.println("Following task is available for accountancy group: " + task.getName());
      taskService.claim(task.getId(), "kermit");
    }

    // Completing the second task ends the process
    for (Task task : tasks) {
      taskService.complete(task.getId());
    }

    // verify that the process is actually finished
    HistoryService historyService = processEngine.getHistoryService();
    HistoricProcessInstance historicProcessInstance =
      historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
    System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
  }

}

總結

  • 可以通過Activiti中的BPMN 2.0結構,對業務流程進行以下方面的:
    • 定義閘道器來實現決策環節: 經理可以駁回財報,重新給會計建立一個任務
    • 考慮使用變數: 可以儲存或引用報告,把它顯示到表單中
    • 在流程最後加入服務任務: 把報告發給每個領導

相關文章