写点什么

工作流中容器化的依赖注入!Activiti 框架集成 CDI 实现工作流的可配置型和扩展型

发布于: 2021 年 06 月 11 日
工作流中容器化的依赖注入!Activiti框架集成CDI实现工作流的可配置型和扩展型

Activiti 工作流集成 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.8</version></dependency>
复制代码


  • activiti-cdi 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, 在文件中需要指定实现的全类名

  • 如果你没有提供自定义的 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 事务环境

  • 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 也提供了自动发布 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 环境的流程执行

  • 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 注解允许通过 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 id="authorizeBusinessTrip" name="Authorize Business Trip"                        activiti:assignee="#{authorizingManager.account.username}" />
复制代码


  • authorizingManager 可以是生产者方法提供的 bean:


@Inject @ProcessVariable Object businessTripRequesterUsername;
@Produces@Namedpublic 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@BusinessProcessScopedpublic 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

发布于: 2021 年 06 月 11 日阅读数: 13
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
工作流中容器化的依赖注入!Activiti框架集成CDI实现工作流的可配置型和扩展型