說說 jBPM 工作流引擎的設(shè)計原理

1服務(wù) API 設(shè)計

jBPM4 工作流引擎的核心 PVM 主要依靠 4 組服務(wù) API :

  • 流程定義服務(wù) - Process Service。
  • 流程執(zhí)行服務(wù)- Execution Service。
  • 流程管理服務(wù) - Managerment Service。
  • 指令服務(wù) - Command Service。
PVM 核心服務(wù) API 之間的作用關(guān)系

應(yīng)用通過這些服務(wù)與 PVM 進(jìn)行數(shù)據(jù)交互,這些都是在支持事務(wù)的持久化模式下運(yùn)行的。比如:

  • ExecutionService.startProcessInstanceByKey - 發(fā)起流程實(shí)例。
  • TaskService.completeTask - 完成任務(wù)。

客戶端 API 是核心工作流模型對象對外暴露的公共方法,我們可以直接使用客戶端 API 來執(zhí)行一些流程操作,客戶端 API 不會進(jìn)行任何持久化操作,它操作的結(jié)果是通過調(diào)用相應(yīng)服務(wù)的 API 后才會被持久化。比如:

  • ProcessInstance.getName - 獲取流程實(shí)例名稱。
  • Task.setAssignee - 設(shè)置任務(wù)分配者。

1.1 活動 API

活動 API 用于實(shí)現(xiàn)流程活動在運(yùn)行時的行為。所有的活動類型都要實(shí)現(xiàn) ActivityBehaviour 接口,它提供了控制流程執(zhí)行的方法,接口定義如下:

public interface ActivityBehaviour extends Serializable {
  
  /** invoked when an execution arrives in an activity.
   * 
   * <p>An ActivityBehaviour can control the propagation 
   * of execution.  ActivityBehaviour's can become external activities when they 
   * invoke {@link ActivityExecution#waitForSignal()}.  That means the 
   * activity will become a wait state.  In that case, {@link ExternalActivityBehaviour} 
   * should be implemented to also handle the external signals. 
   * </p> */
  void execute(ActivityExecution execution) throws Exception;
}

執(zhí)行對象的類型需要實(shí)現(xiàn) ActivityExecution 接口,這個接口定義了控制流程推進(jìn)的方法:

活動定義 說明
String getActivityName() 獲取當(dāng)前活動名稱。
void waitForSignal() 等待執(zhí)行信號。
void takeDefaultTransition() 選擇一個默認(rèn)的流出轉(zhuǎn)移。
void take(String transitionName) 選擇一個指定名稱的流出轉(zhuǎn)移。
void execute(String activityName) 執(zhí)行子活動。
void end() 結(jié)束當(dāng)前流程(包括子流程)。
void end(String state) 結(jié)束當(dāng)前流程(包括子流程),并為子流程指定結(jié)束狀態(tài)。
void setPriority(int priority) 設(shè)置活動優(yōu)先級。

1.2 事件監(jiān)聽 API

事件監(jiān)聽 API 用于自定義事件監(jiān)聽器,它可以用來處理被監(jiān)聽到的流程事件。

它與活動 API 的區(qū)別是:它不能控制流程的執(zhí)行。假設(shè)一個活動通過 execution 已經(jīng)確定了一個轉(zhuǎn)移,這時就會觸發(fā)它所對應(yīng)的事件監(jiān)聽器,因為轉(zhuǎn)移已經(jīng)先被確定,所以事件監(jiān)聽器必然無法改變流程的推進(jìn)路線。

自定義的事件監(jiān)聽器,需要實(shí)現(xiàn) EventListener 接口,這個接口定義如下:

public interface EventListener extends Serializable {
  
  /** is invoked when an execution crosses the event on which this listener is registered */
  void notify(EventListenerExecution execution) throws Exception;

}

這里的 notify 方法需要一個 EventListenerExecution 類型的參數(shù),它與 ActivityExecution 的相同之處是,它們都繼承自 OpenExecution 接口,但它只定義了一個設(shè)置優(yōu)先級的方法:

public interface EventListenerExecution extends OpenExecution {
  
