工作流中容器化的依赖注入!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
@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实例会暂时保存在局部作用域里:
- 会话
 - 请求
 - 依赖环境
 
 
 - 比如启动流程之前.如果当前流程实例没有激活 ,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: 限制指定连线的事件
 
 - @BusinessProcess: 限制指定流程定义的事件
 - 修饰命名可以自由组合:
- 为了接收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
 
工作流中容器化的依赖注入!Activiti集成CDI实现工作流的可配置型和可扩展型的更多相关文章
- Laravel框架下容器Container 的依赖注入和反射应用
		
依赖注入,简单说是把类里头依赖的对象,置于类外头,即客户端调用处.相当于把类与类解耦. 一个简单的例子: class A { public function __construct() { // 这种 ...
 - 在 mvc 4 中使用 unity 进行依赖注入
		
在 mvc 4 中使用 unity 进行依赖注入 关于依赖注入的概念,这里就不再叙述了,我们用 mvc 4 结合 unity,使用构造函数来进行依 赖注入.具体步骤如下: 1. 首先建立 一个 mvc ...
 - 我在项目中运用 IOC(依赖注入)--实战篇
		
上一篇<我在项目中运用 IOC(依赖注入)--入门篇>只是简单的使用 IOC.实际项目使用 IOC 的情景复杂多了,比如说,构造函数有多个参数,有多个类继承同一个接口... Unity都有 ...
 - 如何把对象手动注入Spring容器并实现依赖注入
		
将对象注入到Spring容器并实现依赖注入 public class UserDao { @Resource AccountService accountService; public void pr ...
 - [转载]Spring下IOC容器和DI(依赖注入) @Bean及@Autowired
		
Spring下IOC容器和DI(依赖注入) @Bean及@Autowired自动装配 bean是什么 bean在spring中可以理解为一个对象.理解这个对象需要换一种角度,即可将spring看做一门 ...
 - ASP.NET MVC中使用Unity进行依赖注入的三种方式
		
在ASP.NET MVC中使用Unity进行依赖注入的三种方式 2013-12-15 21:07 by 小白哥哥, 146 阅读, 0 评论, 收藏, 编辑 在ASP.NET MVC4中,为了在解开C ...
 - 细数Javascript技术栈中的四种依赖注入
		
作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...
 - AngularJS学习--- AngularJS中XHR(AJAX)和依赖注入(DI) step5
		
前言:本文接前一篇文章,主要介绍什么是XHR,AJAX,DI,angularjs中如何使用XHR和DI. 1.切换工具目录 git checkout -f step- #切换分支 npm start ...
 - .net core程序中使用微软的依赖注入框架
		
我之前在博文中介绍过Asp.net core下系统自带的依赖注入框架,这个依赖框架在Microsoft.Extensions.DependencyInjection中实现,本身并不是.net core ...
 
随机推荐
- 利用 Windows 线程池定制的 4 种方式完成任务(Windows 核心编程)
			
Windows 线程池 说起底层的线程操作一般都不会陌生,Windows 提供了 CreateThread 函数来创建线程,为了同步线程的操作,Windows 提供了事件内核对象.互斥量内核对象.关键 ...
 - 汇编环境搭建(vs2010(2012)+masm32)
			
我本地使用的环境VS2012(2010)+MASM32,下面的图是在网上找的几个博客拼在一起的,用的是vs2010,但是并不影响.(所有文件我都打包好了,如果懒的话可以直接下载这个包)地址是:http ...
 - 前端不得不了解的TCP协议
			
背景 早期的网络是基于OSI(开放式系统互联网,一般叫OSI参考模型)模型,该模型是由ISO国际标准组织制定的,包含了七层(应用层.表示层.会话层.传输层.网络层.数据链路层.物理层),即复杂又不实用 ...
 - 分析型CRM系统都分析什么?
			
在之前的文章中我们曾经讲过,目前市面上常见的CRM系统大概可以分为通用型.协助型和分析型三种类型.由于每个企业的类型.业务的不同,就需要选择一款适合的CRM客户关系管理系统.今天我们就来说一说,分析型 ...
 - CRM系统实现自动化的“三部曲”
			
在了解CRM系统的自动化的时候,我们先来看一下CRM能干什么. 从上面的流程图我们就可以看出,CRM可以管理售前,售中和售后的整个客户生命周期. 为什么在复杂的客户生命周期中需要自动化呢? 当然是为了 ...
 - 炸天的3D引擎OpenCASCADE的用法及案例(https://blog.csdn.net/xipengbozai/article/details/117044032?spm=1001.2014.3001.5502)
			
What CASCADE?Open CASCADE(简称OCC)平台是由法国Matra Datavision公司开发的CAD/CAE/CAM软件平台,可以说是世界上最重要的几何造型基础软件平台之一.开 ...
 - 最全的cURL命令使用
			
cURL是什么 curl是Linux命令行工具,可以使用任何可支持的协议(如HTTP.FTP.IMAP.POP3.SCP.SFTP.SMTP.TFTP.TELNET.LDAP或FILE)在服务器之间传 ...
 - [bug] Springboot JPA使用Sort排序时的问题
			
参考 https://blog.csdn.net/qq_44039966/article/details/102713779
 - 用 set follow-fork-mode child即可。这是一个 gdb 命令,其目的是告诉 gdb 在目标应用调用fork之后接着调试子进程而不是父进程,因为在 Linux 中fork系统调用成功会返回两次,一次在父进程,一次在子进程
			
GDB的那些奇淫技巧 evilpan 收录于 Security 2020-09-13 约 5433 字 预计阅读 11 分钟 709 次阅读 gdb也用了好几年了,虽然称不上骨灰级玩家,但 ...
 - 华为eNSP模拟器— telnet实验
			
华为eNSP模拟器-telnet实验 一.实验一 路由交换之间实现telnet登陆 实验拓扑 实验目的: 路由器作为 telnet 服务器 交换机作为客户端去连接路由器 实验步骤: 路由器配置 < ...