工作流中容器化的依賴注入!Activiti集成CDI實現工作流的可配置型和可擴展型


Activiti工作流集成CDI簡介

  • activiti-cdi模塊提供activiti的可配置型和cdi擴展
  • activiti-cdi的特性:
    • 支持 @BusinessProcessScoped beans, 綁定到流程實例的cdi bean
    • 流程為cdi bean支持自定義EL處理器
    • 使用注解為流程實例提供聲明式控制
    • Activiti可以掛接在cdi事件總線上
    • 支持Java EEJava SE, 支持Spring
    • 支持單元測試
  • 要在maven項目中使用activiti-cdi,需要添加依賴:
<dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-cdi</artifactId>
        <version>5.8</version>
</dependency>
  • activiti-cdi 5.6以上的版本會自動加入activiti-entinspring

設置activiti-cdi

  • Activiti cdi可以安裝在不同環境中

查找流程引擎

  • cdi擴展需要訪問到ProcessEngine, 為了實現此功能:
    • 使用org.activiti.cdi.spi.ProcessEngineLookup接口在運行期間進行查找
    • cdi模塊使用默認的名為org.activiti.cdi.impl.LocalProcessEngineLookup的實現,使用ProcessEngines這個工具類來查找ProcessEngine
    • 默認配置下,使用ProcessEngines#NAME_DEFAULT來查找ProcessEngine.這個類可能是使用自定義名稱的子類
    • 注意: 需要把activiti.cfg.xml放在classpath
  • Activiti cdi使用java.util.ServiceLoader SPI處理org.activiti.cdi.spi.ProcessEngineLookup的實例
    • 為了提供接口的自定義實現,需要創建一個文本文件,名為META-INF/services/org.activiti.cdi.spi.ProcessEngineLookup, 在文件中需要指定實現的全類名
    • 如果你沒有提供自定義的org.activiti.cdi.spi.ProcessEngineLookup實現,activiti會使用默認的LocalProcessEngineLookup實現,需要做的就是把activiti.cfg.xml放到classpath

配置Process Engine

  • 實際的配置依賴於選用的ProcessEngineLookup策略
  • 在這里主要結合LocalProcessEngineLookup討論可用的配置,要求在classpath下提供一個springactiviti.cfg.xml
  • Activiti提供了不同的ProcessEngineConfiguration實現,主要是依賴實際使用的事務管理策略
  • activiti-cdi模塊對事務的要求不嚴格,意味着任何事務管理策略都可以使用,即便是spring事務抽象層
  • cdi模塊提供兩種自定義ProcessEngineConfiguration實現:
    • org.activiti.cdi.CdiJtaProcessEngineConfiguration: activiti的JtaProcessEngineConfiguration的子類,用於在activiti使用JTA管理的事務環境
    • org.activiti.cdi.CdiStandaloneProcessEngineConfiguration: activiti的StandaloneProcessEngineConfiguration的子類,用於在activiti使用簡單JDBC事務環境
  • JBoss7下的activiti.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

        <!-- lookup the JTA-Transaction manager -->
        <bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
                <property name="jndiName" value="java:jboss/TransactionManager"></property>
                <property name="resourceRef" value="true" />
        </bean>

        <!-- process engine configuration -->
        <bean id="processEngineConfiguration"
                class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
                <!-- lookup the default Jboss datasource -->
                <property name="dataSourceJndiName" value="java:jboss/datasources/ExampleDS" />
                <property name="databaseType" value="h2" />
                <property name="transactionManager" ref="transactionManager" />
                <!-- using externally managed transactions -->
                <property name="transactionsExternallyManaged" value="true" />
                <property name="databaseSchemaUpdate" value="true" />
        </bean>
