16.集成 CDI
activiti-cdi 模块提供 activiti 的可配置型和 cdi 扩展。 activiti-cdi 最突出的特性有:
- 支持@BusinessProcessScoped beans(绑定到流程实例的 cdi bean),
- 流程为 cdi bean 支持自定义 EL 处理器,
- 使用注解为流程实例提供声明式控制,
- Activiti 可以挂接在 cdi 事件总线上,
- 支持 Java EE 和 Java SE,支持 Spring,
- 支持单元测试。
要想在 maven 项目中使用 activiti-cdi,可以添加如下依赖:
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-cdi</artifactId>
<version>5.x</version>
</dependency>
把'x'替换成你的 activiti 版本(>=5.6)。它会自动加入 activiti-entin 和 spring。
设置 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
, 在文件中我们需要指定实现的全类名。
Note
如果你没有提供自定义的 org.activiti.cdi.spi.ProcessEngineLookup
实现, activiti 会使用默认的 LocalProcessEngineLookup
实现。这时, 你所需要做的就是把 activiti.cfg.xml 放到 classpath 下(看下一章)。
配置 Process Engine
实际的配置依赖于选用的 ProcessEngineLookup 策略(参考上章)。 这里,我们主要结合 LocalProcessEngineLookup 讨论可用的配置, 这要求我们在 classpath 下提供一个 spring 的 activiti.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 事务环境。
下面是 JBoss 7 下的 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 class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:jboss/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean
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 class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:appserver/TransactionManager"></property>
<property name="resourceRef" value="true" />
</bean>
<!-- process engine configuration -->
<bean
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>
在 Java SE 环境下的配置和创建 ProcessEngine 章节中提供的例子一样, 使用 "CdiStandaloneProcessEngineConfiguration" 替换 "StandaloneProcessEngineConfiguration"。
发布流程
可以使用标准的 activiti-api 发布流程( RepositoryService
)。另外,activiti-cdi 提供自动发布 classpath 下 processes.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 环境的流程执行
这一章,我们简短了解 activiti cdi 扩展使用的基于环境的流程执行模型。 BPMN 业务流程通常是一个长时间运行的操作,包含了用户和系统任务的操作。 运行过程中,流程会分成多个单独的工作单元,由用户和应用逻辑执行。 在 activiti-cdi 中,流程实例可以分配到 cdi 环境中,关联展现成一个工作单元。 这是非常有用的,如果工作单元太复杂,比如如果实现的用户任务是不同形式的复杂顺序, 可以在这个操作中保持"non-process-scoped"状态。 默认配置下,流程实例分配到"broadest"激活环境,就会启动交互, 如果交互环境没有激活,就会返回到请求中。
与流程实例进行关联交互
处理@BusinessProcessScoped beans,或注入流程变量时,我们实现了激活的 cdi 环境与流程实例的关联。 Activiti-cdi 提供了 org.activiti.cdi.BusinessProcess
bean 来控制关联,特别是:
startProcessBy*(...)
方法,对应 activiti 的RuntimeService
中的相关方法, 允许启动和随后向关联的业务流程,resumeProcessById(String processInstanceId)
,允许通过提供的 id 来关联流程实例,resumeTaskById(String taskId)
,允许通过提供的 id 来关联任务(扩展情况下,也关联相应的流程实例),
一个工作单元(比如用户任务)完成后, completeTask()
方法可以调用来解除流程实例和会话/请求的关联。 这会通知 activiti 当前任务已经完成,并让流程实例继续执行。
注意, BusinessProcess
bean 是 @Named
bean,意思是导出的方法可以通过 表达式语言调用,比如在 JSF 页面中。下面的 JSF 2 代码启动一个新的交互, 把它分配给一个用户任务实例,id 作为一个请求参数传递(比如 pageName.jsf?taskId=XX
):
<f:metadata>
<f:viewParam name="taskId" />
<f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
</f:metadata>
声明式流程控制
Activiti-cdi 允许通过注解声明启动流程实例和完成任务。 @org.activiti.cdi.annotation.StartProcess
注解允许 通过"key"或"name"启动流程实例。 注意流程实例会在注解的方法返回之后启动。比如:
@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 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();
}
你可以使用同样的方法在服务任务中调用 EJB 的业务方法, 使用 activiti:expression="myEjb.method()"
扩展。 注意,这要求在 MyEjb
类中使用 @Named
注解。
使用@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']}
接收流程事件
[EXPERIMENTAL]
Activiti 可以挂在 CDI 的事件总线上。这样我们可以使用标准 CDI 事件机制来监听流程事件。 为了启用 activiti 的 CDI 事件支持,需要在配置中启用对应的解析监听器:
<property name="postBpmnParseHandlers">
<list>
<bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
</list>
</property>
现在 activiti 已经配置成使用 CDI 事件总线发布事件。下面给出了如何在 CDI bean 中处理事件的方式。 在 CDI,我们可以使用 @Observes
注解声明特定的事件监听器。事件监听是类型安全的。 流程事件类型是 org.activiti.cdi.BusinessProcessEvent
。 下面是一个简单事件监听方法的例子:
public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
// handle event
}
监听器可以监听所有事件。如果想限制监听器接收的事件类型,我们可以添加修饰注解:
@BusinessProcess
:限制指定流程定义的事件。 比如:@Observes @BusinessProcess("billingProcess") BusinessProcessEvent evt
@StartActivity
:限制指定环节的事件。比如:@Observes @StartActivity("shipGoods") BusinessProcessEvent evt
在进入 id 为"shipGoods"的环节时会触发。@EndActivity
:限制指定环节的事件。比如:@Observes @EndActivity("shipGoods") BusinessProcessEvent evt
在离开 id 为"shipGoods"的环节时会触发@TakeTransition
:限制指定连线的事件。
修饰命名可以自由组合。比如,为了接收"shipmentProcess"流程中所有离开"shipGoods"环节的事件, 我们可以编写如下监听方法:
public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// handle event
}
默认配置下,事件监听器是同步调用,并在同一个事务环境中。 CDI 事务性监听器(只在 JavaEE / EJB 环境下有效),可以控制监听器什么时候处理事件, 比如,我们可以保证监听器只在事件中的事务成功之后才处理:
public void onShipmentSuceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
// send email to customer.
}
更多功能
- 流程引擎和服务都可以注入:
@Inject ProcessEngine, RepositoryService, TaskService
, ... - 当前流程实例和任务可以注入:
@Inject ProcessInstance, Task
, - 当前业务标识可以注入:
@Inject @BusinessKey String businessKey
, - 当前流程实例 id 可以注入 :
@Inject @ProcessInstanceId String pid
,
已知的问题
虽然 activiti-cdi 已经使用了 SPI,并设计为 可移植扩展,但是只在 Weld 下测试过。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论