工作流引擎Activiti使用進階!詳細解析工作流框架中高級功能的使用示例


Activiti高級功能簡介

  • Activit的高級用例,會超越BPMN 2.0流程的范疇,使用Activiti高級功能需要有Activiti開發的明確目標和足夠的Activiti開發經驗

監聽流程解析

  • bpmn 2.0 xml文件需要被解析為Activiti內部模型,然后才能在Activiti引擎中運行.解析過程發生在發布流程或在內存中找不到對應流程的時候,這時會從數據庫查詢對應的xml
  • 對於每個流程 ,BpmnParser類都會創建一個新的BpmnParse實例.這個實例會作為解析過程中的容器來使用
  • 解析過程:
    • 對於每個BPMN 2.0元素,引擎中都會有一個對應的org.activiti.engine.parse.BpmnParseHandler實例
    • 解析器會保存一個BPMN 2.0元素與BpmnParseHandler實例的映射
    • 默認Activiti使用BpmnParseHandler來處理所有支持的元素
    • 同時也使用BpmnParseHandler來提供執行監聽器,以支持流程歷史
  • 可以向Activiti引擎中添加自定義的org.activiti.engine.parse.BpmnParseHandler實例
  • 經常使用的用例是把執行監聽器添加到對應的環節,來處理一些事件隊列.Activiti在內部就是這樣進行歷史處理的
  • 要想添加這樣的自定義處理器,需要為Activit增加配置:
<property name="preBpmnParseHandlers">
  <list>
    <bean class="org.activiti.parsing.MyFirstBpmnParseHandler" />
  </list>
</property>

<property name="postBpmnParseHandlers">
  <list>
    <bean class="org.activiti.parsing.MySecondBpmnParseHandler" />
    <bean class="org.activiti.parsing.MyThirdBpmnParseHandler" />
  </list>
</property>
  • 當自定義處理器內部邏輯對處理順序有要求時需要考慮:
    • 配置到preBpmnParseHandlers的BpmnParseHandler實例會添加在默認處理器的前面
    • 配置到postBpmnParseHandlers的BpmnParseHandler實例會添加在默認處理器的后面
  • 接口- org.activiti.engine.parse.BpmnParseHandler:
public interface BpmnParseHandler {

  Collection<Class>? extends BaseElement>> getHandledTypes();

  void parse(BpmnParse bpmnParse, BaseElement element);

}
  • BpmnParseHandler接口中:
    • getHandledTypes()方法會翻譯這個解析器處理的所有類型的集合,這些都是BaseElement的子類,返回集合的泛型限制也說明了這一點
    • 也可以繼承AbstractBpmnParseHandler類並重寫getHandledType()方法,這樣就只需要返回一個類型,而不是一個集合
    • 這個類也包含需要默認解析處理器所需要的方法
    • BpmnParseHandler實例只有在解析器訪問到這個方法返回的類型時才會被調用
  • 示例:
    • BPMN 2.0 xml包含process元素時,就會執行executeParse方法中的邏輯
    • 這是一個已經完成類型轉換的方法,替換BpmnParseHandler接口中的parse方法
public class TestBPMNParseHandler extends AbstractBpmnParseHandler<Process> {

  protected Class<? extends BaseElement> getHandledType() {
    return Process.class;
  }

  protected void executeParse(BpmnParse bpmnParse, Process element) {
     ..
  }

}
  • 注意:
    • 在編寫自定義解析處理器時,不要使用任何解析BPMN 2.0結構的內部類,這會導致出現問題很難定義
    • 應該實現BpmnParseHandler接口或集成內部抽象類 org.activiti.engine.impl.bpmn.parser.handler.AbstractBpmnParseHandler
    • 也可以替換默認的BpmnParseHandler實例,把解析BPMN 2.0元素替換為解析Activiti內部模型:
<property name="customDefaultBpmnParseHandlers">
  <list>
    ...
  </list>
</property>
  • 示例: 將所有任務強制設置為異步
public class CustomUserTaskBpmnParseHandler extends ServiceTaskParseHandler {

  protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) {

    // Do the regular stuff
    super.executeParse(bpmnParse, serviceTask);

    // Make always async
    ActivityImpl activity = findActivity(bpmnParse, serviceTask.getId());
    activity.setAsync(true);
  }

}

