工作流引擎使用详解!工作流框架 Activiti 的详细配置以及安装使用
创建 ProcessEngine
Activiti 流程引擎的配置文件是名为 activiti.cfg.xml 的 XML 文件.注意与使用 Spring 方式创建流程引擎是不一样的
使用 org.activiti.engine.ProcessEngines 类,获得 ProcessEngine:
它会在 classpath 下搜索 activiti.cfg.xml,并基于这个文件中的配置构建引擎
配置文件中使用的 ProcessEngineConfiguration 可以通过编程方式创建,可以配置不同的 bean id
如果不使用配置文件进行配置,就会基于默认创建配置
ProcessEngineConfiguration.createXXX() 方法都会返回 ProcessEngineConfiguration,后续可以调整成所需的对象. 在调用 buildProcessEngine()后, 就会创建一个 ProcessEngine:
ProcessEngineConfiguration bean
activiti.cfg.xml 必须包含一个 id='processEngineConfiguration' 的 bean
这个 bean 会用来构建 ProcessEngine. 有多个类可以用来定义 processEngineConfiguration. 这些类对应不同的环境,并设置了对应的默认值:
org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration: 单独运行的流程引擎.Activiti 会自己处理事务.默认数据库只在引擎启动时检测(如果没有 Activiti 的表或者表结构不正确就会抛出异常)
org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration: 单元测试时的辅助类.Activiti 会自己控制事务. 默认使用 H2 内存数据库,数据库表会在引擎启动时创建,关闭时删除.使用它时,不需要其他配置(除非使用 job 执行器或邮件功能)
org.activiti.spring.SpringProcessEngineConfiguration: 在 Spring 环境下使用流程引擎
org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration: 单独运行流程引擎,并使用 JTA 事务
数据库配置
定义数据库配置参数
基于数据库配置参数定义数据库连接配置
jdbcUrl: 数据库的 JDBC URL
jdbcDriver: 对应不同数据库类型的驱动
jdbcUsername: 连接数据库的用户名
jdbcPassword: 连接数据库的密码
基于 JDBC 参数配置的数据库连接 会使用默认的 MyBatis 连接池,配置 MyBatis 连接池:
jdbcMaxActiveConnections: 连接池中处于被使用状态的连接的最大值.默认为 10
jdbcMaxIdleConnections: 连接池中处于空闲状态的连接的最大值
jdbcMaxCheckoutTime: 连接被取出使用的最长时间,超过时间会被强制回收. 默认为 20000(20 秒)
jdbcMaxWaitTime: 这是一个底层配置,让连接池可以在长时间无法获得连接时, 打印一条日志,并重新尝试获取一个连接.(避免因为错误配置导致沉默的操作失败) 默认为 20000(20 秒)
使用 javax.sql.DataSource 配置
Activiti 的发布包中没有这些类, 要把对应的类放到 classpath 下
无论使用 JDBC 还是 DataSource,都可以设置下面的配置:
databaseType:
一般不用设置,因为可以自动通过数据库连接的元数据获取
只有自动检测失败时才需要设置.可能的值有:{h2,mysql,oracle,postgres,mssql,db2}
如果没使用默认的 H2 数据库就必须设置这项.这个配置会决定使用哪些创建/删除脚本和查询语句
databaseSchemaUpdate: 设置流程引擎启动和关闭时如何处理数据库表
false:默认, 检查数据库表的版本和依赖库的版本,如果版本不匹配就抛出异常
true: 构建流程引擎时,执行检查,如果需要就执行更新. 如果表不存在,就创建
create-drop: 构建流程引擎时创建数据库表,关闭流程引擎时删除这些表
JNDI 数据库配置
在默认情况下,Activiti 的数据库配置会放在 web 应用的 WEB-INF/classes 目录下的 db.properties 文件中. 这样做比较繁琐,因为要用户在每次发布时,都修改 Activiti 源码中的 db.properties 并重新编译 war 文件,或者解压缩 war 文件,修改其中的 db.properties
使用 JNDI(Java 命名和目录接口) 来获取数据库连接,连接是由 servlet 容器管理的,可以在 war 部署外边管理配置. 与 db.properties 相比,它也允许对连接进行更多的配置
JNDI 的使用
Activiti Explorer 和 Activiti Rest 应用从 db.properties 转换为使用 JNDI 数据库配置:
需要打开原始的 Spring 配置文件:
activiti-webapp-explorer/src/main/webapp/WEB-INF/activiti-standalone-context.xml
activiti-webapp-rest2/src/main/resources/activiti-context.xml
删除 dbProperties 和 dataSource 两个 bean,然后添加如下 bean:
我们需要添加包含了默认的 H2 配置的 context.xml 文件
如果已经有了 JNDI 配置,会覆盖这些配置.对应的配置文件 activiti-webapp-explorer2/src/main/webapp/META-INF/context.xml:
如果是 Activiti REST 应用,则添加 activiti-webapp-rest2/src/main/webapp/META-INF/context.xml:
最后删除 Activiti Explorer 和 Activiti Rest 两个应用中不再使用的 db.properties 文件
JNDI 的配置
JNDI 数据库配置会因为使用的 Servlet container 不同而不同
Tomcat 容器中的 JNDI 配置如下:
JNDI 资源配置在 CATALINA_BASE/conf/[enginename]/[hostname]/[warname].xml(对于 Activiti Explorer 来说,通常是在 CATALINA_BASE/conf/Catalina/localhost/activiti-explorer.war) 当应用第一次发布时,会把这个文件从 war 中复制出来.所以如果这个文件已经存在了,需要替换它.修改 JNDI 资源让应用连接 mysql 而不是 H2:
Activiti 支持的数据库
h2: 默认配置的数据库
mysql
oracle
postgres
db2
mssql
创建数据库表
创建数据库表的方法:
activiti-engine 的 jar 放到 classpath 下
添加对应的数据库驱动
把 Activiti 配置文件(activiti.cfg.xml)放到 classpath 下,指向你的数据库
执行 DbSchemaCreate 类的 main 方法
数据库表名理解
Activiti 的表都以**ACT_**开头, 第二部分是表示表的用途的两个字母标识.用途和服务的 API 对应
ACT_RE_*: RE 表示 repository. 这个前缀的表包含了流程定义和流程静态资源
ACT_RU_*: RU 表示 runtime. 这些是运行时的表,包含流程实例,任务,变量,异步任务等运行中的数据. Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录.这样运行时表可以一直很小速度很快
ACT_ID_*: ID 表示 identity. 这些表包含身份信息. 比如用户,组等等
ACT_HI_*: HI 表示 history. 这些表包含历史数据. 比如历史流程实例, 变量,任务等等
ACT_GE_*: 通用数据. 用于不同场景下
数据库升级
在执行更新之前要先使用数据库的备份功能备份数据库
默认情况下,每次构建流程引擎时都会进行版本检测.这一切都在应用启动或 Activiti webapp 启动时发生.如果 Activiti 发现数据库表的版本与依赖库的版本不同,就会抛出异常
对 activiti.cfg.xml 配置文件进行配置来升级:
然后,把对应的数据库驱动放到 classpath 里.升级应用的 Activiti 依赖,启动一个新版本的 Activiti 指向包含旧版本的数据库,将 databaseSchemaUpdate 设置为 true,Activiti 会自动将数据库表升级到新版本
当发现依赖和数据库表版本不通过时,也可以执行更新升级 DDL 语句
也可以执行数据库脚本,可以在 Activiti 下载页找到
启用 Job 执行器
JobExecutor 是管理一系列线程的组件,可以触发定时器(包含后续的异步消息).
在单元测试场景下,很难使用多线程.因此 API 允许查询 Job(ManagementService.createJobQuery) 和执行 Job (ManagementService.executeJob),
因此 Job 可以在单元测试中控制, 要避免与 job 执行器冲突,可以关闭它
默认,JobExecutor 在流程引擎启动时就会激活. 如果不想在流程引擎启动后自动激活 JobExecutor,可以设置
配置邮件服务器
Activiti 支持在业务流程中发送邮件,可以在配置中配置邮件服务器
配置 SMTP 邮件服务器来发送邮件
配置历史存储
Activiti 可以配置来定制历史存储信息
表达式和脚本暴露配置
默认情况下,activiti.cfg.xml 和 Spring 配置文件中所有 bean 都可以在表达式和脚本中使用
如果要限制配置文件中的 bean 的可见性,可以通过配置流程引擎配置的 beans 来配置
ProcessEngineConfiguration 的 beans 是一个 map.**当指定了这个参数,只有包含这个 map 中的 bean 可以在表达式和脚本中使用.**通过在 map 中指定的名称来决定暴露的 bean
配置部署缓存
因为流程定义的数据是不会改变的,为了避免每次使用访问数据库,所有流程定义在解析之后都会被缓存
默认情况下,不会限制这个缓存.如果想限制流程定义缓存,可以添加如下配置
这个配置会把默认的 HashMap 缓存替换成 LRU 缓存来提供限制. 这个配置的最佳值跟流程定义的总数有关,实际使用中会具体使用多少流程定义也有关
也可以注入自定义的缓存实现,这个 bean 必须实现 org.activiti.engine.impl.persistence.deploy.DeploymentCache 接口
类似的配置有 knowledgeBaseCacheLimit 和 knowledgeBaseCache, 它们是配置规则缓存的.只有流程中使用规则任务时才用
日志
从 Activiti 5.12 开始,所有日志(activiti,spring,,mybatis 等等)都转发给 slf4j 允许自定义日志实现
引入 Maven 依赖 log4j 实现,需要添加版本
使用 Maven 的实例,忽略版本
映射诊断上下文
Activiti 支持 slf4j 的 MDC 功能, 如下的基础信息会传递到日志中记录:
流程定义 ID: mdcProcessDefinitionID
流程实例 ID: mdcProcessInstanceID
分支 ID: mdcexecutionId
默认不会记录这些信息,可以配置日志使用期望的格式来显示它们,扩展通常的日志信息. 比如,通过 log4j 配置定义会让日志显示上面的信息:
当系统进行高风险任务,日志必须严格检查时,这个功能就非常有用,要使用日志分析的情况
事件处理
Activiti 中实现了一种事件机制,它允许在引擎触发事件时获得提醒
为对应的事件类型注册监听器,在这个类型的任何时间触发时都会收到提醒:
可以添加引擎范围的事件监听器,可以通过配置添加引擎范围的事件监听器在运行阶段使用 API
添加 event-listener 到特定流程定义的 BPMN XML 中
所有分发的事件,都是 org.activiti.engine.delegate.event.ActivitiEvent 的子类.事件包含 type,executionId,processInstanceId 和 processDefinitionId. 对应的事件会包含事件发生时对应上下文的额外信息
事件监听器实现
实现事件监听器要实现 org.activiti.engine.delegate.event.ActivitiEventListener.
下面监听器的实现会把所有监听到的事件打印到标准输出中,包括 job 执行的事件异常:
isFailOnException(): 决定了当事件分发时 onEvent(..) 方法抛出异常时的行为
返回 false,会忽略异常
返回 true,异常不会忽略,继续向上传播,迅速导致当前命令失败
当事件是一个 API 调用的一部分时(或其他事务性操作,比如 job 执行), 事务就会回滚
当事件监听器中的行为不是业务性时,建议返回 false
activiti 提供了一些基础的实现,实现了事件监听器的常用场景可以用来作为基类或监听器实现的样例
org.activiti.engine.delegate.event.BaseEntityEventListener:
这个事件监听器的基类可以用来监听实体相关的事件,可以针对某一类型实体,也可以是全部实体
隐藏了类型检测,并提供了三个需要重写的方法:
onCreate(..)
onUpdate(..)
onDelete(..)
当实体创建,更新,或删除时调用
对于其他实体相关的事件,会调用 onEntityEvent(..)
事件监听器的配置安装
把事件监听器配置到流程引擎配置中,会在流程引擎启动时激活,并在引擎启动过程中持续工作
eventListeners 属性需要 org.activiti.engine.delegate.event.ActivitiEventListener 的队列
通常,我们可以声明一个内部的 bean 定义,或使用 ref 引用已定义的 bean.下面的代码,向配置添加了一个事件监听器,任何事件触发时都会提醒它,无论事件是什么类型:
为了监听特定类型的事件
可以使用 typedEventListeners 属性
它需要一个 map 参数
map 的 key 是逗号分隔的事件名或单独的事件名
map 的 value 是 org.activiti.engine.delegate.event.ActivitiEventListener 队列
下面的代码演示了向配置中添加一个事件监听器,可以监听 job 执行成功或失败:
分发事件的顺序是由监听器添加时的顺序决定的
首先,会调用所有普通的事件监听器(eventListeners 属性),按照它们在 list 中的次序
然后,会调用所有对应类型的监听器(typedEventListeners 属性),对应类型的事件被触发
运行阶段添加监听器
通过 API:RuntimeService, 在运行阶段添加或删除额外的事件监听器:
运行阶段添加的监听器引擎重启后就消失
流程定义添加监听器
特定流程定义添加监听器:
监听器只会监听与这个流程定义相关的事件以及这个流程定义上发起的所有流程实例的事件
监听器实现:
可以使用全类名定义
引用实现了监听器接口的表达式
配置为抛出一个 message,signal,error 的 BPMN 事件
监听器执行自定义逻辑
下面代码为一个流程定义添加了两个监听器:
第一个监听器会接收所有类型的事件,它是通过全类名定义的
第二个监听器只接收作业成功或失败的事件,它使用了定义在流程引擎配置中的 beans 属性中的一个 bean
对于实体相关的事件,也可以设置为针对某个流程定义的监听器,实现只监听发生在某个流程定义上的某个类型实体事件.下面的代码演示了如何实现这种功能:
第一个例子:用于监听所有实体事件
第二个例子:用于监听特定类型的事件
entityType 支持的值有:
attachment
comment
execution
identity-link
job
process-instance
process-definition
task
监听抛出 BPMN 事件
另一种处理事件的方法是抛出一个 BPMN 事件:
只针对与抛出一个 activiti 事件类型的 BPMN 事件, 抛出一个 BPMN 事件,在流程实例删除时,会导致一个错误
下面的代码演示了如何在流程实例中抛出一个 signal,把 signal 抛出到外部流程(全局),在流程实例中抛出一个消息事件,在流程实例中抛出一个错误事件.除了使用 class 或 delegateExpression, 还使用了 throwEvent 属性,通过额外属性,指定了抛出事件的类型
如果需要声明额外的逻辑,是否抛出 BPMN 事件,可以扩展 activiti 提供的监听器类:
在子类中重写 isValidEvent(ActivitiEvent event), 可以防止抛出 BPMN 事件.对应的类是:
org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener
org.activiti.engine.test.api.event.SignalThrowingEventListenerTest
org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener
流程定义监听器注意点
事件监听器只能声明在 process 元素中,作为 extensionElements 的子元素.监听器不能定义在流程的单个 activity 下
delegateExpression 中的表达式无法访问 execution 上下文,这与其他表达式不同(比如 gateway).它只能引用定义在流程引擎配置的 beans 属性中声明的 bean, 或者使用 spring(未使用 beans 属性)中所有实现了监听器接口的 spring-bean
使用监听器的 class 属性时,只会创建一个实例.监听器实现不会依赖成员变量,是多线程安全的
当一个非法的事件类型用在 events 属性或 throwEvent 中时,流程定义发布时就会抛出异常(会导致部署失败)
如果 class 或 delegateExecution 由问题:类不存在,不存在的 bean 引用,或代理类没有实现监听器接口
在流程启动时抛出异常
在第一个有效的流程定义事件被监听器接收时
所以要保证引用的类正确的放在 classpath 下,表达式也要引用一个有效的实例
通过 API 分发事件
Activiti 我们提供了通过 API 使用事件机制的方法,允许触发定义在引擎中的任何自定义事件
建议只触发类型为 CUSTOM 的 ActivitiEvents.可以通过 RuntimeService 触发事件:
支持的事件类型
引擎中每个事件类型都对应 org.activiti.engine.delegate.event.ActivitiEventType 中的一个枚举值
引擎内部所有 ENTITY_* 事件都是与实体相关的,实体事件与实体的对应关系:
[ENTITY_CREATED],[ENTITY_INITIALIZED],[ENTITY_DELETED]:
Attachment
Comment
Deployment
Execution
Group
IdentityLink
Job
Model
ProcessDefinition
ProcessInstance
Task
User
ENTITY_UPDATED:
Attachment
Deployment
Execution
Group
IdentityLink
Job
Model
ProcessDefinition
ProcessInstance
Task
User
ENTITY_SUSPENDED, ENTITY_ACTIVATED:
ProcessDefinition
ProcessInstance
Execution
Task
注意
只有同一个流程引擎中的事件会发送给对应的监听器
如果有很多引擎在同一个数据库运行,事件只会发送给注册到对应引擎的监听器.其他引擎发生的事件不会发送给这个监听器,无论实际上它们运行在同一个或不同的 JVM 中
对应的事件类型都包含对应的实体.根据类型或事件,这些实体不能再进行更新(比如,当实例以被删除).可能的话,使用事件提供的 EngineServices 来以安全的方式来操作引擎.即使如此,也要小心的对事件对应的实体进行更新,操作
没有对应历史的实体事件,因为它们都有运行阶段的对应实体
版权声明: 本文为 InfoQ 作者【攻城狮Chova】的原创文章。
原文链接:【http://xie.infoq.cn/article/8d1829afce4cccc3394ece8da】。文章转载请联系作者。
评论