  /** setter for the priority.  The default priority is 0, which means 
   * NORMAL. Other recognized named priorities are HIGHEST (2), HIGH (1), 
   * LOW (-1) and LOWEST (-2). For the rest, the user can set any other 
   * priority integer value, but then, the UI will have to display it as 
   * an integer and not the named value.*/
  void setPriority(int priority);
}

再次強(qiáng)調(diào):事件監(jiān)聽器無法改變流程的推進(jìn)路徑。

2 運(yùn)行環(huán)境設(shè)計

為了讓流程可以在不同的事務(wù)環(huán)境(Java EE 或 Spring )中運(yùn)行,PVM 定義了運(yùn)行環(huán)境對象,它會根據(jù)配置的環(huán)境,執(zhí)行服務(wù)延遲加載與獲取事務(wù)管理等操作。

運(yùn)行環(huán)境是 EnvironmentFactory 對象,它有兩個實(shí)現(xiàn):

  • ProcessEngineImpl - 默認(rèn)的 Java EE 環(huán)境。
  • SpringProcessEngine - 基于 Spring 框架的環(huán)境。

通過以下方式獲取默認(rèn)環(huán)境工廠對象,從而執(zhí)行任意流程操作:

ConfigurationImpl cfg = new ConfigurationImpl();
cfg.setResource("jbpm.cfg.xml");//指定配置文件

//創(chuàng)建環(huán)境工廠對象
EnvironmentFactory factory=new ProcessEngineImpl(cfg);

//執(zhí)行任意流程操作
Environment environment=factory.openEnvironment();
try {
     RepositoryService repositoryService = environment.get(RepositoryService.class);
} finally {
    factory.close();
}

注意:通過 Environment 對象獲取的流程服務(wù)受到事務(wù)的控制。

也可以通過 Configuration 類加載默認(rèn)的配置文件,獲取各項流程服務(wù),這種方式更方便:

ProcessEngine engine= Configuration.getProcessEngine();
RepositoryService repositoryService=engine.getRepositoryService();

3 命令設(shè)計模式

命令設(shè)計模式是 jBPM4 實(shí)現(xiàn)流程邏輯的核心思想。所有的命令都需要實(shí)現(xiàn) Command 接口,并在 execute() 方法中實(shí)現(xiàn)邏輯:

public interface Command<T> extends Serializable {
  
  T execute(Environment environment) throws Exception;
}

注意: 每個命令都是獨(dú)立的事務(wù)操作,即每一個 execute() 方法的實(shí)現(xiàn)都被一個 Hibernate 事務(wù)所包含。

public class CustomCommand implements Command<Void> {
    private String executionId;

    @Override
    public Void execute(Environment environment) throws Exception {
        //從環(huán)境對象中獲取執(zhí)行服務(wù)
        ExecutionService executionService = environment.get(ExecutionService.class);

        //執(zhí)行服務(wù),完成流程邏輯
        executionService.signalExecutionById(executionId);
        return null;
    }

}

命令定義后,可以通過流程引擎對象來執(zhí)行自定義的命令:

ProcessEngine engine = Configuration.getProcessEngine();
engine.execute(new CustomCommand());

4 服務(wù)設(shè)計

外部應(yīng)用程序(比如客戶端)會調(diào)用服務(wù) API 來作為操作工作流引擎,也可以通過它來持久化 PVM 的操作。

三個基本的服務(wù)接口:

服務(wù)類 說明
RepositoryService 流程定義及其相關(guān)資源的服務(wù)
ExecutionService 流程實(shí)例及其執(zhí)行的服務(wù)
ManagementService Job 相關(guān)服務(wù)

所有的流程邏輯都被封裝為命令,因此上述的三個服務(wù)類的方法實(shí)現(xiàn)執(zhí)行的都是命令。比如 ManagementService 中的 createJobQuery 的實(shí)現(xiàn):

public JobQuery createJobQuery() {
    JobQueryImpl query = commandService.execute(new CreateJobQueryCmd());
    query.setCommandService(commandService);
    return query;
  }

所有的 PVM 命令都統(tǒng)一委派給 CommandService,由它來執(zhí)行這些命令:

public interface CommandService {
  
  String NAME_TX_REQUIRED_COMMAND_SERVICE = "txRequiredCommandService";
  String NAME_NEW_TX_REQUIRED_COMMAND_SERVICE = "newTxRequiredCommandService";

