Activiti 7 原始碼學習

2022-09-22 18:01:10

1.  啟動分析

原始碼版本是 7.1.0.M6

首先從 ProcessEngineAutoConfiguration 開始

ProcessEngineAutoConfiguration 是activiti-spring-boot-starter 7.1.0.M6自動設定的入口類,在這裡主要看 SpringProcessEngineConfiguration

主要是設定了自動部署

最最最重要的是 buildProcessEngine() 方法,將來根據設定構建 ProcessEngine 的時候它就派上用場了

ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault().buildProcessEngine();

下面重點看一下如何構建 ProcessEngine

在父類別(ProcessEngineConfigurationImpl)的 buildProcessEngine() 裡呼叫了一個非常重要的方法 init()

可以看到在init()方法裡初始化了很多元件,接下來挑幾個來重點看一下

initAgendaFactory()

initCommandContextFactory()

new了一個CommandContextFactory,重要的是CommandContextFactory中持有當前processEngineConfiguration的參照

initCommandExecutors()

初始化攔截器interceptor要重點說下,這裡構造了一個攔截器鏈,而且攔截器鏈的最後是CommandInvoker,並且將第一個攔截器放到CommandExecutor裡面,姑且先記下,後面有用到

initServices()

initBehaviorFactory()

在初始化各個元件以後,new了一個ProcessEngineImpl,並將當前的設定 ProcessEngineConfigurationImpl 賦值給它

因此,這個代表流程引擎的ProcessEngine就變成了一個基礎的入口類,它提供了對工作流操作的所有服務的存取。

2.  CommandContextInterceptor

在預設的攔截器中有一個 CommandContextInterceptor 特別重要

在其execute()方法中設定上下文CommandContext

  1. 查詢棧頂部的元素,如果為空,則新new一個CommandContext,如果不為空,則將獲取到的CommandContext的熟悉reused設為true
  2. 將剛才獲取到的CommandContext壓入棧中
  3. 將當前processEngineConfiguration壓入另一個棧中
  4. 呼叫下一個攔截器

也就是說,每個命令在經過CommandContextInterceptor後都有了自己的上下文

那麼,CommandContext中到底有什麼呢?繼續看

CommandContext中有命令(Command),還有agenda(ActivitiEngineAgenda

3.  Command

Activiti這裡採用命令模式,將操作以及與之相關的資訊都封裝成命令。

下面以完成任務為例來看一下命令是如何被完成的

前面初始化services的時候說過了,會將建立好的CommandExecutor設定到各個Service中,因此TaskServicecommandExecutor的出現就不足為奇了

可以看到,完成任務的時候,直接new了一個CompleteTaskCmd,然後交由commandExecutor去執行

CompleteTaskCmd主要有兩個屬性:任務ID 和 流程變數

既然命令交給了CommandExecutor執行,那麼接下來看下它是如何執行的。

在前面 initCommandExecutor() 的時候我們指定,它其實是 CommandExecutorImpl,並且我們還知道它持有預設的命令設定,以及攔截器鏈中的第一個攔截器

從程式碼中可以看到 CommandExecutorImpl#execute() 直接從攔截器鏈中的第一個攔截器開始往後依次呼叫。可以預見到,它肯定會經過CommandContextInterceptor,於是在當前請求執行緒的區域性變數中就會有一個棧(Stack),在棧的頂部放了一個CommandContext,在這個CommandContext中有待執行的Command,有processEngineConfiguration,還有agenda。

它這個CommandContext被設計成是每個執行緒私有的,就是每個執行緒都有自己的一個CommandContext

執行緒區域性變數中存放這棧,棧裡面放著物件

攔截器鏈的最後一個攔截器是 CommandInvoker

4.  CommandInvoker

重頭戲來了,接下來 CommandInvoker 的每個方法都要仔細看了

可以看到,真正去執行命令是在CommandInvoker中觸發的

5.  Agenda

agenda (譯:議程,待議事項,議事日程)的意思是「議程」,「會議議程」,「待議事項」

可以把 agenda 想象成是一個會議,首先每個命令請求都有一個 CommandContext,CommandContext裡面有Agenda

這樣的話,CommandContext 相當於會議室,Agenda 相當於這次會議的議程,就是這次會議要商議的事項有哪些,每個 Operation 相當於一個待議事項,在會議進行期間會不斷產生很多新的事項,然後一個一個事項的過,直到所有的事項都處理完了。

也可以把 agenda 想象成執行緒池,不斷有新的任務被丟進執行緒池,工作執行緒就不斷從工作佇列中取任務執行

還是 「會議室 --> 會議 --> 議程 --> 事項 --> 處理事項」更加形象生動

每個命令請求就相當於發起一次會議,會議的目的是處理這次的命令請求。為了開會討論解決問題,需要有個會議室,然後發起會議,會議上有很多要解決的問題,一個一個解決問題,直到所有問題都被解決,會議結束

每個待議事項都是一個 Runnable 型別的物件,注意別搞混了,Runnable 本身不是執行緒。我個人猜測,之所以設計成Runnable型別的主要是為了方便非同步處理,我們可以設定Activiti的活動是同步還是非同步執行,而直接呼叫Runnable的run()方法就是同步執行,把它放到執行緒池就是非同步執行,業務處理的邏輯都在run()方法裡,完全不用關心是同步還是非同步執行,這種設計太絕了,妙啊。。。(PS:純屬個人猜測,沒有求證過,O(∩_∩)O哈哈~)

命令執行的結果放到會議室(CommandContext)

活動結束後,會呼叫planContinueProcessOperation(),流程繼續執行,進入下一個活動節點

6.  CompleteTaskCmd

回到最初的完成任務命令,我們指定任務執行呼叫的Runnable的run()方法,run()方法裡面是呼叫命令的execute方法

所以,接下來看完成任務這個命令具體做了什麼

7.  ActivityBehavior

要理解 Behavior 必須要和流程圖聯絡起來,流程圖上的一些元素比如 閘道器、使用者任務、子流程、事件等等都有對應的行為,每種行為的處理方式都不同

ActivityBehavior 的實現類比較多,層級也比較深,不一一列舉,以其中一個為例看看就行了

繼續回到完成任務,剛才看到往agenda中放了一個 TriggerExecutionOperation,該操作觸發等待狀態並繼續該流程,並離開該活動。

8.  回顧

Command:命令

ActivitiEngineAgendaFactory:用於建立ActivitiEngineAgenda

ActivitiEngineAgenda:議程,待議事項,用於迴圈執行Operation

AbstractOperation:事項/操作,它實現了Runnable介面

CommandContextFactory:用於建立CommandContext

CommandContext:每個命令執行執行緒都有自己的CommandContext,其內部有對Command和Agenda的參照

CommandExecutor:執行Command,從攔截器鏈的第一個攔截器開始執行

CommandInvoker:攔截器鏈上的最後一個攔截器,負責將命令封裝成Operation,在Agenda中執行Operation的時候就會呼叫具體命令的execute方法

ActivityBehavior:代表活動的行為,這是真正底層的驅動流程流轉的核心