跳到主要内容

CDI 集成

Doubao 中英对照 CDI integration

flowable-cdi 模块同时利用了 Flowable 的可配置性和 CDI 的可扩展性。flowable-cdi 最突出的特性包括:

  • 支持 @BusinessProcessScoped bean(生命周期绑定到流程实例的 CDI bean),
  • 自定义 El-Resolver,用于从流程中解析 CDI bean(包括 EJB),
  • 使用注解对流程实例进行声明式控制,
  • Flowable 已挂接到 CDI 事件总线,
  • 同时适用于 Java EE 和 Java SE,也适用于 Spring,
  • 支持单元测试。
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-cdi</artifactId>
<version>7.x</version>
</dependency>

设置 flowable-cdi​

flowable-cdi 可以在不同环境中设置。本节简要介绍配置选项。

查找流程引擎​

CDI 扩展需要获得对 ProcessEngine 的访问。为此,运行时会查找 org.flowable.cdi.spi.ProcessEngineLookup 接口的一个实现。CDI 模块自带一个名为 org.flowable.cdi.impl.LocalProcessEngineLookup 的默认实现,它使用 ProcessEngines 工具类来查找 ProcessEngine。在默认配置下,使用 ProcessEngines#NAME_DEFAULT 来查找 ProcessEngine。可以通过子类化该类来设置自定义名称。注意:你的 classpath 上需要有一个 flowable.cfg.xml 配置。

flowable-cdi 使用 java.util.ServiceLoader SPI 来解析 org.flowable.cdi.spi.ProcessEngineLookup 的实例。为了提供该接口的自定义实现,我们需要向部署中添加一个名为 META-INF/services/org.flowable.cdi.spi.ProcessEngineLookup 的纯文本文件,并在其中指定实现类的全限定类名。

注意

如果你不提供自定义的 org.flowable.cdi.spi.ProcessEngineLookup 实现,Flowable 将使用默认的 LocalProcessEngineLookup 实现。在这种情况下,你只需在 classpath 上提供一个 flowable.cfg.xml 文件(见下一节)。

配置流程引擎​

配置方式取决于所选的 ProcessEngineLookup 策略(参见上一节)。这里我们重点介绍与 LocalProcessEngineLookup 搭配使用的配置选项,该策略要求我们在 classpath 上提供一个 Spring flowable.cfg.xml 文件。

Flowable 提供不同的 ProcessEngineConfiguration 实现,主要取决于底层的事务管理策略。flowable-cdi 模块不关心事务,这意味着理论上可以使用任何事务管理策略(甚至 Spring 事务抽象)。为方便起见,CDI 模块提供了两个自定义的 ProcessEngineConfiguration 实现:

  • org.flowable.cdi.CdiJtaProcessEngineConfiguration:如果希望 Flowable 使用 JTA 管理的事务,可以使用 Flowable JtaProcessEngineConfiguration 的子类。
  • org.flowable.cdi.CdiStandaloneProcessEngineConfiguration:如果希望 Flowable 使用普通 JDBC 事务,可以使用 Flowable StandaloneProcessEngineConfiguration 的子类。以下是一个用于 JBoss 7 的 flowable.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.flowable.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/flowable 的数据源):

<?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.flowable.cdi.CdiJtaProcessEngineConfiguration">
<property name="dataSourceJndiName" value="jdbc/flowable" />
<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>4.2.5.RELEASE</version>
</dependency>

Java SE 环境中的配置与创建 ProcessEngine一节中提供的示例完全相同,只需将 StandaloneProcessEngineConfiguration 替换为 CdiStandaloneProcessEngineConfiguration。

部署流程​

可以使用标准的 Flowable API(RepositoryService)部署流程。此外,flowable-cdi 还提供了自动部署 processes.xml(位于 classpath 顶层)中所列流程的能力。以下是一个 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 的上下文流程执行​

本节简要介绍 Flowable CDI 扩展所使用的上下文流程执行模型。一个 BPMN 业务流程通常是长时间运行的交互,由用户任务和系统任务共同组成。在运行时,一个流程会被拆分为一组独立的工作单元,由用户和/或应用逻辑执行。在 flowable-cdi 中,流程实例可以与一个 CDI 作用域关联,该关联代表一个工作单元。当某个工作单元较为复杂时(例如用户任务的实现是由一系列不同表单组成的复杂序列,且在此交互过程中需要保留"非流程作用域"状态),这一点尤其有用。

在默认配置下,流程实例与"最宽的"活动作用域关联:从 conversation 开始;如果 conversation 上下文未激活,则回退到 request。

将会话与流程实例关联​

在解析 @BusinessProcessScoped bean 或注入流程变量时,我们依赖活动 CDI 作用域与流程实例之间已存在的关联。flowable-cdi 提供了 org.flowable.cdi.BusinessProcess bean 来控制该关联,其中最主要的是:

  • startProcessBy(…​) 方法,对应 Flowable RuntimeService 暴露的相应方法,用于启动并随后关联一个业务流程。
  • resumeProcessById(String processInstanceId),用于将流程实例与提供的 id 关联。
  • resumeTaskById(String taskId),用于将任务与提供的 id 关联(并由此扩展到相应的流程实例)。

一旦某个工作单元(例如一个用户任务)完成,可以调用 completeTask() 方法来解除 conversation/request 与流程实例的关联。这会通知引擎当前任务已完成,并使流程实例继续推进。

请注意,BusinessProcess bean 是一个 @Named bean,这意味着可以使用表达式语言调用其暴露的方法,例如从 JSF 页面中调用。以下 JSF2 片段会启动一个新的 conversation 并将其与某个用户任务实例关联,该任务的 id 作为请求参数传入(例如 pageName.jsf?taskId=XX):

<f:metadata>
<f:viewParam name="taskId" />
<f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
</f:metadata>

以声明式方式控制流程​

flowable-cdi 允许使用注解以声明方式启动流程实例并完成任务。@org.flowable.cdi.annotation.StartProcess 注解允许通过 "key" 或 "name" 启动流程实例。请注意,流程实例是在被注解的方法返回之后才启动的。示例:

@StartProcess("authorizeBusinessTripRequest")
public String submitRequest(BusinessTripRequest request) {
// do some work
return "success";
}

根据 Flowable 的配置,被注解方法的代码与流程实例的启动将在同一事务中进行。@org.flowable.cdi.annotation.CompleteTask 注解的工作方式相同:

@CompleteTask(endConversation=false)
public String authorizeBusinessTrip() {
// do some work
return "success";
}

@CompleteTask 注解提供了结束当前 conversation 的能力。默认行为是在对 Flowable 的调用返回后结束 conversation。如上例所示,可以禁用结束 conversation。

从流程中引用 Bean​

flowable-cdi 使用自定义 resolver 将 CDI bean 暴露给 Flowable El。这样就可以从流程中引用 bean:

<userTask id="authorizeBusinessTrip" name="Authorize Business Trip"
flowable:assignee="#{authorizingManager.account.username}" />

其中 authorizingManager 可以是由 producer 方法提供的 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();
}

我们也可以使用同一特性,在服务任务中调用 EJB 的业务方法,使用 flowable:expression="myEjb.method()" 扩展。请注意,这要求 MyEjb 类上有一个 @Named 注解。

使用 @BusinessProcessScoped bean​

使用 flowable-cdi,可以将 bean 的生命周期绑定到流程实例。为此提供了一个自定义上下文实现,即 BusinessProcessContext。BusinessProcessScoped bean 的实例作为流程变量存储在当前流程实例中。BusinessProcessScoped bean 需要可 Passivation(例如实现 Serializable)。以下是一个流程作用域 bean 的示例:

@Named
@BusinessProcessScoped
public class BusinessTripRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String startDate;
private String endDate;
// ...
}

有时我们希望在没有与流程实例关联的情况下使用流程作用域 bean,例如在启动流程之前。如果当前没有活动的流程实例,BusinessProcessScoped bean 的实例会临时存储在本地作用域中,即 Conversation 或 Request(取决于上下文)。如果该作用域随后与某个业务流程实例关联,这些 bean 实例就会被 flush 到该流程实例中。

注入流程变量​

流程变量可以被注入。flowable-cdi 支持:

  • 使用 @Inject \[additional qualifiers\] Type fieldName 对 @BusinessProcessScoped bean 进行类型安全注入。
  • 使用 @ProcessVariable(name?) 限定符对其他流程变量进行非类型安全注入:
@Inject @ProcessVariable Object accountNumber;
@Inject @ProcessVariable("accountNumber") Object account

要使用 EL 引用流程变量,也有类似的选项:

  • 可以直接引用 @Named @BusinessProcessScoped bean,
  • 可以使用 ProcessVariables bean 引用其他流程变量:
#{processVariables['accountNumber']}

接收流程事件​

Flowable 可以挂接到 CDI 事件总线。这使我们能够使用标准的 CDI 事件机制接收流程事件通知。要为 Flowable 启用 CDI 事件支持,请在配置中启用相应的解析监听器:

<property name="postBpmnParseHandlers">
<list>
<bean class="org.flowable.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
</list>
</property>

现在 Flowable 已配置为使用 CDI 事件总线发布事件。下面概述了如何在 CDI bean 中接收流程事件。在 CDI 中,我们可以使用 @Observes 注解以声明方式指定事件观察者。事件通知是类型安全的。流程事件的类型为 org.flowable.cdi.BusinessProcessEvent。
以下是一个简单的事件观察者方法示例:

public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
// handle event
}

该观察者会收到所有事件的通知。如果希望限制该观察者接收的事件集合,可以添加限定符注解:

  • @BusinessProcess:将事件集合限制为某个流程定义。示例:@Observes @BusinessProcess("billingProcess") BusinessProcessEvent evt
  • @StartActivity:将事件集合限制为某个活动。例如:每当进入 id 为 "shipGoods" 的活动时,就会调用 @Observes @StartActivity("shipGoods") BusinessProcessEvent evt。
  • @EndActivity:将事件集合限制为某个活动。例如:每当离开 id 为 "shipGoods" 的活动时,就会调用 @Observes @EndActivity("shipGoods") BusinessProcessEvent evt。
  • @TakeTransition:将事件集合限制为某个迁移。
  • @CreateTask:将事件集合限制为某个任务的创建。
  • @DeleteTask:将事件集合限制为某个任务的删除。
  • @AssignTask:将事件集合限制为某个任务的分配。
  • @CompleteTask:将事件集合限制为某个任务的完成。

上面命名的限定符可以自由组合。例如,为了接收在 "shipmentProcess" 中离开 "shipGoods" 活动时产生的所有事件,我们可以编写如下观察者方法:

public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// handle event
}

在默认配置下,事件监听器是同步调用的,并且处于同一事务的上下文中。CDI 事务性观察者(仅在与 JavaEE/EJB 结合时可用)允许控制事件何时被传递给观察者方法。使用事务性观察者,例如我们可以确保只有在事件被触发的事务成功时,观察者才会收到通知:

public void onShipmentSucceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// send email to customer.
}

附加特性​

  • ProcessEngine 以及各服务都可以注入:@Inject ProcessEngine, RepositoryService, TaskService, …​
  • 当前流程实例和任务可以注入:@Inject ProcessInstance, Task,
  • 当前业务 key 可以注入:@Inject @BusinessKey String businessKey,
  • 当前流程实例 id 可以注入:@Inject @ProcessInstanceId String pid,

已知限制​

尽管 flowable-cdi 是基于 SPI 实现并被设计为"可移植扩展"的,但它目前只使用 Weld 进行了测试。