支持高並發的UUID的ID生成器

  • 在高並發的場景中,默認的ID生成器可能因為無法很快的獲取新ID區域而導致異常
  • 所有流程引擎都有一個ID生成器,默認的ID生成器會在數據庫划取一塊ID范圍,其余引擎不能使用相同范圍的ID
  • 在引擎運行期間,當默認的ID生成器發現已經越過ID范圍時,就會啟動一個新事務來獲得新范圍.在極限的情況下,高負載會導致問題
  • 對於大部分情況,默認ID生成已經足夠:
    • 默認的org.activiti.engine.impl.db.DbIdGenerator有一個idBlockSize屬性,可以配置獲取ID范圍的大小,這樣就可以改變獲取ID的行為
    • 另一個可以選用的默認ID生成器是org.activiti.engine.impl.persistence.StrongUuidGenerator:
      • 會在本地生成一個唯一的UUID作為所有實體的標識
      • 因為生成UUID不需要訪問數據庫,所以在高並發環境下的表現比較好
  • 默認ID生成器的性能依賴於運行硬件
  • 將UUID生成器配置到Activiti:
<property name="idGenerator">
    <bean class="org.activiti.engine.impl.persistence.StrongUuidGenerator" />
</property>
  • 使用UUID生成器需要添加依賴:
 <dependency>
    <groupId>com.fasterxml.uuid</groupId>
    <artifactId>java-uuid-generator</artifactId>
    <version>3.1.3</version>
</dependency>

多租戶

  • 多租戶:
    • 通常是在軟件需要作為多個不同組織服務時產生的概念
    • 關鍵是數據分片,組織不能看到其余組織的數據
    • 在這種場景下,組織,部門,小組就叫做租戶
  • 多租戶和安裝多個實例是從基本上不同的:
    • 多租戶是一個Activiti流程引擎實例為每個組織分別運行,對應不同的數據表
    • 安裝多個Activiti流程引擎實例時,雖然Activiti是輕量級的,運行流程引擎不會消耗很多資源,但是增加了復雜性,並需要更多維護工作.然而對於一些場景,也是正確的解決方案
  • Activiti的多租戶主要圍繞着數據分片來實現:
    • Activiti沒有強行校驗多租戶的規則,即Activiti不會校驗查詢和使用數據時用戶是否使用了正確的租戶
    • 校驗由Activiti引擎的調用者層負責完成
    • Activiti只確認租戶信息會被保存,並在查詢流程數據時會被用到
  • 在向Activiti流程引擎發布流程定義時,需要傳遞一個租戶標識.是一個字符串,限制在256字符內,作為租戶的唯一標識
 repositoryService.createDeployment()
            .addClassPathResource(...)
            .tenantId("myTenantId")
            .deploy();
  • 通過部署傳遞租戶Id有以下作用:
    • 所有包含在部署中的流程定義都會繼承部署的tenantId
    • 所有從這些流程定義發起的流程實例,都會繼承流程定義的tenantId
    • 所有流程實例運行階段創建的任務都會繼承流程實例的tenantId.單獨運行的task也可以包含tenantId
    • 所有流程實例運行階段創建的分支都會繼承流程實例的tenantId
    • 在流程本身或通過API觸發一個信號拋出事件可以通過tenantId實現.信號只會在租戶環境下執行:如果有多個信號捕獲事件,並且名字相同,實際只有正確的tenantId下的事件會被調用
    • 所有作業(定時器,異步調用)會集成tenantId,或者來自流程定義(比如定時開始事件),或流程實例(運行期創建的作業,比如異步調用).這樣其實潛在的可以支持為一些租戶指定不同優先級的自定義jobExecutor
    • 所有歷史實體(歷史流程實例,任務和節點)會從對應的運行狀態集成tenantId
    • 作為單獨的一部分,model也可以設置tenantId.這里的model用來存儲Activiti modeler設計的bpmn 2.0模型
  • 為了確保流程數據使用tenantId,所有的查詢API都可以通過tenantId進行查詢,可以使用其他的實體的對應查詢實現替換:
runtimeService.createProcessInstanceQuery()
    .processInstanceTenantId("myTenantId")
    .processDefinitionKey("myProcessDefinitionKey")
    .variableValueEquals("myVar", "someValue")
    .list()