  /**
   * @throws JbpmException if command throws an exception.
   */
  <T> T execute(Command<T> command);
}

CommandService 有兩種工作模式:

  • NAME_TX_REQUIRED_COMMAND_SERVICE :在同一線程中使用一個事務(wù)來執(zhí)行所有的命令。
  • NAME_NEW_TX_REQUIRED_COMMAND_SERVICE :一個命令執(zhí)行一個事務(wù)。

CommandService 只定義了一個用于執(zhí)行命令方法 execute()。

在默認(rèn)的配置文件 jbpm.default.cfg.xml 中,預(yù)設(shè)了以下這些服務(wù):

<repository-service />
<repository-cache />
<execution-service />
<history-service />
<management-service />
<identity-service />
<task-service />

CommandService 的設(shè)計采用了職責(zé)鏈的設(shè)計模式,它是環(huán)繞在命令周圍的一群攔截器所組成的一條職責(zé)鏈。我們可以組合不同的攔截器,按照不同的順序,在不同的環(huán)境下實(shí)現(xiàn)不同的持久化事務(wù)策略。

在 jbpm.tx.hibernate.cfg.xml 中,描述了 CommandService 的實(shí)現(xiàn)策略:

<command-service name="txRequiredCommandService">
  <skip-interceptor />
  <retry-interceptor />
  <environment-interceptor />
  <standard-transaction-interceptor />
</command-service>

<command-service name="newTxRequiredCommandService">
  <retry-interceptor />
  <environment-interceptor policy="requiresNew" />
  <standard-transaction-interceptor />
</command-service>

這就是我們之前所說的 CommandService 存在的兩種工作模式的配置方式。

各個服務(wù)會按照需要來選擇合適的 CommandService 工作模式來執(zhí)行命令。各個攔截器繼承自 Interceptor 抽象類,而它實(shí)現(xiàn)的就是 CommandService 接口:

public abstract class Interceptor implements CommandService {

  protected CommandService next;

  public CommandService getNext() {
    return next;
  }
  public void setNext(CommandService next) {
    this.next = next;
  }
}

多個 CommandService 被配置為一條職責(zé)鏈來攔截命令,這樣各個服務(wù)就通過職責(zé)鏈來選擇不同的策略,而無須改變命令本身啦O(∩_∩)O哈哈~

我們以 newTxRequiredCommandService 的 CommandService 實(shí)現(xiàn)為例,來說明這條職責(zé)鏈的作用,調(diào)用一條命令后,它會依次執(zhí)行以下的攔截器——

  1. retry-interceptor:在數(shù)據(jù)庫的樂觀鎖失敗時,捕獲 Hibernate 的 StaleObjectException,并嘗試重新調(diào)用命令。
  2. environment-interceptor:為命令的調(diào)用提供一個環(huán)境對象。
  3. standard-transaction-interceptor:初始化標(biāo)準(zhǔn)事務(wù)對象(StandardTransaction)。
  4. 最后,由 DefaultCommandService 來調(diào)用命令。

也可以在此通過配置,使用其他的方式來調(diào)用命令——

  • EjbLocalCommandService:把命令委派給一個本地的 EJB,這樣可以啟動一個 EJB 內(nèi)容管理事務(wù)。
  • EjbRemoteCommandService:把命令委派給一個遠(yuǎn)程的 EJB,這樣命令可以在另一個 JVM 上被執(zhí)行。
  • AsyncCommandService:命令被包裝為一個異步消息,這樣命令就會在一個新的事務(wù)中被異步執(zhí)行。

5 流程歷史庫

在整個流程實(shí)例執(zhí)行過程的各個關(guān)鍵階段,都設(shè)計了歷史事件觸發(fā)器,它會把流程實(shí)例數(shù)據(jù)存入歷史庫,實(shí)現(xiàn)了運(yùn)行中的流程數(shù)據(jù)與歷史流程數(shù)據(jù)的分離。

在流程實(shí)例的運(yùn)行過程中,或觸發(fā)歷史流程事件,然后根據(jù)分類被分發(fā)到配置好的 HistorySession 中,HistorySession 的默認(rèn)實(shí)現(xiàn) HistorySessionImpl 會調(diào)用相應(yīng)的歷史事件對象 (HistoryEvent )的 process 方法來執(zhí)行相應(yīng)的歷史事件處理邏輯:

public class HistorySessionImpl implements HistorySession {

  public void process(HistoryEvent historyEvent) {
    historyEvent.process();
  }
}

抽象類 HistoryEvent 的事件本身不會被持久化,它的抽象方法 process() 在它的實(shí)現(xiàn)類中,創(chuàng)建了歷史實(shí)體,比如 HistoryEvent 的一個實(shí)現(xiàn)類 ActivityStart:

public void process() {
    DbSession dbSession = EnvironmentImpl.getFromCurrent(DbSession.class);

    long processInstanceDbid = execution.getProcessInstance().getDbid();

    HistoryProcessInstance historyProcessInstanceImpl = dbSession.get(HistoryProcessInstanceImpl.class, processInstanceDbid);
    
    HistoryActivityInstanceImpl historyActivityInstance = 
        createHistoryActivityInstance(historyProcessInstanceImpl);
    
    String activityType = execution.getActivity().getType();
    historyActivityInstance.setType(activityType);
    
    dbSession.save(historyActivityInstance);
    
    execution.setHistoryActivityInstanceDbid(historyActivityInstance.getDbid());
}

這里創(chuàng)建了 HistoryActivityInstanceImpl ,并執(zhí)行了持久化操作。

在 process() 中歷史事件創(chuàng)建的實(shí)體與當(dāng)前的流程實(shí)體是對應(yīng)、歸并的關(guān)系,比如 ProcessInstanceCreate 事件會創(chuàng)建與持久化 HistoryProcessInstance;而 ProcessInstanceEnd 事件會設(shè)置與持久化對應(yīng)的 HistoryProcessInstance 對象的狀態(tài)(結(jié)束)。

歷史流程庫維護(hù)著過往流程的歸檔信息。但流程實(shí)例或活動實(shí)例結(jié)束時,就會在歷史流程庫中寫入數(shù)據(jù),因為這些數(shù)據(jù)對于當(dāng)前運(yùn)行著的流程來說,是歷史(過時)信息。

歷史流程庫使用 5 張表維護(hù)著 4 種實(shí)體歷史信息:

實(shí)體 表名
歷史流程實(shí)例 jbpm4_hist_procinst
歷史活動實(shí)例 jbpm4_hist_actinst
歷史任務(wù) jbpm4_hist_task
歷史流程變量 jbpm4_hist_var

最后一張是 jbpm4_hist_detail,它記錄著上述這些實(shí)體的歷史明細(xì)表。

可以使用 HistoryService 的 createHistroyXxxQuery() 方法來獲取上述實(shí)體的查詢對象,來獲取歷史流程實(shí)體信息:


查詢歷史流程實(shí)體的方法

在 HistoryService 中還提供了一些用于數(shù)據(jù)分析的方法,比如:

方法 說明
avgDurationPerActivity(String processDefinitionId) 獲取活動的平均執(zhí)行時間。
choiceDistribution(String processDefinitionId, String activityName) 獲取流程轉(zhuǎn)移的選擇次數(shù)。

需要的話,也可以根據(jù)歷史明細(xì)表 jbpm4_hist_detail,擴(kuò)展出我們自己的流程數(shù)據(jù)分析方法哦O(∩_∩)O哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,365評論 25 708
  • 感恩感謝今天回程順利,國家免費(fèi)高速有些不適應(yīng),因為惠民政策太好了,可是就是這樣子才導(dǎo)致了高速沒法開高速,太多的人擠...
    清凈心學(xué)閱讀 214評論 0 0
  • QT容器遍歷分為Java和STL遍歷 STL風(fēng)格遍歷器的語法類似于使用指針對數(shù)組的操作。我們可以使用++和--運(yùn)算...
    YBshone閱讀 5,230評論 0 2
  • 五臺山文殊院主持智真長老,作為佛門高僧,他智慧曠達(dá),仁愛慈悲,看穿人心,洞悉人性,知過去,曉未來,乃當(dāng)世活...
    雪飛江湖閱讀 666評論 0 0

友情鏈接更多精彩內(nèi)容