</beans>
  • 在Glassfish 3.1.1,假設配置好名為jdbc/activiti的datasource:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

        <!-- lookup the JTA-Transaction manager -->
        <bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
                <property name="jndiName" value="java:appserver/TransactionManager"></property>
                <property name="resourceRef" value="true" />
        </bean>

        <!-- process engine configuration -->
        <bean id="processEngineConfiguration"
                class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
                <property name="dataSourceJndiName" value="jdbc/activiti" />
                <property name="transactionManager" ref="transactionManager" />
                <!-- using externally managed transactions -->
                <property name="transactionsExternallyManaged" value="true" />
                <property name="databaseSchemaUpdate" value="true" />
        </bean>
</beans>
  • 注意: 上面的配置要引入spring-context模塊依賴
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.0.3.RELEASE</version>
</dependency>

發布流程

  • 可以使用標准的activiti-api發布流程-RepositoryService
  • activiti-cdi也提供了自動發布classpathprocesses.xml中列出的流程的方式
  • processes.xml:
<?xml version="1.0" encoding="utf-8" ?>
<!-- list the processes to be deployed -->
<processes>
        <process resource="diagrams/myProcess.bpmn20.xml" />
        <process resource="diagrams/myOtherProcess.bpmn20.xml" />
</processes>

基於CDI環境的流程執行

  • BPMN業務流程通常是一個長時間運行的操作,包含了用戶和系統任務的操作
  • 運行過程中,流程會分成多個單獨的工作單元,由用戶和應用邏輯執行
  • activiti-cdi中,流程實例可以分配到cdi環境中,關聯展現成一個工作單元:
    • 這是非常有用的,如果工作單元太復雜:比如如果實現的用戶任務是不同形式的復雜順序,可以在這個操作中保持non-process-scoped狀態
    • 默認配置下,流程實例分配到broadest激活環境,就會啟動交互,如果交互環境沒有激活,就會返回到請求中

與流程實例進行關聯交互

  • 處理 @BusinessProcessScoped beans, 或注入流程變量時,實現了激活的cdi環境與流程實例的關聯
  • Activiti-cdi提供了org.activiti.cdi.BusinessProcess bean來控制關聯:
    • startProcessByXx(...): 對應activiti的RuntimeService中的相關方法,允許啟動和隨后向關聯的業務流程
    • resumeProcessById(String processInstanceId): 允許通過提供的Id來關聯流程實例
    • resumeTaskById(String taskId): 允許通過提供的Id來關聯任務,也可以擴展關聯流程實例
  • 一個工作單元完成后 ,completeTask() 方法可以調用來解除流程實例和會話或請求的關聯.這會通知activiti當前任務已經完成,並讓流程實例繼續執行
  • BusinessProcess bean@Named bean, 意思是導出的方法可以通過表達式語言調用:
    • 比如在JSF頁面中.下面的JSF 2 代碼啟動一個新的交互,分配給一個用戶任務實例,Id作為一個請求參數傳遞:
<f:metadata>
<f:viewParam name="taskId" />
<f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
</f:metadata>

聲明式流程控制

  • Activiti-cdi允許通過注解聲明啟動流程實例和完成任務
  • @org.activiti.cdi.annotation.StartProcess注解允許通過keyname啟動流程實例.流程實例會在注解的方法返回之后啟動:
@StartProcess("authorizeBusinessTripRequest")
public String submitRequest(BusinessTripRequest request) {
        // do some work
        return "success";
}
  • 根據activiti的配置,注解方法的代碼和啟動流程實例會在同一個事務中執行 .@org.activiti.cdi.annotation.CompleteTask事務的使用方式相同:
@CompleteTask(endConversation=false)
public String authorizeBusinessTrip() {
        // do some work
        return "success";
}

@CompleteTask注解可以結束當前會話.默認行為會在activiti返回后結束會話.可以禁用結束會話的功能

在流程中引用bean

  • Activiti-cdi使用自定義解析器把CDI bean暴露到activiti El中,可以在流程中引用這些bean:
<userTask id="authorizeBusinessTrip" name="Authorize Business Trip"
                        activiti:assignee="#{authorizingManager.account.username}" />
  • authorizingManager可以是生產者方法提供的bean:
@Inject @ProcessVariable Object businessTripRequesterUsername;

@Produces
@Named
public Employee authorizingManager() {
        TypedQuery<Employee> query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.account.username='"
                + businessTripRequesterUsername + "'", Employee.class);
        Employee employee = query.getSingleResult();
        return employee.getManager();

使用@BusinessProcessScoped beans

  • 使用activiti-cdi,bean的生命周期可以綁定到流程實例上:
    • 可以提供一個自定義的環境實現,命名為BusinessProcessContext.
    • BusinessProcessScoped bean的實例會作為流程變量保存到當前流程實例中
    • BusinessProcessScoped bean需要是PassivationCapable,比如序列化
  • 使用流程作用域bean的示例如下:
@Named
@BusinessProcessScoped
public class BusinessTripRequest implements Serializable {
        private static final long serialVersionUID = 1L;
        private String startDate;
        private String endDate;
        // ...
}
  • 有時,需要使用流程作用域bean,沒有與流程實例關聯:
    • 比如啟動流程之前.如果當前流程實例沒有激活 ,BusinessProcessScoped bean實例會暫時保存在局部作用域里:
      • 會話
      • 請求
      • 依賴環境
  • 如果作用域后來與業務流程實例關聯了,bean實例會刷新到流程實例里

注入流程變量

  • 流程變量可以實現用於注入
  • Activiti-CDI支持以下注入流程變量的方式:
    • @BusinessProcessScoped使用 @Inject [附加修飾] 類型 屬性名實現類型安全的流程變量的注入
    • 使用@ProcessVariable(name)修飾符實現對類型不安全的流程變量的注入
@Inject @ProcessVariable Object accountNumber;
@Inject @ProcessVariable("accountNumber") Object account
  • 為了通過EL引用流程變量, 可以使用如下方式:
    • @Named @BusinessProcessScoped beans可以直接引用
    • 其他流程變量可以使用ProcessVariables bean來使用
#{processVariables['accountNumber']}

接收流程事件

  • Activiti可以掛在CDI的事件總線上,就可以使用標准CDI事件機制來監聽流程事件
  • 為了啟用activiti的CDI事件支持,需要在配置中啟用對應的解析監聽器:
<property name="postBpmnParseHandlers">
        <list>
                <bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
        </list>
</property>
  • 這樣activiti就配置成了使用CDI事件總線發布事件
  • CDI bean中處理事件的方式:
    • 使用@Observes注解聲明特定的事件監聽器
    • 事件監聽是類型安全的
    • 流程事件類型是org.activiti.cdi.BusinessProcessEvent
  • 一個簡單事件監聽方法示例:
public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
        // handle event
}
  • 監聽器可以監聽所有事件.如果想限制監聽器接收的事件類型,可以添加修飾注解:
    • @BusinessProcess: 限制指定流程定義的事件
      • @Observes @BusinessProcess("billingProcess")
    • @StartActivity: 限制指定進入環節的事件
      • @Observes @StartActivity("shipGoods")
    • @EndActivity: 限制指定結束環節的事件
      • @Observes @EndActivity("shipGoods")
    • @TakeTransition: 限制指定連線的事件
  • 修飾命名可以自由組合:
    • 為了接收shipmentProcess流程中所有離開shipGoods環節的事件:
public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
        // handle event
}
  • 默認配置下,事件監聽器是同步調用,並在同一個事務環境中
  • CDI事務性監聽器可以控制監聽器什么時候處理事件:
    • 可以保證監聽器只在事件中的事務成功之后才處理
public void onShipmentSuceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
        // send email to customer.
}

Activiti CDI中的更多功能

  • 流程引擎和服務都可以注入: Inject ProcessEngine,RepositoryService,TaskService,...
  • 當前流程實例和任務可以注入: @Inject ProcessInstance, Task
  • 當前業務標識可以注入: @Inject @BusinessKey String businessKey
  • 當前流程實例id可以注入: @Inject @ProcessInstanceId String pid


免責聲明!

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



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