查詢API也允許對tenantId使用like語法, 也可以過濾未設置tenantId的實體

  • 重要注意點:
    • 因為數據庫的限制,特別是處理null的唯一校驗.默認表示未設置租戶的tenantId的值是空字符串
    • 流程定義key,流程定義version,tenantId的組合應該是唯一的,這個有數據庫約束校驗這個規則
    • 要注意tenantId不應設置為null,會影響一些數據庫Oracle的查詢,會把空字符串當做null處理
    • 這也是為什么withoutTenantId查詢會檢查空字符串或null.這意味着相同的流程定義,即流程定義key相同可以部署到不同的租戶下,可以擁有各自的版本.當不使用租戶時也不會影響使用
  • 這些限制不會影響Activiti在集群環境下運行
  • 可以通過調用repositoryServicechangeDeploymentTenantId(String deploymentId, String newTenantId) 修改tenantId. 會修改之前繼承的所有tenantId. 當需要從非多租戶環境向多租戶環境下切換時,會非常實用

執行自定義SQL

  • Activiti API允許使用高級API操作數據庫:
    • 在查詢數據方面,查詢API和Native Query API是非常強大的
    • 但是對於某些情況,不夠輕便
    • 使用完全自定義的SQL語句:select, insert, update和delete.可以執行在Activiti的數據存儲之上,但是完全又可以配置在流程引擎中:比如使用事務
  • 為了使用自定義SQL,Activiti引擎使用MyBatis框架的功能:
    • 因此使用自定義SQL的第一件事,要創建MyBatis映射類
    • 假設不需要全部的任務數據,只需要其中的一小部分.可以使用Mapper實現:
public interface MyTestMapper {

    @Select("SELECT ID_ as id, NAME_ as name, CREATE_TIME_ as createTime FROM ACT_RU_TASK")
    List<Map<String, Object>> selectTasks();

}
  • Mapper需要設置到流程引擎配置中:
...
<property name="customMybatisMappers">
  <set>
    <value>org.activiti.standalone.cfg.MyTestMapper</value>
  </set>
</property>
...
  • 這個Mapper是一個接口:
    • MyBatis框架會在運行階段為這個接口創建一個實例
    • 返回值是沒有類型的,是一個map的list,和對應的行列對應
    • 如果需要也可以使用MyBatis映射
  • 執行上面的查詢:
    • 可以使用managementService.executeCustomSql方法
    • 這個方法需要一個CustomSqlExecution實體
    • 這個實體類是一個封裝類,隱藏了引擎的內部實現所需執行的信息
    • 但是由於Java泛型,查詢返回的結果可讀性差
  • 示例:
    • mapper類和返回類型類
    • 簡單調用mapper方法 並返回結果
CustomSqlExecution<MyTestMapper, List<Map<String, Object>>> customSqlExecution =
          new AbstractCustomSqlExecution<MyTestMapper, List<Map<String, Object>>>(MyTestMapper.class) {

  public List<Map<String, Object>> execute(MyTestMapper customMapper) {
    return customMapper.selectTasks();
  }

};

List<Map<String, Object>> results = managementService.executeCustomSql(customSqlExecution);

list中的Map只包含id,name和create time, 不是全部的任務對象

  • 可以通過這樣的方式執行任意SQL:
 @Select({
        "SELECT task.ID_ as taskId, variable.LONG_ as variableValue FROM ACT_RU_VARIABLE variable",
        "inner join ACT_RU_TASK task on variable.TASK_ID_ = task.ID_",
        "where variable.NAME_ = #{variableName}"
    })
    List<Map<String, Object>> selectTaskWithSpecificVariable(String variableName);

使用這種方法,任務表會與變量表關聯.只會獲得對應名稱的變量,任務Id和對應的數值會被返回

使用ProcessEngineConfigurator實現流程引擎配置

  • 可以使用ProcessEngineConfigurator實現一種高級的擴展流程引擎的配置:
    • 創建一個org.activiti.engine.cfg.ProcessEngineConfigurator接口的實現
    • 注入到流程引擎配置里
<bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">

    ...

    <property name="configurators">
        <list>
            <bean class="com.mycompany.MyConfigurator">
                ...
            </bean>
        </list>
    </property>

    ...

</bean>
  • 實現ProcessEngineConfigurator接口需要實現兩個方法:
    • configure: 將ProcessEngineConfiguration作為參數,可以通過這種方法添加自定義配置,這個方法在流程創建之前,在所有默認配置執行之前保證調用到
    • getPriority: 如果一些configurator存在依賴項的時候,允許對configurator進行排序
  • configurator實例:
    • LDAP集成:
      • 這個configurator用來替換默認的usergroup管理器類,使用處理LDAP用戶存儲的類
      • 基本上一個configurator允許很大程度上修改或增強流程引擎,對高級的場景非常有用
    • 使用自定義的版本替換流程定義緩存, 如下:
public class ProcessDefinitionCacheConfigurator extends AbstractProcessEngineConfigurator {

    public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
            MyCache myCache = new MyCache();
            processEngineConfiguration.setProcessDefinitionCache(enterpriseProcessDefinitionCache);
    }

}
  • 流程引擎配置器也可以通過ServiceLoader自動從classpath中加載:
    • 放在jar中的configurator實現必須放在classpath
    • 並在jarMETA-INF/services目錄下包含一個org.activiti.engine.cfg.ProcessEngineConfigurator文件
    • 文件的內容是自定義實現的全類名
    • 當流程引擎啟動時,日志會顯示找到了哪些configurator
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 auto-discoverable Process Engine Configurators
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 Process Engine Configurators in total:
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - class org.activiti.MyCustomConfigurator
  • 這種ServiceLoader的方式在某些環境下可能無法正常運行.使用ProcessEngineConfigurationenableConfiguratorServiceLoader屬性來禁用這個功能,這個屬性的默認值為true

啟動安全BPMN 2.0xml

  • 大多數情況下,BPMN 2.0流程發布到Activiti引擎是在嚴格的控制下的
  • 然而在某些情況下,可能需要把比較隨意的BPMN 2.0 xml上傳到引擎,這時就要要考慮惡意用戶會攻擊服務器
  • 為了避免BPMN 2.0xml引擎服務器受到攻擊,可以在引擎中設置enableSafeBpmnXml:
<property name="enableSafeBpmnXml" value="true"/>
  • 默認這個功能沒有開啟.因為這個功能需要使用StaxSource
  • 由於JDK6,JBoss使用的是舊版的xml解析實現,無法使用StaxSource類,所以不能啟用安全的BPMN 2.0xml
  • 如果Activiti運行的平台支持安全的BPMN 2.0xml功能,建議打開

事件日志

  • 在Activiti 5.16版本中,添加了事件日志機制:
    • 這種日志機制構建在通用目的下的Activiti引擎的事件機制,默認是禁用的
    • 目的是由引擎產生的事件會被捕獲,包含所有事件數據的map會被創建出來,並提供給org.activiti.engine.impl.event.logger.EventFlusher, 會把數據刷新到別的地方
    • 默認會使用一個簡單地基於數據庫的事件處理器或者叫作刷新器,會使用jacksonmap轉換為JSON, 並保存到數據庫中的EventLogEntryEntity實體
    • 默認會創建數據庫日志表ACT_EVT_LOG. 如果沒有使用事件日志,可以刪除這個表
  • 啟用數據庫日志:
processEngineConfiguration.setEnableDatabaseEventLogging(true);

或者在流程引擎運行階段:

databaseEventLogger = new EventLogger(processEngineConfiguration.getClock());
runtimeService.addEventListener(databaseEventLogger);
  • EventLogger類可以繼承:
    • 在需要使用自定義的數據日志時:
      • createEventFlusher() 方法需要返回一個org.activiti.engine.impl.event.logger.EventFlusher接口的實例
      • managementService.getEventLogEntries(startLogNr, size) 可以獲取Actviti的EventLogEntryEntity實例
  • 可以使用大數據的NoSQL存儲: MongoDb,Elastic Search等等來存儲JSON。
  • 使用的類是可插拔的: org.activiti.engine.impl.event.logger.EventLogger/EventFlusher和很多EventHandler
  • 可以切換成自定義應用場景: 不在數據庫中存儲JSON,而是放到隊列或大數據存儲中
  • 注意:
    • 事件日志機制是Activiti傳統歷史管理器的附加品
    • 雖然所有數據都在數據庫表中,但是並沒有為查詢優化,不容易獲取
    • 真實的使用場景:
      • 審計跟蹤
      • 將事件日志數據放到大數據存儲中


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM