最近学习工做流 推荐一个activiti 的教程文档

2019年11月08日 阅读数:1162
这篇文章主要向大家介绍最近学习工做流 推荐一个activiti 的教程文档,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

全文地址:http://www.mossle.com/docs/activiti/javascript

Activiti 5.15 用户手册


Table of Contentsphp

1. 简介
协议
下载
源码
必要的软件
JDK 6+
Eclipse Indigo 和 Juno
报告问题
试验性功能
内部实现类
2. 开始学习
一分钟入门
安装Activiti
安装Activiti数据库
引入Activiti jar和依赖
下一步
3. 配置
建立ProcessEngine
ProcessEngineConfiguration bean
数据库配置
支持的数据库
建立数据库表
理解数据库表的命名
数据库升级
启用Job执行器
配置邮件服务器
配置历史
为表达式和脚本暴露配置
配置部署缓存
日志
映射诊断上下文
事件处理
事件监听器实现
配置与安装
在运行阶段添加监听器
为流程定义添加监听器
经过API分发事件
支持的事件类型
附加信息
4. Activiti API
流程引擎的API和服务
异常策略
使用Activiti的服务
发布流程
启动一个流程实例
完成任务
挂起,激活一个流程
更多知识
查询API
表达式
单元测试
调试单元测试
web应用中的流程引擎
5. Spring集成
ProcessEngineFactoryBean
事务
表达式
资源的自动部署
单元测试
基于注解的配置
JPA 和 Hibernate 4.2.x
6. 部署
业务文档
编程式部署
经过Activiti Explorer控制台部署
外部资源
Java类
在流程中使用Spring beans
建立独立应用
流程定义的版本
提供流程图片
自动生成流程图片
类别
7. BPMN 2.0介绍
啥是BPMN?
定义一个流程
快速起步:10分钟教程
前提
目标
用例
流程图
XML内容
启动一个流程实例
任务列表
领取任务
完成任务
结束流程
代码总结
更多思考
8. BPMN 2.0结构
自定义扩展
事件(Event)
事件定义
定时器事件定义
错误事件定义
信号事件定义
消息事件定义
开始事件
空开始事件
定时开始事件
消息开始事件
信号开始事件
错误开始事件
结束事件
空结束事件
错误结束事件
取消结束事件
边界事件
定时边界事件
错误边界事件
信号边界事件
消息边界事件
取消边界事件
补偿边界事件
中间捕获事件
定时中间捕获事件
信号中间捕获事件
消息中间捕获事件
内部触发事件
中间触发空事件
信号中间触发事件
补偿中间触发事件
顺序流
描述
图形标记
XML内容
条件顺序流
默认顺序流
网关
排他网关
并行网关
包含网关
基于事件网关
任务
用户任务
脚本任务
Java服务任务
Web Service任务
业务规则任务
邮件任务
Mule任务
Camel任务
手工任务
Java接收任务
Shell任务
执行监听器
任务监听器
多实例(循环)
补偿处理器
子流程和调用节点
子流程
事件子流程
事务子流程
调用活动(子流程)
事务和并发
异步操做
排他任务
流程实例受权
数据对象
9. 表单
表单属性
外置表单的渲染
10. JPA
要求
配置
用法
简单例子
查询JPA流程变量
使用Spring beans和JPA结合的高级例子
11. 历史
查询历史
HistoricProcessInstanceQuery
HistoricVariableInstanceQuery
HistoricActivityInstanceQuery
HistoricDetailQuery
HistoricTaskInstanceQuery
历史配置
审计目的的历史
12. Eclipse Designer
Installation
Activiti Designer 编辑器的特性
Activiti Designer 的BPMN 特性
Activiti Designer 部署特性
扩展Activiti Designer
定制画板
校验图形和导出到自定义的输出格式
13. Activiti Explorer
流程图
任务
启动流程实例
个人流程实例
管理
报表
报告数据JSON
实例流程
报告开始表单
流程例子
修改数据库
14. Activiti Modeler
编辑模型
导入模型
把发布的流程定义转换成可编辑的模型
把模型导出成BPMN XML
把模型部署到Activiti引擎中
15. REST API
通用Activiti REST原则
安装与认证
使用Tomcat
方法和返回值
错误响应体
请求参数
部署
部署列表
得到一个部署
建立新部署
删除部署
列出部署内的资源
获取部署资源
获取部署资源的内容
流程定义
流程定义列表
得到一个流程定义
更新流程定义的分类
得到一个流程定义的资源内容
得到流程定义的BPMN模型
暂停流程定义
激活流程定义
得到流程定义的全部候选启动者
为流程定义添加一个候选启动者
删除流程定义的候选启动者
得到流程定义的一个候选启动者
模型
得到模型列表
得到一个模型
更新模型
新建模型
删除模型
得到模型的可编译源码
设置模型的可编辑源码
得到模型的附加可编辑源码
设置模型的附加可编辑源码
流程实例
得到流程实例
删除流程实例
激活或挂起流程实例
启动流程实例
显示流程实例列表
查询流程实例
得到流程实例的流程图
得到流程实例的参与者
为流程实例添加一个参与者
删除一个流程实例的参与者
列出流程实例的变量
得到流程实例的一个变量
建立(或更新)流程实例变量
更新一个流程实例变量
建立一个新的二进制流程变量
更新一个二进制的流程实例变量
分支
获取一个分支
对分支执行操做
得到一个分支的全部活动节点
获取分支列表
查询分支
获取分支的变量列表
得到分支的一个变量
新建(或更新)分支变量
更新分支变量
建立一个二进制变量
更新已经已存在的二进制分支变量
任务
获取任务
任务列表
查询任务
更新任务
操做任务
删除任务
得到任务的变量
获取任务的一个变量
获取变量的二进制数据
建立任务变量
建立二进制任务变量
更新任务的一个已有变量
更新一个二进制任务变量
删除任务变量
删除任务的全部局部变量
得到任务的全部IdentityLink
得到一个任务的全部组或用户的IdentityLink
得到一个任务的一个IdentityLink
为任务建立一个IdentityLink
删除任务的一个IdentityLink
为任务建立评论
得到任务的全部评论
得到任务的一个评论
删除任务的一条评论
得到任务的全部事件
得到任务的一个事件
为任务建立一个附件,包含外部资源的连接
为任务建立一个附件,包含附件文件
得到任务的全部附件
得到任务的一个附件
获取附件的内容
删除任务的一个附件
历史
得到历史流程实例
历史流程实例列表
查询历史流程实例
删除历史流程实例
获取历史流程实例的IdentityLink
获取历史流程实例变量的二进制数据
为历史流程实例建立一条新评论
得到一个历史流程实例的全部评论
得到历史流程实例的一条评论
删除历史流程实例的一条评论
得到单独历史任务实例
获取历史任务实例
查询历史任务实例
删除历史任务实例
得到历史任务实例的IdentityLink
获取历史任务实例变量的二进制值
获取历史活动实例
查询历史活动实例
列出历史变量实例
查询历史变量实例
获取历史任务实例变量的二进制值
获取历史细节
查询历史细节
获取历史细节变量的二进制数据
表单
获取表单数据
提交任务表单数据
数据库表
表列表
得到一张表
得到表的列信息
得到表的行数据
引擎
得到引擎属性
得到引擎信息
运行时
接收信号事件
做业
获取一个做业
删除做业
执行做业
得到做业的异常堆栈
得到做业列表
用户
得到一个用户
获取用户列表
更新用户
建立用户
删除用户
获取用户图片
更新用户图片
列出用户列表
获取用户信息
更新用户的信息
建立用户信息条目
删除用户的信息
群组
得到群组
获取群组列表
更新群组
建立群组
删除群组
获取群组的成员
为群组添加一个成员
删除群组的成员
传统REST - 通用方法
资源
上传发布
获取发布
获取发布资源
获取发布的一个资源
删除发布
删除发布
引擎
获取流程引擎
流程
流程定义列表
得到流程定义表单属性
得到流程定义表单资源
获取流程定义图
启动流程实例
流程实例列表
得到流程实例细节
得到流程实例图
得到流程实例的任务
继续特定流程实例的活动(receiveTask)
触发特定流程实例的信号
任务
得到任务简介
任务列表
获取任务
获取任务表单
执行任务操做
表单属性列表
为任务添加一个附件
得到任务附件
为任务添加一个url
身份
登陆
得到用户
列出用户的群组
查询用户
建立用户
为群组添加用户
从群组删除用户
得到用户图片
得到群组
群组用户列表
查询群组
建立群组
为群组添加用户
为群组删除用户
管理
做业列表
得到做业
执行一个做业
执行多个做业
数据库表列表
得到表元数据
得到表数据
16. 集成CDI
设置activiti-cdi
查找流程引擎
配置Process Engine
发布流程
基于CDI环境的流程执行
与流程实例进行关联交互
声明式流程控制
在流程中引用bean
使用@BusinessProcessScoped beans
注入流程变量
接收流程事件
更多功能
已知的问题
17. 集成LDAP
用法
用例
配置
属性
为Explorer集成LDAP
18. 高级功能
监听流程解析
支持高并发的UUID id生成器
多租户
执行自定义SQL
使用ProcessEngineConfigurator实现高级流程引擎配置
启用安全的BPMN 2.0 xml

List of Tablescss

2.1. 示例用户
2.2. webapp工具
3.1. 支持的数据库
3.2. 支持的事件
6.1.
6.2.
6.3.
8.1. 邮件服务器配置
8.2. 邮件服务器配置
8.3. Mule服务器配置
8.4. 终端URL:
8.5. 已有的camel行为:
8.6. 已有的camel行为:
8.7. Shell任务参数配置
15.1. HTTP方法和对应操做
15.2. HTTP方法响应代码
15.3. URL查询参数类型
15.4. JSON参数类型
15.5. 查询JSON参数
15.6. 查询JSON参数
15.7. 默认查询JSON类型
15.8. 变量JSON属性
15.9. 变量类型
15.10. URL查询参数
15.11. REST响应码
15.12. 得到一个部署 - URL参数
15.13. 得到一个部署 - 响应码
15.14. 建立新部署 - 响应码
15.15. 删除部署 - URL参数
15.16. 删除部署 - 响应码
15.17. 列出部署内的资源 - URL参数
15.18. 列出部署内的资源 - 响应码
15.19. 获取部署资源 - URL参数
15.20. 获取部署资源 - 响应码
15.21. 获取部署资源的内容 - URL参数
15.22. 获取部署资源的内容 - 响应码
15.23. 流程定义列表 - URL参数
15.24. 流程定义列表 - 响应码
15.25. 得到一个流程定义 - URL参数
15.26. 得到一个流程定义 - 响应码
15.27. 更新流程定义的分类 - 响应码
15.28. 得到一个流程定义的资源内容 - URL参数
15.29. 得到流程定义的BPMN模型 - URL参数
15.30. 得到流程定义的BPMN模型 - 响应码
15.31. 暂停流程定义 - 请求的JSON参数
15.32. 暂停流程定义 - 响应码
15.33. 激活流程定义 - 响应码
15.34. 得到流程定义的全部候选启动者 - URL参数
15.35. 得到流程定义的全部候选启动者 - 响应码
15.36. 为流程定义添加一个候选启动者 - URL参数
15.37. 为流程定义添加一个候选启动者 - 响应码
15.38. 删除流程定义的候选启动者 - URL参数
15.39. 删除流程定义的候选启动者 - 响应码
15.40. 得到流程定义的一个候选启动者 - URL参数
15.41. 得到流程定义的一个候选启动者 - 响应码
15.42. 得到模型列表 - URL参数
15.43. 得到模型列表 - 响应码
15.44. 得到一个模型 - URL参数
15.45. 得到一个模型 - 响应码
15.46. 更新模型 - 响应码
15.47. 新建模型 - 响应码
15.48. 删除模型 - URL参数
15.49. 删除模型 - 响应码
15.50. 得到模型的可编译源码 - URL参数
15.51. 得到模型的可编译源码 - 响应码
15.52. 设置模型的可编辑源码 - URL参数
15.53. 设置模型的可编辑源码 - 响应码
15.54. 得到模型的附加可编辑源码 - URL参数
15.55. 得到模型的附加可编辑源码 - 响应码
15.56. 设置模型的附加可编辑源码 - URL参数
15.57. 设置模型的附加可编辑源码 - 响应码
15.58. 得到流程实例 - URL参数
15.59. 得到流程实例 - 响应码
15.60. 删除流程实例 - URL参数
15.61. 删除流程实例 - 响应码
15.62. 激活或挂起流程实例 - URL参数
15.63. 激活或挂起流程实例 - 响应码
15.64. 启动流程实例 - 响应码
15.65. 显示流程实例列表 - URL参数
15.66. 显示流程实例列表 - 响应码
15.67. 查询流程实例 - 响应码
15.68. 得到流程实例的流程图 - URL参数
15.69. 得到流程实例的流程图 - 响应码
15.70. 得到流程实例的参与者 - URL参数
15.71. 得到流程实例的参与者 - 响应码
15.72. 为流程实例添加一个参与者 - URL参数
15.73. 为流程实例添加一个参与者 - 响应码
15.74. 删除一个流程实例的参与者 - URL参数
15.75. 删除一个流程实例的参与者 - 响应码
15.76. 列出流程实例的变量 - URL参数
15.77. 列出流程实例的变量 - 响应码
15.78. 得到流程实例的一个变量 - URL参数
15.79. 得到流程实例的一个变量 - 响应码
15.80. 建立(或更新)流程实例变量 - URL参数
15.81. 建立(或更新)流程实例变量 - 响应码
15.82. 更新一个流程实例变量 - URL参数
15.83. 更新一个流程实例变量 - 响应码
15.84. 建立一个新的二进制流程变量 - URL参数
15.85. 建立一个新的二进制流程变量 - 响应码
15.86. 更新一个二进制的流程实例变量 - URL参数
15.87. 更新一个二进制的流程实例变量 - 响应码
15.88. 获取一个分支 - URL参数
15.89. 获取一个分支 - 响应码
15.90. 对分支执行操做 - URL参数
15.91. 对分支执行操做 - 响应码
15.92. 得到一个分支的全部活动节点 - URL参数
15.93. 得到一个分支的全部活动节点 - 响应码
15.94. 获取分支列表 - URL参数
15.95. 获取分支列表 - 响应码
15.96. 查询分支 - 响应码
15.97. 获取分支的变量列表 - URL参数
15.98. 获取分支的变量列表 - 响应码
15.99. 得到分支的一个变量 - URL参数
15.100. 得到分支的一个变量 - 响应码
15.101. 新建(或更新)分支变量 - URL参数
15.102. 新建(或更新)分支变量 - 响应码
15.103. 更新分支变量 - URL参数
15.104. 更新分支变量 - 响应码
15.105. 建立一个二进制变量 - URL参数
15.106. 建立一个二进制变量 - 响应码
15.107. 更新已经已存在的二进制分支变量 - URL参数
15.108. 更新已经已存在的二进制分支变量 - 响应码
15.109. 获取任务 - URL参数
15.110. 获取任务 - 响应码
15.111. 任务列表 - URL参数
15.112. 任务列表 - 响应码
15.113. 查询任务 - 响应码
15.114. 更新任务 - 响应码
15.115. 操做任务 - 响应码
15.116. >删除任务 - URL参数
15.117. >删除任务 - 响应码
15.118. 得到任务的变量 - URL参数
15.119. 得到任务的变量 - 响应码
15.120. 获取任务的一个变量 - URL参数
15.121. 获取任务的一个变量 - 响应码
15.122. 获取变量的二进制数据 - URL参数
15.123. 获取变量的二进制数据 - 响应码
15.124. 建立任务变量 - URL参数
15.125. 建立任务变量 - 响应码
15.126. 建立二进制任务变量 - URL参数
15.127. 建立二进制任务变量 - 响应码
15.128. 更新任务的一个已有变量 - URL参数
15.129. 更新任务的一个已有变量 - 响应码
15.130. 更新一个二进制任务变量 - URL参数
15.131. 更新一个二进制任务变量 - 响应码
15.132. 删除任务变量 - URL参数
15.133. 删除任务变量 - 响应码
15.134. 删除任务的全部局部变量 - URL参数
15.135. 删除任务的全部局部变量 - 响应码
15.136. 得到任务的全部IdentityLink - URL参数
15.137. 得到任务的全部IdentityLink - 响应码
15.138. 得到一个任务的全部组或用户的IdentityLink - URL参数
15.139. 得到一个任务的全部组或用户的IdentityLink - 响应码
15.140. 为任务建立一个IdentityLink - URL参数
15.141. 为任务建立一个IdentityLink - 响应码
15.142. 删除任务的一个IdentityLink - URL参数
15.143. 删除任务的一个IdentityLink - 响应码
15.144. 为任务建立评论 - URL参数
15.145. 为任务建立评论 - 响应码
15.146. 得到任务的全部评论 - URL参数
15.147. 得到任务的全部评论 - 响应码
15.148. 得到任务的一个评论 - URL参数
15.149. 得到任务的一个评论 - 响应码
15.150. 删除任务的一条评论 - URL参数
15.151. 删除任务的一条评论 - 响应码
15.152. 得到任务的全部事件 - URL参数
15.153. 得到任务的全部事件 - 响应码
15.154. 得到任务的一个事件 - URL参数
15.155. 得到任务的一个事件 - 响应码
15.156. 为任务建立一个附件,包含外部资源的连接 - URL参数
15.157. 为任务建立一个附件,包含外部资源的连接 - 响应码
15.158. 为任务建立一个附件,包含附件文件 - URL参数
15.159. 为任务建立一个附件,包含附件文件 - 响应码
15.160. 得到任务的全部附件 - URL参数
15.161. 得到任务的全部附件 - 响应码
15.162. 得到任务的一个附件 - URL参数
15.163. 得到任务的一个附件 - 响应码
15.164. 获取附件的内容 - URL参数
15.165. 获取附件的内容 - 响应码
15.166. 删除任务的一个附件 - URL参数
15.167. 删除任务的一个附件 - 响应码
15.168. 得到历史流程实例 - 响应码
15.169. 历史流程实例列表 - URL参数
15.170. 历史流程实例列表 - 响应码
15.171. 查询历史流程实例 - 响应码
15.172. 响应码
15.173. 响应码
15.174. 获取历史流程实例变量的二进制数据 - 响应码
15.175. 为历史流程实例建立一条新评论 - URL参数
15.176. 为历史流程实例建立一条新评论 - 响应码
15.177. 得到流程实例的全部评论 - URL参数
15.178. 得到流程实例的全部评论 - 响应码
15.179. 得到历史流程的一条评论 - URL参数
15.180. 得到历史流程的一条评论 - 响应码
15.181. 删除历史流程实例的一条评论 - URL参数
15.182. 删除历史流程实例的一条评论 - 响应码
15.183. 得到单独历史任务实例 - 响应码
15.184. 获取历史任务实例 - URL参数
15.185. 获取历史任务实例 - 响应码
15.186. 查询历史任务实例 - 响应码
15.187. 响应码
15.188. 响应码
15.189. 获取历史任务实例变量的二进制值 - 响应码
15.190. 获取历史活动实例 - URL参数
15.191. 获取历史活动实例 - 响应码
15.192. 查询历史活动实例 - 响应码
15.193. 列出历史变量实例 - URL参数
15.194. 列出历史变量实例 - 响应码
15.195. 查询历史变量实例 - 响应码
15.196. 获取历史任务实例变量的二进制值 - 响应码
15.197. 获取历史细节 - URL参数
15.198. 获取历史细节 - 响应码
15.199. 查询历史细节 - 响应码
15.200. 获取历史细节变量的二进制数据 - 响应码
15.201. 获取表单数据 - URL参数
15.202. 获取表单数据 - 响应码
15.203. 提交任务表单数据 - 响应码
15.204. 表列表 - 响应码
15.205. 得到一张表 - URL参数
15.206. 得到一张表 - 响应码
15.207. 得到表的列信息 - URL参数
15.208. 得到表的列信息 - 响应码
15.209. 得到表的行数据 - URL参数
15.210. 得到表的行数据 - URL参数
15.211. 得到表的行数据 - 响应码
15.212. 得到引擎属性 - 响应码
15.213. 得到引擎信息 - 响应码
15.214. 接收信号事件 - JSON体参数
15.215. 接收信号事件 - 响应码
15.216. 获取一个做业 - URL参数
15.217. 获取一个做业 - 响应码
15.218. 删除做业 - URL参数
15.219. 删除做业 - 响应码
15.220. 执行做业 - 请求的JSON参数
15.221. 执行做业 - 响应码
15.222. 得到做业的异常堆栈 - URL参数
15.223. 得到做业的异常堆栈 - 响应码
15.224. 得到做业列表 - URL参数
15.225. 得到做业列表 - 响应码
15.226. 得到一个用户 - URL参数
15.227. 得到一个用户 - 响应码
15.228. 获取用户列表 - URL参数
15.229. 获取用户列表 - 响应码
15.230. 更新用户 - 响应码
15.231. 建立用户 - 响应码
15.232. 删除用户 - URL参数
15.233. 删除用户 - 响应码
15.234. 获取用户图片 - URL参数
15.235. 获取用户图片 - 响应码
15.236. 更新用户图片 - URL参数
15.237. 更新用户图片 - 响应码
15.238. 列出用户列表 - URL参数
15.239. 列出用户列表 - 响应码
15.240. 获取用户信息 - URL参数
15.241. 获取用户信息 - 响应码
15.242. 更新用户的信息 - URL参数
15.243. 更新用户的信息 - 响应码
15.244. 建立用户信息条目 - URL参数
15.245. 建立用户信息条目 - 响应码
15.246. 删除用户的信息 - URL参数
15.247. 删除用户的信息 - 响应码
15.248. 得到群组 - URL参数
15.249. 得到群组 - 响应码
15.250. 获取群组列表 - URL参数
15.251. 获取群组列表 - 响应码
15.252. 更新群组 - 响应码
15.253. 建立群组 - 响应码
15.254. 删除群组 - URL参数
15.255. 删除群组 - 响应码
15.256. 为群组添加一个成员 - URL参数
15.257. 为群组添加一个成员 - 响应码
15.258. 删除群组的成员 - URL参数
15.259. 删除群组的成员 - 响应码
17.1. LDAP配置属性
17.2. 高级属性

Chapter 1. 简介

协议

Activiti是基于Apache V2协议发布的。前端

源码

发布包里包含大部分的已经打好jar包的源码。  若是想找到并构建完整的源码库,请参考   wiki “构建发布包”。  node

必要的软件

JDK 6+

Activiti须要运行在JDK 6或以上版本上。    进入 Oracle Java SE 下载页面    点击 "下载 JDK"按钮。页面上也提供了安装的方法。    为了验证是否安装成功,能够在命令行中执行 java -version。 它将会打印出安装的JDK的版本。    mysql

Eclipse Indigo 和 Juno

(译者注:Eclipse 3.7 版本代号 Indigo 靛青,     Eclipse 4.2 版本代号 Juno 朱诺)。 在Eclipse下载页面下载你选择的eclipse发布包。 解压下载文件,你就能够经过eclipse目录下的eclipse文件启动它。 此外,在该用户指南后面,专门有一章介绍安装eclipse设计器插件。    ios

报告问题

任何一个自觉的开发者都应该看看   如何聪明的提出问题。  git

看完以后,你能够在用户论坛上进行提问和评论,  或者在JIRA问题跟踪系统中建立问题。    

Note

虽然Activiti已经托管在GitHub上了,可是问题不该该提交到GitHub的问题跟踪系统上。若是你想报告一个问题, 不要建立一个GitHub的问题,而是应该使用JIRA。      

 

试验性功能

那些标记着 [EXPERIMENTAL] 的章节表示功能还没有稳定。  

全部包名中包含 .impl. 的类都是内部实现类,都是不保证稳定的。  不过,若是用户指南把哪些类列为配置项,那么它们能够认为是稳定不变的。  

内部实现类

在jar包中,全部包名中包含.impl.(好比:org.activiti.engine.impl.pvm.delegate)的类都是实现类,  它们应该被视为流程引擎内部的类。对于这些类和接口都不可以保证其稳定性。  

Chapter 2. 开始学习

一分钟入门

  从Activiti网站下载Activiti Explorer的WAR文件后,   能够按照下列步骤以默认配置运行样例。   你须要一个Java 运行环境和    Apache Tomcat    (其实,任何提供了servlet功能的web容器均可以正常运行。可是咱们主要是使用tomcat进行的测试)。      

  • 把下载的activiti-explorer.war复制到Tomcat的webapps目录下。

  • 执行Tomcat的bin目录下的startup.bat或startup.sh启动服务器。

  • Tomcat启动后,打开浏览器访问http://localhost:8080/activiti-explorer。 使用kermit/kermit登陆。          

  这样就行了!Activiti Explorer默认使用H2内存数据库,若是你想使用其余数据库   请参考这里。    

安装Activiti

要安装Activiti你须要一个     Java运行环境 和     Apache Tomcat。    还要确认设置好JAVA_HOME系统变量。 不一样的操做系统下的设置方法是不一样的。    

要运行Activiti Explorer和REST web应用,你要从Activiti的下载页下载WAR文件, 复制到Tomcat安装目录下webapps目录下。 默认Explorer应用使用的内存数据库已经包含了示例流程,用户和群组信息。    

下面是示例中能够使用的用户:

Table 2.1. 示例用户

帐号 密码 角色
kermit kermit admin
gonzo gonzo manager
fozzie fozzie user

如今,你能够访问下列web应用:

Table 2.2. webapp工具

Webapp名称 URL 描述  
Activiti Explorer http://localhost:8080/activiti-explorer 流程引擎的用户控制台。使用它来启动新流程,分配任务, 查看并认领任务,等等。这个工具也能够用来管理Activiti引擎。              

注意Activiti Explorer演示实例只是一种简单快速展现Activiti的功能的方式。   可是并非说只能使用这种方式使用Activiti。    Activiti只是一个jar,   能够内嵌到任何Java环境中:swing或者Tomcat, JBoss, WebSphere等等。   也能够把Activiti做为一个典型的单独运行的BPM服务器运行。   只要java能够作的,Activiti也能够。    

安装Activiti数据库

就像在一分钟入门里说过的,Activiti Explorer默认使用H2内存数据库。 要让Activiti使用独立运行的H2数据库或者其余数据库, 能够修改Activiti Explorer web应用WEB-INF/classes目录下的db.properties。    

另外,注意Activiti Explorer自动生成了演示用的默认用户和群组,流程定义,数据模型。 要想禁用这个功能,要修改WEB-INF目录下的activiti-standalone-context.xml。 能够使用下面的demoDataGenerator bean定义代码彻底禁用安装默认数据。从代码中也能够看出,咱们能够单独启用或禁用每一项功能。    

      <beanid="demoDataGenerator"class="org.activiti.explorer.demo.DemoDataGenerator">         <propertyname="processEngine"ref="processEngine"/>         <propertyname="createDemoUsersAndGroups"value="false"/>         <propertyname="createDemoProcessDefinitions"value="false"/>         <propertyname="createDemoModels"value="false"/>       </bean>     

引入Activiti jar和依赖

    为了引用Activiti jar和依赖,咱们推荐使用 Maven(或Ivy), 它简化了咱们之间的依赖管理。 参考http://www.activiti.org/community.html#maven.repository 来为你的项目引入必须的jar包。    

    若是不想用Maven,你也能够本身把这些jar引入到你的项目中。 Activiti下载zip包包含了一个libs目录, 包含了全部Activiti的jar包(和源代码jar包)。依赖没有用这种方式发布。 Activiti引擎必须的依赖以下所示(经过mvn dependency:tree生成):       

org.activiti:activiti-engine:jar:5.12.1+- org.apache.commons:commons-email:jar:1.2:compile |  +- javax.mail:mail:jar:1.4.1:compile |  \- javax.activation:activation:jar:1.1:compile +- org.apache.commons:commons-lang3:jar:3.1:compile +- org.mybatis:mybatis:jar:3.1.1:compile +- org.springframework:spring-beans:jar:3.1.2.RELEASE:compile |  \- org.springframework:spring-core:jar:3.1.2.RELEASE:compile |     +- org.springframework:spring-asm:jar:3.1.2.RELEASE:compile |     \- commons-logging:commons-logging:jar:1.1.1:compile \- joda-time:joda-time:jar:2.1:compile        

注意:只有使用了mail service task才必须引入mail依赖jar。    

    全部依赖能够在Activiti 源码的模块中, 经过mvn dependency:copy-dependencies下载。    

下一步

    使用Activiti Explorer web应用 是一个熟悉Activiti概念和功能的好办法。可是, Activiti的主要目标是为你本身的应用添增强大的BPM和工做流功能。 下面的章节会帮助你熟悉 如何在你的环境中使用Activiti进行编程:        

  • 配置章节 会教你如何设置Activiti, 如何得到ProcessEngine类的实例, 它是全部Activiti引擎功能的中心入口。                

  • API章节会带领你了解创建Activiti API的服务。 这些服务用简便的方法提供了Activiti引擎的强大功能, 它们能够使用在任何Java环境下。                

  • 对深刻了解BPMN 2.0,Activiti引擎中流程的编写结构感兴趣吗? 请继续浏览BPMN 2.0 章节。                

 

Chapter 3. 配置

建立ProcessEngine

    Activiti流程引擎的配置文件是名为activiti.cfg.xml的XML文件。    注意这与使用Spring方式建立流程引擎    是同样的。    

    得到ProcessEngine最简单的办法是    使用org.activiti.engine.ProcessEngines类:    

ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine()

它会在classpath下搜索activiti.cfg.xml,    并基于这个文件中的配置构建引擎。    下面代码展现了实例配置。    后面的章节会给出配置参数的详细介绍。    

<beansxmlns="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">
 
<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
   
<propertyname="jdbcUrl"value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"/>     <propertyname="jdbcDriver"value="org.h2.Driver"/>     <propertyname="jdbcUsername"value="sa"/>     <propertyname="jdbcPassword"value=""/>
   
<propertyname="databaseSchemaUpdate"value="true"/>
   
<propertyname="jobExecutorActivate"value="false"/>
   
<propertyname="mailServerHost"value="mail.my-corp.com"/>     <propertyname="mailServerPort"value="5025"/>   </bean>
</beans>

 

注意配置XML文件实际上是一个spring的配置文件。       但不是说Activiti只能用在Spring环境中!      咱们只是利用了Spring的解析和依赖注入功能      来构建引擎。    

      配置文件中使用的ProcessEngineConfiguration能够经过编程方式建立。      能够配置不一样的bean id(好比,第三行)。      

ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault();ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource);ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource,String beanName);ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream);ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream,String beanName);

也能够不使用配置文件,基于默认建立配置   (参考各类支持类)   

ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();

全部这些ProcessEngineConfiguration.createXXX()方法都返回    ProcessEngineConfiguration,后续能够调整成所需的对象。   在调用buildProcessEngine()后,   就会建立一个ProcessEngine:   

ProcessEngine processEngine =ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration()   .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)   .setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000")   .setJobExecutorActivate(true)   .buildProcessEngine();

 

ProcessEngineConfiguration bean

activiti.cfg.xml必须包含一个id为'processEngineConfiguration'的bean。      

 <beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

这个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环境下使用流程引擎。            参考Spring集成章节。          

  • org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration:             单独运行流程引擎,并使用JTA事务。          

 

数据库配置

Activiti可能使用两种方式配置数据库。      第一种方式是定义数据库配置参数:      

  • jdbcUrl: 数据库的JDBC URL。          

  • jdbcDriver: 对应不一样数据库类型的驱动。          

  • jdbcUsername: 链接数据库的用户名。          

  • jdbcPassword: 链接数据库的密码。          

 

      基于JDBC参数配置的数据库链接      会使用默认的MyBatis链接池。      下面的参数能够用来配置链接池(来自MyBatis参数):      

  • jdbcMaxActiveConnections:             链接池中处于被使用状态的链接的最大值。默认为10。          

  • jdbcMaxIdleConnections:             链接池中处于空闲状态的链接的最大值。          

  • jdbcMaxCheckoutTime:             链接被取出使用的最长时间,超过期间会被强制回收。            默认为20000(20秒)。          

  • jdbcMaxWaitTime:             这是一个底层配置,让链接池能够在长时间没法得到链接时,            打印一条日志,并从新尝试获取一个链接。(避免由于错误配置致使沉默的操做失败)。            默认为20000(20秒)。          

 

      示例数据库配置:      

<propertyname="jdbcUrl"value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"/><propertyname="jdbcDriver"value="org.h2.Driver"/><propertyname="jdbcUsername"value="sa"/><propertyname="jdbcPassword"value=""/>       

 

也能够使用javax.sql.DataSource。      (好比,Apache Commons的DBCP):      

<beanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource">   <propertyname="driverClassName"value="com.mysql.jdbc.Driver"/>   <propertyname="url"value="jdbc:mysql://localhost:3306/activiti"/>   <propertyname="username"value="activiti"/>   <propertyname="password"value="activiti"/>   <propertyname="defaultAutoCommit"value="false"/></bean>
<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
   
<propertyname="dataSource"ref="dataSource"/>     ...      

注意,Activiti的发布包中没有这些类。      你要本身把对应的类(好比,从DBCP里)放到你的classpath下。    

      不管你使用JDBC仍是DataSource的方式,均可以设置下面的配置:      

  • databaseType:             通常不用设置,由于能够自动经过数据库链接的元数据获取。            只有自动检测失败时才须要设置。            可能的值有:{h2, mysql, oracle, postgres, mssql, db2}。             若是没使用默认的H2数据库就必须设置这项。            这个配置会决定使用哪些建立/删除脚本和查询语句。            参考支持数据库章节            了解支持哪些类型。          

  • databaseSchemaUpdate:             设置流程引擎启动和关闭时如何处理数据库表。            

    • false(默认):检查数据库表的版本和依赖库的版本,                  若是版本不匹配就抛出异常。                

    • true: 构建流程引擎时,执行检查,若是须要就执行更新。                  若是表不存在,就建立。                

    • create-drop: 构建流程引擎时建立数据库表,                  关闭流程引擎时删除这些表。                

     

 

支持的数据库

下面列出Activiti使用的数据库类型(大小写敏感)。

Table 3.1. 支持的数据库

Activiti数据库类型 JDBC URL实例 备注
h2 jdbc:h2:tcp://localhost/activiti 默认配置的数据库
mysql jdbc:mysql://localhost:3306/activiti?autoReconnect=true 使用mysql-connector-java驱动测试
oracle jdbc:oracle:thin:@localhost:1521:xe  
postgres jdbc:postgresql://localhost:5432/activiti  
db2 jdbc:db2://localhost:50000/activiti  
mssql jdbc:sqlserver://localhost:1433/activiti  

建立数据库表

      下面是建立数据库表最简单的办法:      

  • 把activiti-engine的jar放到classpath下

  • 添加对应的数据库驱动

  • 把Activiti配置文件 (activiti.cfg.xml) 放到 classpath下,          指向你的数据库(参考数据库配置章节

  • 执行 DbSchemaCreate 类的main方法

 

      然而,只有数据库管理员能够执行DDL语句。   在生产环境,也也是最明智的选择。       SQL DDL语句能够从Activiti下载页或Activiti发布目录里找到,在database子目录下。      脚本也包含在引擎的jar中(activiti-engine-x.jar),      在org/activiti/db/create包下(drop目录里是删除语句)。       SQL文件的命名方式以下      

activiti.{db}.{create|drop}.{type}.sql

其中 db支持的数据库,        type 是      

  • engine: 引擎执行的表。必须。

  • identity: 包含用户,群组,用户与组之间的关系的表。          这些表是可选的,只有使用引擎自带的默认身份管理时才须要。

  • history: 包含历史和审计信息的表。可选的:历史级别设为none时不会使用。          注意这也会引用一些须要把数据保存到历史表中的功能(好比任务的评论)。

 

MySQL用户须要注意: 版本低于5.6.4的MySQL不支持毫秒精度的timstamp或date类型。 更眼中的是,有些版本会在尝试建立这样一列时抛出异常,而有些版本则不会。 在执行自动建立/更新时,引擎会在执行过程当中修改DDL。 当使用DDL时,能够选择通用版本和名为mysql55的文件。 (它适合全部版本低于5.6.4的状况)。 后一个文件会将列的类型设置为没有毫秒的状况。    

        总结一下,对于MySQL版本会执行以下操做        

  • <5.6: 不支持毫秒精度。能够使用DDL文件(包含mysql55的文件)。能够实现自动建立/更新。

  • 5.6.0 - 5.6.3: 不支持毫秒精度。没法自动建立/更新。建议更新到新的数据库版本。若是真的须要的话,也能够使用mysql 5.5

  • 5.6.4+:支持毫秒精度。能够使用DDL文件(默认包含mysql的文件)。能够实现自动建立、更新。

 

注意对于已经更新了MySQL数据库,并且Activiti表已经建立/更新的状况, 必须手工修改列的类型。    

理解数据库表的命名

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配置文件里:     

<beans ... >
 
<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">     <!-- ... -->     <propertyname="databaseSchemaUpdate"value="true"/>     <!-- ... -->   </bean>
</beans>

而后,把对应的数据库驱动放到classpath里。     升级应用的Activiti依赖。启动一个新版本的Activiti      指向包含旧版本的数据库。将databaseSchemaUpdate设置为true,      Activiti会自动将数据库表升级到新版本,     当发现依赖和数据库表版本不经过时。     

也能够执行更新升级DDL语句。     也能够执行数据库脚本,能够在Activiti下载页找到。     

启用Job执行器

JobExecutor是管理一系列线程的组件,能够触发定时器(也包含后续的异步消息)。    在单元测试场景下,很难使用多线程。所以API容许查询(ManagementService.createJobQuery)和执行job    (ManagementService.executeJob),因此job能够在单元测试中控制。    要避免与job执行器冲突,能够关闭它。    

    默认,JobExecutor在流程引擎启动时就会激活。    若是不想在流程引擎启动后自动激活JobExecutor,能够设置   

<propertyname="jobExecutorActivate"value="false"/>

 

配置邮件服务器

      能够选择配置邮件服务器。Activiti支持在业务流程中发送邮件。      想真正的发送一个email,必须配置一个真实的SMTP邮件服务器。      参考e-mail任务。    

配置历史

      能够选择定制历史存储的配置。你能够经过配置影响引擎的历史功能。      参考历史配置。      

<propertyname="history"value="audit"/>

 

为表达式和脚本暴露配置

默认,activiti.cfg.xml和你本身的Spring配置文件中全部bean     均可以在表达式和脚本中使用。    若是你想限制配置文件中的bean的可见性,    能够配置流程引擎配置的beans配置。     ProcessEngineConfiguration的beans是一个map。当你指定了这个参数,    只有包含这个map中的bean能够在表达式和脚本中使用。    经过在map中指定的名称来决定暴露的bean。    

配置部署缓存

全部流程定义都被缓存了(解析以后)避免每次使用前都要访问数据库,    由于流程定义数据是不会改变的。    默认,不会限制这个缓存。若是想限制流程定义缓存,能够添加以下配置    

<propertyname="processDefinitionCacheLimit"value="10"/>

这个配置会把默认的hashmap缓存替换成LRU缓存,来提供限制。    固然,这个配置的最佳值跟流程定义的总数有关,    实际使用中会具体使用多少流程定义也有关。

也你能够注入本身的缓存实现。这个bean必须实现     org.activiti.engine.impl.persistence.deploy.DeploymentCache接口:    

<propertyname="processDefinitionCache">   <beanclass="org.activiti.MyCache"/></property>

有一个相似的配置叫knowledgeBaseCacheLimitknowledgeBaseCache,     它们是配置规则缓存的。只有流程中使用规则任务时才会用到。

日志

从Activiti 5.12开始,SLF4J被用做日志框架,替换了以前使用java.util.logging。    全部日志(activiti, spring, mybatis等等)都转发给SLF4J     容许使用你选择的日志实现。

默认activiti-engine依赖中没有提供SLF4J绑定的jar,    须要根据你的实际须要使用日志框架。若是没有添加任何实现jar,SLF4J会使用NOP-logger,不使用任何日志,不会发出警告,并且什么日志都不会记录。    能够经过http://www.slf4j.org/codes.html#StaticLoggerBinder了解这些实现。

使用Maven,好比使用一个依赖(这里使用log4j),注意你还须要添加一个version:    

<dependency>   <groupId>org.slf4j</groupId>   <artifactId>slf4j-log4j12</artifactId></dependency>

activiti-explorer和activiti-rest应用都使用了Log4j绑定。执行全部activiti-*模块的单元测试页使用了Log4j。

特别提醒若是容器classpath中存在commons-logging:   为了把spring日志转发给SLF4J,须要使用桥接(参考http://www.slf4j.org/legacy.html#jclOverSLF4J)。   若是你的容器提供了commons-logging实现,请参考下面网页:http://www.slf4j.org/codes.html#release来确保稳定性。    

  使用Maven的实例(忽略版本):      

<dependency>   <groupId>org.slf4j</groupId>   <artifactId>jcl-over-slf4j</artifactId></dependency>

 

映射诊断上下文

在5.13中,activiti支持slf4j的MDC功能。 以下的基础信息会传递到日志中记录:      

  •             流程定义Id标记为mdcProcessDefinitionID            

  • 流程实例Id标记为mdcProcessInstanceID            

  • 分支Id标记为mdcexecutionId        

默认不会记录这些信息。能够配置日志使用指望的格式来显示它们,扩展一般的日志信息。   好比,下面的log4j配置定义会让日志显示上面说起的信息:     

 log4j.appender.consoleAppender.layout.ConversionPattern=ProcessDefinitionId=%X{mdcProcessDefinitionID} executionId=%X{mdcExecutionId} mdcProcessInstanceID=%X{mdcProcessInstanceID} mdcBusinessKey=%X{mdcBusinessKey}%m%n"       

当系统进行高风险任务,日志必须严格检查时,这个功能就很是有用,好比要使用日志分析的状况。    

事件处理

Activiti 5.15中实现了一种事件机制。它容许在引擎触发事件时得到提醒。   参考全部支持的事件类型了解有效的事件。    

      能够为对应的事件类型注册监听器,在这个类型的任什么时候间触发时都会收到提醒。   你能够添加引擎范围的事件监听器经过配置,   添加引擎范围的事件监听器在运行阶段使用API,   或添加event-listener到特定流程定义的BPMN XML中。    

       全部分发的事件,都是org.activiti.engine.delegate.event.ActivitiEvent的子类。事件包含(若是有效)typeexecutionIdprocessInstanceIdprocessDefinitionId。    对应的事件会包含事件发生时对应上下文的额外信息,    这些额外的载荷能够在支持的全部事件类型中找到。    

事件监听器实现

          实现事件监听器的惟一要求是实现org.activiti.engine.delegate.event.ActivitiEventListener。   西面是一个实现监听器的例子,它会把全部监听到的事件打印到标准输出中,包括job执行的事件异常:          

publicclassMyEventListenerimplementsActivitiEventListener{
 
@Override   publicvoid onEvent(ActivitiEventevent){     switch(event.getType()){
     
case JOB_EXECUTION_SUCCESS:         System.out.println("A job well done!");         break;
     
case JOB_EXECUTION_FAILURE:         System.out.println("A job has failed...");         break;
     
default:         System.out.println("Event received: "+event.getType());     }   }
 
@Override   publicboolean isFailOnException(){     // The logic in the onEvent method of this listener is not critical, exceptions     // can be ignored if logging fails...     returnfalse;   }}

 

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。   下面的代码,向配置添加了一个事件监听器,任何事件触发时都会提醒它,不管事件是什么类型:                

<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">     ...     <propertyname="eventListeners">       <list>          <beanclass="org.activiti.engine.example.MyEventListener"/>       </list>     </property></bean>

 

为了监听特定类型的事件,能够使用typedEventListeners属性,它须要一个map参数。    map的key是逗号分隔的事件名(或单独的事件名)。    map的value是org.activiti.engine.delegate.event.ActivitiEventListener队列。   下面的代码演示了向配置中添加一个事件监听器,能够监听job执行成功或失败:                

<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">     ...     <propertyname="typedEventListeners">       <map>         <entrykey="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE">           <list>             <beanclass="org.activiti.engine.example.MyJobEventListener"/>           </list>         </entry>       </map>     </property></bean>

 

分发事件的顺序是由监听器添加时的顺序决定的。首先,会调用全部普通的事件监听器(eventListeners属性),按照它们在list中的次序。 而后,会调用全部对应类型的监听器(typedEventListeners属性),若是对应类型的事件被触发了。

在运行阶段添加监听器

能够经过API(RuntimeService)在运行阶段添加或删除额外的事件监听器:        

/**  * Adds an event-listener which will be notified of ALL events by the dispatcher.  * @param listenerToAdd the listener to add  */void addEventListener(ActivitiEventListener listenerToAdd);
/**  * Adds an event-listener which will only be notified when an event occurs, which type is in the given types.  * @param listenerToAdd the listener to add  * @param types types of events the listener should be notified for  */void addEventListener(ActivitiEventListener listenerToAdd,ActivitiEventType... types);
/**  * Removes the given listener from this dispatcher. The listener will no longer be notified,  * regardless of the type(s) it was registered for in the first place.  * @param listenerToRemove listener to remove  */  void removeEventListener(ActivitiEventListener listenerToRemove);

 

注意运行期添加的监听器引擎重启后就消失了。        

为流程定义添加监听器

能够为特定流程定义添加监听器。监听器只会监听与这个流程定义相关的事件,以及这个流程定义上发起的全部流程实例的事件。 监听器实现能够使用,全类名定义,引用实现了监听器接口的表达式,或配置为抛出一个message/signal/error的BPMN事件。        

让监听器执行用户定义的逻辑

下面代码为一个流程定义添加了两个监听器。第一个监听器会接收全部类型的事件,它是经过全类名定义的。 第二个监听器只接收做业成功或失败的事件,它使用了定义在流程引擎配置中的beans属性中的一个bean。

<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerclass="org.activiti.engine.test.MyEventListener"/>     <activiti:eventListenerdelegateExpression="${testEventListener}"events="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE"/>   </extensionElements>
  ...
</process>

对于实体相关的事件,也能够设置为针对某个流程定义的监听器,实现只监听发生在某个流程定义上的某个类型实体事件。   下面的代码演示了如何实现这种功能。能够用于全部实体事件(第一个例子),也能够只监听特定类型的事件(第二个例子)。

<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerclass="org.activiti.engine.test.MyEventListener"entityType="task"/>     <activiti:eventListenerdelegateExpression="${testEventListener}"events="ENTITY_CREATED"entityType="task"/>   </extensionElements>
  ...
</process>

entityType支持的值有:attachment, comment, execution,identity-link, job, process-instance, process-definition, task

监听抛出BPMN事件

[试验阶段]                   

另外一种处理事件的方法是抛出一个BPMN事件。请注意它只针对与抛出一个activiti事件类型的BPMN事件。 好比,抛出一个BPMN事件,在流程实例删除时,会致使一个错误。 下面的代码演示了如何在流程实例中抛出一个signal,把signal抛出到外部流程(全局),在流程实例中抛出一个消息事件, 在流程实例中抛出一个错误事件。除了使用classdelegateExpression, 还使用了throwEvent属性,经过额外属性,指定了抛出事件的类型。

<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerthrowEvent="signal"signalName="My signal"events="TASK_ASSIGNED"/>   </extensionElements></process>
<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerthrowEvent="globalSignal"signalName="My signal"events="TASK_ASSIGNED"/>   </extensionElements></process>
<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerthrowEvent="message"messageName="My message"events="TASK_ASSIGNED"/>   </extensionElements></process>
<processid="testEventListeners">   <extensionElements>     <activiti:eventListenerthrowEvent="error"errorCode="123"events="TASK_ASSIGNED"/>   </extensionElements></process>

若是须要声明额外的逻辑,是否抛出BPMN事件,能够扩展activiti提供的监听器类。在子类中重写isValidEvent(ActivitiEvent event), 能够防止抛出BPMN事件。对应的类是org.activiti.engine.test.api.event.SignalThrowingEventListenerTest, org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListenerorg.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener.

流程定义中监听器的注意事项

 

  •               事件监听器只能声明在process元素中,做为extensionElements的子元素。   监听器不能定义在流程的单个activity下。            

  • delegateExpression中的表达式没法访问execution上下文,这与其余表达式不一样(好比gateway)。   它只能引用定义在流程引擎配置的beans属性中声明的bean,或者使用spring(未使用beans属性)中全部实现了监听器接口的spring-bean。            

  •               在使用监听器的 class 属性时,只会建立一个实例。记住监听器实现不会依赖成员变量,   确认是多线程安全的。            

  •               当一个非法的事件类型用在events属性或throwEvent中时,流程定义发布时就会抛出异常。(会致使部署失败)。若是classdelegateExecution由问题(类不存在,不存在的bean引用,或代理类没有实现监听器接口),会在流程启动时抛出异常(或在第一个有效的流程定义事件被监听器接收时)。因此要保证引用的类正确的放在classpath下,表达式也要引用一个有效的实例。            

 

经过API分发事件

咱们提供了经过API使用事件机制的方法,容许你们触发定义在引擎中的任何自定义事件。 建议(不强制)只触发类型为CUSTOMActivitiEvents。能够经过RuntimeService触发事件:        

/**  * Dispatches the given event to any listeners that are registered.  * @param event event to dispatch.  *  * @throws ActivitiException if an exception occurs when dispatching the event or when the {@link ActivitiEventDispatcher}  * is disabled.  * @throws ActivitiIllegalArgumentException when the given event is not suitable for dispatching.  */  void dispatchEvent(ActivitiEventevent);

 

支持的事件类型

下面是引擎中可能出现的全部事件类型。每一个类型都对应org.activiti.engine.delegate.event.ActivitiEventType中的一个枚举值。

Table 3.2. 支持的事件

事件名称 描述 事件类型
ENGINE_CREATED 监听器监听的流程引擎已经建立完毕,并准备好接受API调用。 org.activiti...ActivitiEvent
ENGINE_CLOSED 监听器监听的流程引擎已经关闭,再也不接受API调用。 org.activiti...ActivitiEvent
ENTITY_CREATED 建立了一个新实体。实体包含在事件中。 org.activiti...ActivitiEntityEvent
ENTITY_INITIALIZED 建立了一个新实体,初始化也完成了。若是这个实体的建立会包含子实体的建立,这个事件会在子实体都建立/初始化完成后被触发,这是与ENTITY_CREATED的区别。 org.activiti...ActivitiEntityEvent
ENTITY_UPDATED 更新了已存在的实体。实体包含在事件中。 org.activiti...ActivitiEntityEvent
ENTITY_DELETED 删除了已存在的实体。实体包含在事件中。 org.activiti...ActivitiEntityEvent
ENTITY_SUSPENDED 暂停了已存在的实体。实体包含在事件中。会被ProcessDefinitions, ProcessInstances 和 Tasks抛出。 org.activiti...ActivitiEntityEvent
ENTITY_ACTIVATED 激活了已存在的实体,实体包含在事件中。会被ProcessDefinitions, ProcessInstances 和 Tasks抛出。 org.activiti...ActivitiEntityEvent
JOB_EXECUTION_SUCCESS 做业执行成功。job包含在事件中。 org.activiti...ActivitiEntityEvent
JOB_EXECUTION_FAILURE 做业执行失败。做业和异常信息包含在事件中。 org.activiti...ActivitiEntityEvent and org.activiti...ActivitiExceptionEvent
JOB_RETRIES_DECREMENTED 由于做业执行失败,致使重试次数减小。做业包含在事件中。 org.activiti...ActivitiEntityEvent
TIMER_FIRED 触发了定时器。job包含在事件中。 org.activiti...ActivitiEntityEvent
ACTIVITY_STARTED 一个节点开始执行 org.activiti...ActivitiActivityEvent
ACTIVITY_COMPLETED 一个节点成功结束 org.activiti...ActivitiActivityEvent
ACTIVITY_SIGNALED 一个节点收到了一个信号 org.activiti...ActivitiSignalEvent
ACTIVITY_MESSAGE_RECEIVED 一个节点收到了一个消息。在节点收到消息以前触发。收到后,会触发ACTIVITY_SIGNALACTIVITY_STARTED,这会根据节点的类型(边界事件,事件子流程开始事件) org.activiti...ActivitiMessageEvent
ACTIVITY_ERROR_RECEIVED 一个节点收到了一个错误事件。在节点实际处理错误以前触发。 事件的activityId对应着处理错误的节点。 这个事件后续会是ACTIVITY_SIGNALLEDACTIVITY_COMPLETE, 若是错误发送成功的话。 org.activiti...ActivitiErrorEvent
UNCAUGHT_BPMN_ERROR 抛出了未捕获的BPMN错误。流程没有提供针对这个错误的处理器。 事件的activityId为空。 org.activiti...ActivitiErrorEvent
ACTIVITY_COMPENSATE 一个节点将要被补偿。事件包含了将要执行补偿的节点id。 org.activiti...ActivitiActivityEvent
VARIABLE_CREATED 建立了一个变量。事件包含变量名,变量值和对应的分支或任务(若是存在)。 org.activiti...ActivitiVariableEvent
VARIABLE_UPDATED 更新了一个变量。事件包含变量名,变量值和对应的分支或任务(若是存在)。 org.activiti...ActivitiVariableEvent
VARIABLE_DELETED 删除了一个变量。事件包含变量名,变量值和对应的分支或任务(若是存在)。 org.activiti...ActivitiVariableEvent
TASK_ASSIGNED 任务被分配给了一我的员。事件包含任务。 org.activiti...ActivitiEntityEvent
TASK_COMPLETED 任务被完成了。它会在ENTITY_DELETE事件以前触发。当任务是流程一部分时,事件会在流程继续运行以前, 后续事件将是ACTIVITY_COMPLETE,对应着完成任务的节点。 org.activiti...ActivitiEntityEvent
MEMBERSHIP_CREATED 用户被添加到一个组里。事件包含了用户和组的id。 org.activiti...ActivitiMembershipEvent
MEMBERSHIP_DELETED 用户被从一个组中删除。事件包含了用户和组的id。 org.activiti...ActivitiMembershipEvent
MEMBERSHIPS_DELETED 全部成员被从一个组中删除。在成员删除以前触发这个事件,因此他们都是能够访问的。 由于性能方面的考虑,不会为每一个成员触发单独的MEMBERSHIP_DELETED事件。 org.activiti...ActivitiMembershipEvent

引擎内部全部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来以安全的方式来操做引擎。即便如此,你须要当心的对事件对应的实体进行更新/操做。        

          没有对应历史的实体事件,由于它们都有运行阶段的对应实体。        

Chapter 4. Activiti API

流程引擎的API和服务

引擎API是与Activiti打交道的最经常使用方式。 咱们从ProcessEngine开始, 建立它的不少种方法都已经在 配置章节中有所涉及。 从ProcessEngine中,你能够得到不少囊括工做流/BPM方法的服务。 ProcessEngine和服务类都是线程安全的。 你能够在整个服务器中仅保持它们的一个引用就能够了。    

ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();RepositoryService repositoryService = processEngine.getRepositoryService();TaskService taskService = processEngine.getTaskService();ManagementService managementService = processEngine.getManagementService();IdentityService identityService = processEngine.getIdentityService();HistoryService historyService = processEngine.getHistoryService();FormService formService = processEngine.getFormService();

ProcessEngines.getDefaultProcessEngine()会在第一次调用时 初始化并建立一个流程引擎,之后再调用就会返回相同的流程引擎。 使用对应的方法能够建立和关闭全部流程引擎:ProcessEngines.init()     和 ProcessEngines.destroy()。    

ProcessEngines会扫描全部activiti.cfg.xmlactiviti-context.xml 文件。 对于activiti.cfg.xml文件,流程引擎会使用Activiti的经典方式构建:     ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine().     对于activiti-context.xml文件,流程引擎会使用Spring方法构建:先建立一个Spring的环境, 而后经过环境得到流程引擎。    

    全部服务都是无状态的。这意味着能够在多节点集群环境下运行Activiti,每一个节点都指向同一个数据库, 不用担忧哪一个机器实际执行前端的调用。 不管在哪里执行服务都没有问题。    

RepositoryService多是使用Activiti引擎时最早接触的服务。 它提供了管理和控制发布包流程定义的操做。 这里不涉及太多细节,流程定义是BPMN 2.0流程的java实现。 它包含了一个流程每一个环节的结构和行为。 发布包是Activiti引擎的打包单位。一个发布包能够包含多个BPMN 2.0 xml文件和其余资源。 开发者能够自由选择把任意资源包含到发布包中。 既能够把一个单独的BPMN 2.0 xml文件放到发布包里,也能够把整个流程和相关资源都放在一块儿。 (好比,'hr-processes'实例能够包含hr流程相关的任何资源)。 能够经过RepositoryService部署这种发布包。 发布一个发布包,意味着把它上传到引擎中,全部流程都会在保存进数据库以前分析解析好。 从这点来讲,系统知道这个发布包的存在,发布包中包含的流程就已经能够启动了。    

除此以外,服务能够        

  • 查询引擎中的发布包和流程定义。            

  • 暂停或激活发布包,对应所有和特定流程定义。 暂停意味着它们不能再执行任何操做了,激活是对应的反向操做。            

  • 得到多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。            

  • 得到流程定义的pojo版本, 能够用来经过java解析流程,而没必要经过xml。            

 

正如RepositoryService负责静态信息(好比,不会改变的数据,至少是不怎么改变的), RuntimeService正好是彻底相反的。它负责启动一个流程定义的新实例。 如上所述,流程定义定义了流程各个节点的结构和行为。 流程实例就是这样一个流程定义的实例。对每一个流程定义来讲,同一时间会有不少实例在执行。 RuntimeService也能够用来获取和保存流程变量。 这些数据是特定于某个流程实例的,并会被不少流程中的节点使用 (好比,一个排他网关经常使用流程变量来决定选择哪条路径继续流程)。 Runtimeservice也能查询流程实例和执行。 执行对应BPMN 2.0中的'token'。基本上执行指向流程实例当前在哪里。 最后,RuntimeService能够在流程实例等待外部触发时使用,这时能够用来继续流程实例。 流程实例能够有不少暂停状态,而服务提供了多种方法来'触发'实例, 接受外部触发后,流程实例就会继续向下执行。    

任务是由系统中真实人员执行的,它是Activiti这类BPMN引擎的核心功能之一。 全部与任务有关的功能都包含在TaskService中:        

  • 查询分配给用户或组的任务            

  • 建立独立运行任务。这些任务与流程实例无关。            

  • 手工设置任务的执行者,或者这些用户经过何种方式与任务关联。            

  • 认领并完成一个任务。认领意味着一我的指望成为任务的执行者, 即这个用户会完成这个任务。完成意味着“作这个任务要求的事情”。 一般来讲会有不少种处理形式。            

 

IdentityService很是简单。它能够管理(建立,更新,删除,查询...)群组和用户。 请注意, Activiti执行时并无对用户进行检查。 例如,任务能够分配给任何人,可是引擎不会校验系统中是否存在这个用户。 这是Activiti引擎也能够使用外部服务,好比ldap,活动目录,等等。    

FormService是一个可选服务。即便不使用它,Activiti也能够完美运行, 不会损失任何功能。这个服务提供了启动表单任务表单两个概念。 启动表单会在流程实例启动以前展现给用户, 任务表单会在用户完成任务时展现。Activiti支持在BPMN 2.0流程定义中设置这些表单。 这个服务以一种简单的方式将数据暴露出来。再次重申,它时可选的, 表单也不必定要嵌入到流程定义中。    

HistoryService提供了Activiti引擎手机的全部历史数据。 在执行流程时,引擎会保存不少数据(根据配置),好比流程实例启动时间,任务的参与者, 完成任务的时间,每一个流程实例的执行路径,等等。 这个服务主要经过查询功能来得到这些数据。    

ManagementService在使用Activiti的定制环境中基本上不会用到。 它能够查询数据库的表和表的元数据。另外,它提供了查询和管理异步操做的功能。 Activiti的异步操做用途不少,好比定时器,异步操做, 延迟暂停、激活,等等。后续,会讨论这些功能的更多细节。    

能够从javadocs中得到这些服务和引擎API的更多信息。    

异常策略

Activiti中的基础异常为org.activiti.engine.ActivitiException,一个非检查异常。 这个异常能够在任什么时候候被API抛出,不过特定方法抛出的“特定”的异常都记录在 javadocs中。 例如,下面的TaskService:    

/**  * Called when the task is successfully executed.  * @param taskId the id of the task to complete, cannot be null.  * @throws ActivitiObjectNotFoundException when no task exists with the given id.  */  void complete(String taskId);     

在上面的例子中,当传入一个不存在的任务的id时,就会抛出异常。 同时,javadoc明确指出taskId不能为null,若是传入null, 就会抛出ActivitiIllegalArgumentException。    

  咱们但愿避免过多的异常继承,下面的子类用于特定的场合。   流程引擎和API调用的其余场合不会使用下面的异常,   它们会抛出一个普通的ActivitiExceptions。      

  • ActivitiWrongDbException:当Activiti引擎发现数据库版本号和引擎版本号不一致时抛出。            

  • ActivitiOptimisticLockingException:对同一数据进行并发方法并出现乐观锁时抛出。            

  • ActivitiClassLoadingException:当没法找到须要加载的类或在加载类时出现了错误(好比,JavaDelegate,TaskListener等。            

  • ActivitiObjectNotFoundException:当请求或操做的对应不存在时抛出。            

  • ActivitiIllegalArgumentException:这个异常表示调用Activiti API时传入了一个非法的参数,多是引擎配置中的非法值,或提供了一个非法制,或流程定义中使用的非法值。            

  • ActivitiTaskAlreadyClaimedException:当任务已经被认领了,再调用taskService.claim(...)就会抛出。            

 

使用Activiti的服务

像上面介绍的那样,要想操做Activiti引擎,须要经过 org.activiti.engine.ProcessEngine实例暴露的服务。 下面的代码假设你已经拥有了一个能够运行的Activiti环境。 你就能够操做一个org.activiti.engine.ProcessEngine。 若是只想简单尝试一下代码, 能够下载或者cloneActiviti单元测试模板, 导入到IDE中,把testUserguideCode()方法添加到 org.activiti.MyUnitTest中。    

这个小例子的最终目标是作一个工做业务流程, 演示公司中简单的请假申请:        

 

发布流程

任何与“静态”资源有关的数据(好比流程定义)均可以经过 RepositoryService访问。 从概念上讲,因此静态数据都是Activiti的资源内容。        

src/test/resources/org/activiti/test目录下建立一个新的xml文件 VacationRequest.bpmn20.xml(若是不使用单元测试模板,你也能够在任何地方建立), 内容以下。注意这一章不会解释例子中使用的xml结构。 若是有须要能够先阅读bpmn 2.0章来了解这些。            

<?xml version="1.0" encoding="UTF-8"?><definitionsid="definitions"              targetNamespace="http://activiti.org/bpmn20"              xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"              xmlns:activiti="http://activiti.org/bpmn">
 
<processid="vacationRequest"name="Vacation request">
   
<startEventid="request"activiti:initiator="employeeName">       <extensionElements>         <activiti:formPropertyid="numberOfDays"name="Number of days"type="long"value="1"required="true"/>         <activiti:formPropertyid="startDate"name="First day of holiday (dd-MM-yyy)"datePattern="dd-MM-yyyy hh:mm"type="date"required="true"/>         <activiti:formPropertyid="vacationMotivation"name="Motivation"type="string"/>       </extensionElements>     </startEvent>     <sequenceFlowid="flow1"sourceRef="request"targetRef="handleRequest"/>
   
<userTaskid="handleRequest"name="Handle vacation request">       <documentation>         ${employeeName} would like to take ${numberOfDays} day(s) of vacation (Motivation: ${vacationMotivation}).       </documentation>       <extensionElements>          <activiti:formPropertyid="vacationApproved"name="Do you approve this vacation"type="enum"required="true">           <activiti:valueid="true"name="Approve"/>           <activiti:valueid="false"name="Reject"/>         </activiti:formProperty>         <activiti:formPropertyid="managerMotivation"name="Motivation"type="string"/>       </extensionElements>       <potentialOwner>         <resourceAssignmentExpression>           <formalExpression>management</formalExpression>         </resourceAssignmentExpression>       </potentialOwner>     </userTask>     <sequenceFlowid="flow2"sourceRef="handleRequest"targetRef="requestApprovedDecision"/>
   
<exclusiveGatewayid="requestApprovedDecision"name="Request approved?"/>     <sequenceFlowid="flow3"sourceRef="requestApprovedDecision"targetRef="sendApprovalMail">       <conditionExpressionxsi:type="tFormalExpression">${vacationApproved == 'true'}</conditionExpression>     </sequenceFlow>
   
<taskid="sendApprovalMail"name="Send confirmation e-mail"/>     <sequenceFlowid="flow4"sourceRef="sendApprovalMail"targetRef="theEnd1"/>     <endEventid="theEnd1"/>
   
<sequenceFlowid="flow5"sourceRef="requestApprovedDecision"targetRef="adjustVacationRequestTask">       <conditionExpressionxsi:type="tFormalExpression">${vacationApproved == 'false'}</conditionExpression>     </sequenceFlow>
   
<userTaskid="adjustVacationRequestTask"name="Adjust vacation request">       <documentation>         Your manager has disapproved your vacation request for ${numberOfDays} days.         Reason: ${managerMotivation}       </documentation>       <extensionElements>         <activiti:formPropertyid="numberOfDays"name="Number of days"value="${numberOfDays}"type="long"required="true"/>         <activiti:formPropertyid="startDate"name="First day of holiday (dd-MM-yyy)"value="${startDate}"datePattern="dd-MM-yyyy hh:mm"type="date"required="true"/>         <activiti:formPropertyid="vacationMotivation"name="Motivation"value="${vacationMotivation}"type="string"/>         <activiti:formPropertyid="resendRequest"name="Resend vacation request to manager?"type="enum"required="true">           <activiti:valueid="true"name="Yes"/>           <activiti:valueid="false"name="No"/>         </activiti:formProperty>       </extensionElements>       <humanPerformer>         <resourceAssignmentExpression>           <formalExpression>${employeeName}</formalExpression>         </resourceAssignmentExpression>       </humanPerformer>     </userTask>     <sequenceFlowid="flow6"sourceRef="adjustVacationRequestTask"targetRef="resendRequestDecision"/>
   
<exclusiveGatewayid="resendRequestDecision"name="Resend request?"/>     <sequenceFlowid="flow7"sourceRef="resendRequestDecision"targetRef="handleRequest">       <conditionExpressionxsi:type="tFormalExpression">${resendRequest == 'true'}</conditionExpression>     </sequenceFlow>
     
<sequenceFlowid="flow8"sourceRef="resendRequestDecision"targetRef="theEnd2">       <conditionExpressionxsi:type="tFormalExpression">${resendRequest == 'false'}</conditionExpression>     </sequenceFlow>     <endEventid="theEnd2"/>
 
</process>
</definitions>            

 

为了让Activiti引擎知道这个流程,咱们必须先进行“发布”。 发布意味着引擎会把BPMN 2.0 xml解析成能够执行的东西, “发布包”中的全部流程定义都会添加到数据库中。 这样,当引擎重启时,它依然能够得到“已发布”的流程:            

ProcessEngine processEngine =ProcessEngines.getDefaultProcessEngine();RepositoryService repositoryService = processEngine.getRepositoryService(); repositoryService.createDeployment()   .addClasspathResource("org/activiti/test/VacationRequest.bpmn20.xml")   .deploy();
Log.info("Number of process definitions: "+ repositoryService.createProcessDefinitionQuery().count());            

 

能够阅读发布章来了解更多关于发布的信息。        

启动一个流程实例

把流程定义发布到Activiti引擎后,咱们能够基于它发起新流程实例。 对每一个流程定义,均可以有不少流程实例。 流程定义是“蓝图”,流程实例是它的一个运行的执行。        

全部与流程运行状态相关的东西均可以经过RuntimeService得到。 有不少方法能够启动一个新流程实例。在下面的代码中,咱们使用定义在流程定义xml 中的key来启动流程实例。 咱们也能够在流程实例启动时添加一些流程变量,由于第一个用户任务的表达式须要这些变量。 流程变量常常会被用到,由于它们赋予来自同一个流程定义的不一样流程实例的特别含义。 简单来讲,流程变量是区分流程实例的关键。            

Map<String,Object> variables =newHashMap<String,Object>(); variables.put("employeeName","Kermit"); variables.put("numberOfDays",newInteger(4)); variables.put("vacationMotivation","I'm really tired!");
RuntimeService runtimeService = processEngine.getRuntimeService();ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
// Verify that we started a new process instanceLog.info("Number of process instances: "+ runtimeService.createProcessInstanceQuery().count());            

 

完成任务

流程启动后,第一步就是用户任务。这是必须由系统用户处理的一个环节。 一般,用户会有一个“任务列表”,展现了全部必须由整个用户处理的任务。 下面的代码展现了对应的查询多是怎样的:            

// Fetch all tasks for the management groupTaskService taskService = processEngine.getTaskService();List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();for(Task task : tasks){   Log.info("Task available: "+ task.getName());}             

 

为了让流程实例继续运行,咱们须要完成整个任务。对Activiti来讲,就是须要complete任务。 下面的代码展现了如何作这件事:            

Task task = tasks.get(0);
Map<String,Object> taskVariables =newHashMap<String,Object>(); taskVariables.put("vacationApproved","false"); taskVariables.put("managerMotivation","We have a tight deadline!"); taskService.complete(task.getId(), taskVariables);            

流程实例会进入到下一个环节。在这里例子中, 下一环节容许员工经过表单调整原始的请假申请。员工能够从新提交请假申请, 这会使流程从新进入到第一个任务。        

挂起,激活一个流程

咱们能够挂起一个流程定义。当挂起流程定时时, 就不能建立新流程了(会抛出一个异常)。 能够经过RepositoryService挂起一个流程:            

repositoryService.suspendProcessDefinitionByKey("vacationRequest");try{   runtimeService.startProcessInstanceByKey("vacationRequest");}catch(ActivitiException e){   e.printStackTrace();}             

要想从新激活一个流程定义,能够调用repositoryService.activateProcessDefinitionXXX方法。        

也能够挂起一个流程实例。挂起时,流程不能继续执行(好比,完成任务会抛出异常), 异步操做(好比定时器)也不会执行。 骨气流程实例能够调用 runtimeService.suspendProcessInstance方法。 激活流程实例能够调用runtimeService.activateProcessInstanceXXX方法。        

更多知识

上面章节中咱们仅仅覆盖了Activiti功能的表层。 将来咱们会继续扩展这些章节,以覆盖更多Activiti API。 固然,像其余开源项目同样,学习的最好方式 是研究代码,阅读javadoc。        

查询API

有两种方法能够从引擎中查询数据:查询API和原生查询。查询API提供了彻底类型安全的API。   你能够为本身的查询条件添加不少条件   (因此条件都以AND组合)和精确的排序条件。下面的代码展现了一个例子:      

      List<Task> tasks = taskService.createTaskQuery()          .taskAssignee("kermit")          .processVariableValueEquals("orderId","0815")          .orderByDueDate().asc()          .list();       

有时,你须要更强大的查询,好比使用OR条件或不能使用查询API实现的条件。   这时,咱们推荐原生查询,它让你能够编写本身的SQL查询。   返回类型由你使用的查询对象决定,数据会映射到正确的对象上。好比,任务,流程实例,,执行,等等。   由于查询会做用在数据库上,你必须使用数据库中定义的表名和列名;这要求了解内部数据结构,   所以使用原生查询时必定要注意。表名能够经过API得到,能够尽可能减小对数据库的依赖。      

      List<Task> tasks = taskService.createNativeTaskQuery()         .sql("SELECT count(*) FROM "+ managementService.getTableName(Task.class)+" T WHERE T.NAME_ = #{taskName}")         .parameter("taskName","gonzoTask")         .list();
     
long count = taskService.createNativeTaskQuery()         .sql("SELECT count(*) FROM "+ managementService.getTableName(Task.class)+" T1, "                + managementService.getTableName(VariableInstanceEntity.class)+" V1 WHERE V1.TASK_ID_ = T1.ID_")         .count();      

 

表达式

Activiti使用UEL处理表达式。UEL即统一表达式语言,它时EE6规范的一部分(参考           EE6规范)。为了在全部运行环境都支持最新UEL的全部共嫩个,咱们使用了一个JUEL的修改版本。    

  表达式能够用在不少场景下,好比Java服务任务执行监听器任务监听器条件流。   虽然有两重表达式,值表达式和方法表达式,Activiti进行了抽象,因此二者能够一样使用在须要表达式的场景中。      

  • Value expression:解析为值。默认,全部流程变量均可以使用。全部spring bean(spring环境中)也能够使用在表达式中。 一些实例:

    ${myVar} ${myBean.myProperty}

     

  • Method expression:调用一个方法,使用或不使用参数。当调用一个无参数的方法时,记得在方法名后添加空的括号(以区分值表达式)。 传递的参数能够是字符串也能够是表达式,它们会被自动解析。例子:

    ${printer.print()} ${myBean.addNewOrder('orderName')} ${myBean.doSomething(myVar, execution)}

     

注意这些表达式支持解析原始类型(包括比较),bean,list,数组和map。    

  在全部流程实例中,表达式中还能够使用一些默认对象:      

  • executionDelegateExecution提供外出执行的额外信息。

  • taskDelegateTask提供当前任务的额外信息。注意,只对任务监听器的表达式有效。

  • authenticatedUserId:当前登陆的用户id。若是没有用户登陆,这个变量就不可用。

 

  想要更多具体的使用方式和例子,参考spring中的表达式Java服务任务执行监听器任务监听器条件流。    

单元测试

  业务流程是软件项目的一部分,它也应该和普通的业务流程同样进行测试:   使用单元测试。   由于Activiti是一个嵌入式的java引擎,   为业务流程编写单元测试和写普通单元测试彻底同样。    

Activiti支持JUnit 3和4进行单元测试。使用JUnit 3时,   必须集成org.activiti.engine.test.ActivitiTestCase。   它经过保护的成员变量提供ProcessEngine和服务,   在测试的setup()中,   默认会使用classpath下的activiti.cfg.xml初始化流程引擎。   想使用不一样的配置文件,能够重写getConfigurationResource()方法。   若是配置文件相同的话,对应的流程引擎会被静态缓存,   就能够用于多个单元测试。   

    继承了ActivitiTestCase你,能够在测试方法上使用 org.activiti.engine.test.Deployment注解。 测试执行前,与测试类在同一个包下的, 格式为testClassName.testMethod.bpmn20.xml的资源文件,会被部署。 测试结束后,发布包也会被删除,包括全部相关的流程实例,任务,等等。 Deployment注解也能够直接设置资源的位置。 参考Javadocs得到更多信息。   

     把这些放在一块儿,JUnit 3测试看起来像这样。     

publicclassMyBusinessProcessTestextendsActivitiTestCase{
 
@Deployment   publicvoid testSimpleProcess(){     runtimeService.startProcessInstanceByKey("simpleProcess");
   
Task task = taskService.createTaskQuery().singleResult();     assertEquals("My Task", task.getName());
    taskService
.complete(task.getId());     assertEquals(0, runtimeService.createProcessInstanceQuery().count());   }}      

 

要想在使用JUnit 4编写单元测试时得到一样的功能,   能够使用org.activiti.engine.test.ActivitiRule。   经过它,能够经过getter方法得到流程引擎和各类服务。   和 ActivitiTestCase同样(参考上面章节),使用这个Rule    也会启用org.activiti.engine.test.Deployment注解(参考上面章节使用和配置的介绍),   它会在classpath下查找默认的配置文件。   若是配置文件相同的话,对应的流程引擎会被静态缓存,   就能够用于多个单元测试。   

     下面的代码演示了JUnit 4单元测试并使用了ActivitiRule的例子。     

publicclassMyBusinessProcessTest{
 
@Rule   publicActivitiRule activitiRule =newActivitiRule();
 
@Test   @Deployment   publicvoid ruleUsageExample(){     RuntimeService runtimeService = activitiRule.getRuntimeService();     runtimeService.startProcessInstanceByKey("ruleUsage");
   
TaskService taskService = activitiRule.getTaskService();     Task task = taskService.createTaskQuery().singleResult();     assertEquals("My Task", task.getName());
    taskService
.complete(task.getId());     assertEquals(0, runtimeService.createProcessInstanceQuery().count());   }}      

 

调试单元测试

  当使用内存数据库H2进行单元测试时,下面的教程会告诉咱们   如何在调试环境下更容易的监视Activiti的数据库。   这里的截图都是基于eclipse,这种机制很容易复用到其余IDE下。       IDEs.    

假设咱们已经在单元测试里设置了一个断点。    Ecilpse里,在代码左侧双击:      

      如今用调试模式运行单元测试(右击单元测试,   选择“运行为”和“单元测试”),测试会停在咱们的断点上,   而后咱们就能够监视测试的变量,它们显示在右侧面板里。      

  要监视Activiti的数据,打开“显示”窗口   (若是找不到,打开“窗口”->“显示视图”->“其余”,选择显示。)   并点击(代码已完成)org.h2.tools.Server.createWebServer("-web").start()      

选择你点击的行,右击。而后选择“显示”(或者直接快捷方式就不用右击了)      

  如今打开一个浏览器,打开http://localhost:8082,   输入内存数据库的JDBC URL(默认为jdbc:h2:mem:activiti),   点击链接按钮。      

  你仙子阿能够看到Activiti的数据,经过它们能够了解单元测试时如何以及为何这样运行的。      

 

web应用中的流程引擎

ProcessEngine是线程安全的,   能够在多线程下共享。在web应用中,   意味着能够在容器启动时建立流程引擎,   在容器关闭时关闭流程引擎。    

  下面代码演示了如何编写一个ServletContextListener    在普通的Servlet环境下初始化和销毁流程引擎:      

publicclassProcessEnginesServletContextListenerimplementsServletContextListener{
 
publicvoid contextInitialized(ServletContextEvent servletContextEvent){     ProcessEngines.init();   }
 
publicvoid contextDestroyed(ServletContextEvent servletContextEvent){     ProcessEngines.destroy();   }
}

contextInitialized方法会执行ProcessEngines.init()。   这会查找classpath下的activiti.cfg.xml文件,   根据配置文件建立一个ProcessEngine(好比,多个jar中都包含配置文件)。   若是classpath中包含多个配置文件,确认它们有不一样的名字。   当须要使用流程引擎时,能够经过

ProcessEngines.getDefaultProcessEngine()

ProcessEngines.getProcessEngine("myName");

。   固然,也能够使用其余方式建立流程引擎,   能够参考配置章节中的描述。    

ContextListener中的contextDestroyed方法会执行ProcessEngines.destroy(). 这会关闭全部初始化的流程引擎。    

Chapter 5. Spring集成

虽然没有Spring你也能够使用Activiti,可是咱们提供了一些很是不错的集成特性。这一章咱们将介绍这些特性。  

ProcessEngineFactoryBean

能够把流程引擎(ProcessEngine)做为一个普通的Spring bean进行配置。 类 org.activiti.spring.ProcessEngineFactoryBean是集成的切入点。 这个bean须要一个流程引擎配置来建立流程引擎。这也意味着在文档的配置这一章的介绍属性的建立和配置对于Spring来讲也是同样的。对于Spring集成的配置和流程引擎bean看起来像这样:    

<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">     ... </bean>
<beanid="processEngine"class="org.activiti.spring.ProcessEngineFactoryBean">   <propertyname="processEngineConfiguration"ref="processEngineConfiguration"/></bean>  

注意如今使用的 processEngineConfiguration bean 是 org.activiti.spring.SpringProcessEngineConfiguration 类。    

事务

咱们将会一步一步地解释在Spring examples中公布的 SpringTransactionIntegrationTest 下面是咱们使用这个例子的Spring配置文件(你能够在SpringTransactionIntegrationTest-context.xml找到它)如下展现的部分包括数据源(dataSource), 事务管理器(transactionManager),流程引擎(processEngine)和Activiti引擎服务。    

当把数据源(DataSource)传递给 SpringProcessEngineConfiguration (使用"dataSource"属性)以后,Activiti内部使用了一个org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy代理来封装传递进来的数据源(DataSource)。 这样作是为了确保从数据源(DataSource)获取的SQL链接可以与Spring的事物结合在一块儿发挥得更出色。这意味它再也不须要在你的Spring配置中代理数据源(dataSource)了。 然而它仍然容许你传递一个TransactionAwareDataSourceProxySpringProcessEngineConfiguration中。在这个例子中并不会发生多余的包装。    

  为了确保在你的Spring配置中申明的一个TransactionAwareDataSourceProxy,你不能把使用它的应用交给Spring事物控制的资源。(例如 DataSourceTransactionManager 和JPATransactionManager须要非代理的数据源 )    

<beansxmlns="http://www.springframework.org/schema/beans"        xmlns:context="http://www.springframework.org/schema/context"        xmlns:tx="http://www.springframework.org/schema/tx"        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                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd                            http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
<beanid="dataSource"class="org.springframework.jdbc.datasource.SimpleDriverDataSource">     <propertyname="driverClass"value="org.h2.Driver"/>     <propertyname="url"value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"/>     <propertyname="username"value="sa"/>     <propertyname="password"value=""/>   </bean>
 
<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     <propertyname="dataSource"ref="dataSource"/>   </bean>
 
<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">     <propertyname="dataSource"ref="dataSource"/>     <propertyname="transactionManager"ref="transactionManager"/>     <propertyname="databaseSchemaUpdate"value="true"/>     <propertyname="jobExecutorActivate"value="false"/>   </bean>
 
<beanid="processEngine"class="org.activiti.spring.ProcessEngineFactoryBean">     <propertyname="processEngineConfiguration"ref="processEngineConfiguration"/>   </bean>
 
<beanid="repositoryService"factory-bean="processEngine"factory-method="getRepositoryService"/>   <beanid="runtimeService"factory-bean="processEngine"factory-method="getRuntimeService"/>   <beanid="taskService"factory-bean="processEngine"factory-method="getTaskService"/>   <beanid="historyService"factory-bean="processEngine"factory-method="getHistoryService"/>   <beanid="managementService"factory-bean="processEngine"factory-method="getManagementService"/>
...

Spring配置文件的其他部分包含beans和咱们将要在这个特有的例子中的配置:

<beans>   ...   <tx:annotation-driventransaction-manager="transactionManager"/>
 
<beanid="userBean"class="org.activiti.spring.test.UserBean">     <propertyname="runtimeService"ref="runtimeService"/>   </bean>
 
<beanid="printer"class="org.activiti.spring.test.Printer"/>
</beans>

首先使用任意的一种Spring建立应用上下文的方式建立其Spring应用上下文。在这个例子中你能够使用类路径下面的XML资源来配置咱们的Spring应用上下文:

ClassPathXmlApplicationContext applicationContext =     newClassPathXmlApplicationContext("org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");

或者, 若是它是一个测试的话:    

@ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")

 

而后咱们就能够获得Activiti的服务beans而且调用该服务上面的方法。ProcessEngineFactoryBean将会对该服务添加一些额外的拦截器,在Activiti服务上面的方法使用的是 Propagation.REQUIRED事物语义。因此,咱们能够使用repositoryService去部署一个流程,以下所示:    

RepositoryService repositoryService =(RepositoryService) applicationContext.getBean("repositoryService");String deploymentId = repositoryService   .createDeployment()   .addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")   .deploy()   .getId();     

其余相同的服务也是一样能够这么使用。在这个例子中,Spring的事物将会围绕在userBean.hello()上,而且调用Activiti服务的方法也会加入到这个事物中。    

UserBean userBean =(UserBean) applicationContext.getBean("userBean"); userBean.hello();

这个UserBean看起来像这样。记得在上面Spring bean的配置中咱们把repositoryService注入到userBean中。

publicclassUserBean{
 
/** 由Spring注入 */   privateRuntimeService runtimeService;
 
@Transactional   publicvoid hello(){         //这里,你能够在大家的领域模型中作一些事物处理。         //当在调用Activiti RuntimeService的startProcessInstanceByKey方法时,         //它将会结合到同一个事物中。     runtimeService.startProcessInstanceByKey("helloProcess");   }
 
publicvoid setRuntimeService(RuntimeService runtimeService){     this.runtimeService = runtimeService;   }}

表达式

当使用ProcessEngineFactoryBean时候,默认状况下,在BPMN流程中的全部表达式都将会'看见'全部的Spring beans。 它能够限制你在表达式中暴露出的beans或者甚至能够在你的配置中使用一个Map不暴露任何beans。下面的例子暴露了一个单例bean(printer),能够把"printer"看成关键字使用.    想要不暴露任何beans,仅仅只须要在SpringProcessEngineConfiguration中传递一个空的list做为'beans'的属性。当不设置'beans'的属性时,在应用上下文中Spring beans都是能够使用的。

<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">   ...   <propertyname="beans">     <map>       <entrykey="printer"value-ref="printer"/>     </map>   </property></bean>
 
<beanid="printer"class="org.activiti.examples.spring.Printer"/>    

如今暴露出来的beans就能够在表达式中使用:例如,在SpringTransactionIntegrationTest中的 hello.bpmn20.xml展现的是如何使用UEL方法表达式去调用Spring bean的方法:    

<definitionsid="definitions" ...>
 
<processid="helloProcess">
   
<startEventid="start"/>     <sequenceFlowid="flow1"sourceRef="start"targetRef="print"/>
   
<serviceTaskid="print"activiti:expression="#{printer.printMessage()}"/>     <sequenceFlowid="flow2"sourceRef="print"targetRef="end"/>
   
<endEventid="end"/>
 
</process>
</definitions>

这里的 Printer 看起来像这样:

publicclassPrinter{
 
publicvoid printMessage(){     System.out.println("hello world");   }}

而且Spring bean的配置(如上文所示)看起来像这样:

<beans ...>   ...
 
<beanid="printer"class="org.activiti.examples.spring.Printer"/>
</beans>

资源的自动部署

Spring的集成也有一个专门用于对资源部署的特性。在流程引擎的配置中,你能够指定一组资源。当流程引擎被建立的时候, 全部在这里的资源都将会被自动扫描与部署。在这里有过滤以防止资源从新部署,只有当这个资源真正发生改变的时候,它才会向Activiti使用的数据库建立新的部署。 这对于不少用例来讲,当Spring容器常常重启的状况下(例如 测试),使用它是很是不错的选择。    

这里有一个例子:

<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">   ...   <propertyname="deploymentResources"value="classpath*:/org/activiti/spring/test/autodeployment/autodeploy.*.bpmn20.xml"/></bean>
<beanid="processEngine"class="org.activiti.spring.ProcessEngineFactoryBean">   <propertyname="processEngineConfiguration"ref="processEngineConfiguration"/></bean>

单元测试

当集成Spring时,使用标准的Activiti测试工具类是很是容易的对业务流程进行测试。 下面的例子展现了如何在一个典型的基于Spring单元测试测试业务流程:      

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:org/activiti/spring/test/junit4/springTypicalUsageTest-context.xml")publicclassMyBusinessProcessTest{
 
@Autowired   privateRuntimeService runtimeService;
 
@Autowired   privateTaskService taskService;
 
@Autowired   @Rule   publicActivitiRule activitiSpringRule;
 
@Test   @Deployment   publicvoid simpleProcessTest(){     runtimeService.startProcessInstanceByKey("simpleProcess");     Task task = taskService.createTaskQuery().singleResult();     assertEquals("My Task", task.getName());
    taskService
.complete(task.getId());     assertEquals(0, runtimeService.createProcessInstanceQuery().count());
 
}}      

注意对于这种方式,你须要在Spring配置中(在上文的例子中它是自动注入的)定义一个org.activiti.engine.test.ActivitiRulebean      

<beanid="activitiRule"class="org.activiti.engine.test.ActivitiRule">   <propertyname="processEngine"ref="processEngine"/></bean>       

 

基于注解的配置

[试验] @EnableActiviti注解相对较新,将来可能会有变动。    

        除了基于XML的配置之外,还能够选择基于注解的方式来配置Spring环境。 这与使用XML的方法很是类似,除了要使用@Bean注解, 并且配置是使用java编写的。 它已经能够直接用于Activiti-Spring的集成了:    

        首先介绍(须要Spring 3.0+)的是@EnableActiviti注解。 最简单的用法以下所示:        

  @Configuration   @EnableActiviti   publicstaticclassSimplestConfiguration{
 
}

它会建立一个Spring环境,并对Activiti流程引擎进行以下配置          

  •                 默认的内存H2数据库,启用数据库自动升级。              

  •                 一个简单的 DataSourceTransactionManager              

  • 一个默认的 SpringJobExecutor              

  • 自动扫描 processes/ 目录下的bpmn20.xml文件。              

          在这样一个环境里,能够直接经过注入操做Activiti引擎:          

  @Autowired   privateProcessEngine processEngine;
 
@Autowired   privateRuntimeService runtimeService;
 
@Autowired   privateTaskService taskService;
 
@Autowired   privateHistoryService historyService;
 
@Autowired   privateRepositoryService repositoryService;
 
@Autowired   privateManagementService managementService;
 
@Autowired   privateFormService formService;

 

固然,默认值均可以自定义。好比,若是配置了DataSource,它就会代替默认建立的数据库配置。 事务管理器,job执行器和其余组件都与之相同。 好比以下配置:        

  @Configuration   @EnableActiviti   publicstaticclassConfig{
   
@Bean     publicDataSource dataSource(){         BasicDataSource basicDataSource =newBasicDataSource();         basicDataSource.setUsername("sa");         basicDataSource.setUrl("jdbc:h2:mem:anotherDatabase");         basicDataSource.setDefaultAutoCommit(false);         basicDataSource.setDriverClassName(org.h2.Driver.class.getName());         basicDataSource.setPassword("");         return basicDataSource;     }
 
}

其余数据库会代替默认的。    

        下面介绍了更加复杂的配置。注意AbstractActivitiConfigurer用法, 它暴露了流程引擎的配置,能够用来对它的细节进行详细的配置。        

@Configuration@EnableActiviti@EnableTransactionManagement(proxyTargetClass =true)classJPAConfiguration{
   
@Bean     publicOpenJpaVendorAdapter openJpaVendorAdapter(){         OpenJpaVendorAdapter openJpaVendorAdapter =newOpenJpaVendorAdapter();         openJpaVendorAdapter.setDatabasePlatform(H2Dictionary.class.getName());         return openJpaVendorAdapter;     }
   
@Bean     publicDataSource dataSource(){         BasicDataSource basicDataSource =newBasicDataSource();         basicDataSource.setUsername("sa");         basicDataSource.setUrl("jdbc:h2:mem:activiti");         basicDataSource.setDefaultAutoCommit(false);         basicDataSource.setDriverClassName(org.h2.Driver.class.getName());         basicDataSource.setPassword("");         return basicDataSource;     }
   
@Bean     publicLocalContainerEntityManagerFactoryBean entityManagerFactoryBean(         OpenJpaVendorAdapter openJpaVendorAdapter,DataSource ds){         LocalContainerEntityManagerFactoryBean emf =newLocalContainerEntityManagerFactoryBean();         emf.setPersistenceXmlLocation("classpath:/org/activiti/spring/test/jpa/custom-persistence.xml");         emf.setJpaVendorAdapter(openJpaVendorAdapter);         emf.setDataSource(ds);         return emf;     }
   
@Bean     publicPlatformTransactionManager jpaTransactionManager(         EntityManagerFactory entityManagerFactory){         returnnewJpaTransactionManager(entityManagerFactory);     }
   
@Bean     publicAbstractActivitiConfigurer abstractActivitiConfigurer(         finalEntityManagerFactory emf,         finalPlatformTransactionManager transactionManager){
       
returnnewAbstractActivitiConfigurer(){
           
@Override             publicvoid postProcessSpringProcessEngineConfiguration(SpringProcessEngineConfiguration engine){                 engine.setTransactionManager(transactionManager);                 engine.setJpaEntityManagerFactory(emf);                 engine.setJpaHandleTransaction(false);                 engine.setJobExecutorActivate(false);                 engine.setJpaCloseEntityManager(false);                 engine.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);             }         };     }
   
// A random bean     @Bean     publicLoanRequestBean loanRequestBean(){         returnnewLoanRequestBean();     }}

 

JPA 和 Hibernate 4.2.x

在Activiti引擎的serviceTask或listener中使用Hibernate 4.2.x JPA时,须要添加Spring ORM这个额外的依赖。    Hibernate 4.1.x及如下版本是不须要的。应该添加以下依赖:      

<dependency>   <groupId>org.springframework</groupId>   <artifactId>spring-orm</artifactId>   <version>${org.springframework.version}</version></dependency>       

 

Chapter 6. 部署

业务文档

为了部署流程,它们不得不包装在一个业务文档中。一个业务文档是Activiti引擎部署的单元。一个业务文档至关与一个压缩文件,它包含BPMN2.0流程,任务表单,规则和其余任意类型的文件。 大致上,业务文档是包含命名资源的容器。    

当一个业务文档被部署,它将会自动扫描以 .bpmn20.xml 或者.bpmn做为扩展名的BPMN文件。每一个那样的文件都将会被解析而且可能会包含多个流程定义。    

Note

业务归档中的Java类将不可以添加到类路径下。为了可以让流程运行,必须把存在于业务归档程中的流程定义使用的全部自定义的类(例如:Java服务任务或者实现事件的监听器)放在activiti引擎的类路径下:        

编程式部署

经过一个压缩文件(支持Zip和Bar)部署业务归档,它看起来像这样:     

String barFileName ="path/to/process-one.bar";ZipInputStream inputStream =newZipInputStream(newFileInputStream(barFileName));
repositoryService
.createDeployment()     .name("process-one.bar")     .addZipInputStream(inputStream)     .deploy();        

它也能够经过一个独立资源(例如bpmn,xml等)构建部署。 详细信息请查看javadocs。

经过Activiti Explorer控制台部署

Activiti web控制台容许你经过web界面的用户接口上传一个bar格式的压缩文件(或者一个bpmn20.xml格式的文件)。 选择Management 标签 和 点击 Deployment:        

如今将会有一个弹出窗口容许你从电脑上面选择一个文件,或者你能够简单的拖拽到指定的区域(若是你的浏览器支持)。        

 

外部资源

流程定义保存在Activiti所支持的数据库中。当使用服务任务、执行监听器或者从Activiti配置文件中配置的Spring beans时,流程定义可以引用这些委托类。 这些类或者Spring配置文件对于全部流程引擎中可能执行的流程定义必须是可用的。    

Java类

  当流程实例被启动的时候,在流程中被使用的全部自定义类(例如:服务任务中使用的JavaDelegates、事件监听器、任务监听器,...)应该存在与流程引擎的类路径下。

而后,在部署业务文档时,这些类没必要都存在于类路径下。当使用Ant部署一个新的业务文档时,这意味着你的委托类没必要存在与类路径下。      

  当你使用示例设置并添加你自定义的类,你应该添加包含自定义类的jar包到activiti-explorer控制台或者activiti-rest 的webapp lib文件夹中。以及不要忽略包含你自定义类的依赖关系(若是有)。   另外,你还能够包含你本身的依赖添加到你的Tomcat容器的安装目录中的${tomcat.home}/lib。      

在流程中使用Spring beans

当表达式或者脚本使用Spring beans时,这些beans对于引擎执行流程定义时必须是可用的。若是你将要构建你本身的web应用而且按照Spring集成这一章中描述那样在你的应用上下文配置流程引擎,这个看上去很是的简单。可是要记住,若是你也在使用 Activiti rest web应用,那么也应该更新 Activiti rest web应用的上下文。   你能够把在activiti-rest/lib/activiti-cfg.jar 文件中的activiti.cfg.xml替换成你的Spring上下文配置的activiti-context.xml文件。

建立独立应用

你能够考虑把Activiti rest web 应用加入到你的web应用之中,所以,就仅仅只须要配置一个 ProcessEngine,从而不用确保全部的流程引擎的全部委托类在类路径下面而且是否使用正确的spring配置。      

流程定义的版本

BPMN中并无版本的概念,没有版本也是不错的,由于可执行的BPMN流程做为你开发项目的一部分存在版本控制系统的知识库中(例如 SVN,Git 或者Mercurial)。 而在Activiti中,流程定义的版本是在部署时建立的。在部署的时候,流程定义被存储到Activiti使用的数据库以前,Activiti讲会自动给 流程定义 分配一个版本号。    

对于业务文档中每个的流程定义,都会经过下列部署执行初始化属性key, version, nameid:    

  •   XML文件中流程定义(流程模型)的 id属性被当作是流程定义的 key属性。  

  • XML文件中的流程模型的name 属性被当作是流程定义的 name 属性。若是该name属性并无指定,那么id属性被当作是name。      

  • 带有特定key的流程定义在第一次部署的时候,将会自动分配版本号为1,对于以后部署相同key的流程定义时候,此次部署的版本号将会设置为比当前最大的版本号大1的值。该key属性被用来区别不一样的流程定义。   

  •   流程定义中的id属性被设置为 {processDefinitionKey}:{processDefinitionVersion}:{generated-id},           这里的generated-id是一个惟一的数字被添加,用于确保在集群环境中缓存的流程定义的惟一性。        

举个流程的例子

<definitionsid="myDefinitions">   <processid="myProcess"name="My important process">     ...

当部署了这个流程定义以后,在数据库中的流程定义看起来像这样:      

Table 6.1. 

id key name version
myProcess:1:676 myProcess My important process 1


假设咱们如今部署用一个流程的最新版本号(例如 改变用户任务),可是流程定义的id保持不变。   流程定义表将包含如下列表信息:       

Table 6.2. 

id key name version
myProcess:1:676 myProcess My important process 1
myProcess:2:870 myProcess My important process 2


runtimeService.startProcessInstanceByKey("myProcess")方法被调用时,它将会使用流程定义版本号为2的,由于这是最新版本的流程定义。能够说每次流程定义建立流程实例时,都会默认使用最新版本的流程定义。   

   咱们应该建立第二个流程,在Activiti中,以下,定义而且部署它,该流程定义会添加到流程定义表中。    

<definitionsid="myNewDefinitions">   <processid="myNewProcess"name="My important process">     ...

这个表结构看起来像这样:  

Table 6.3. 

id key name version
myProcess:1:676 myProcess My important process 1
myProcess:2:870 myProcess My important process 2
myNewProcess:1:1033 myNewProcess My important process 1

注意:为什么新流程的key与咱们的第一个流程是不一样的?尽管流程定义的名称是相同的(固然,咱们应该也是能够改变这一点的),Activiti仅仅只考虑id属性判断流程。所以,新的流程定义部署的版本号为1。  

提供流程图片

  流程定义的流程图能够被添加到部署中,该流程图将会持久化到Activiti所使用的数据库中而且能够经过Activiti的API进行访问。该流程图也能够被用来在Activiti Explorer控制台中的流程中进行显示。    

  若是在咱们的类路径下面有一个流程,org/activiti/expenseProcess.bpmn20.xml ,该流程定义有一个流程key 'expense'。 如下遵循流程定义图片的命名规范(按照这个特意顺序):      

  •     若是在部署时一个图片资源已经存在,它是BPMN2.0的XML文件名后面是流程定义的key而且是一个图片的后缀。那么该图片将被使用。在咱们的例子中,     这应该是 org/activiti/expenseProcess.expense.png(或者 jpg/gif)。若是你在一个BPMN2.0 XML文件中定义多个流程定义图片,这种方式更有意义。每一个流程定义图片的文件名中都将会有一个流程定义key。          

  •     若是并无这样的图片存在,部署的时候寻找与匹配BPMN2.0 XML 文件的名称的图片资源。在咱们的例子中,这应该是org/activiti/expenseProcess.png. 注意:这意味着在同一个BPMN2.0 XML文件夹中的每一个流程定义都会有相同的流程定义图片。所以,在每个BPMN 2.0 XML文件夹中仅仅只有一个流程定义,这绝对是不会有问题的。          

 

     当使用编程式的部署方式:      

repositoryService.createDeployment()   .name("expense-process.bar")   .addClasspathResource("org/activiti/expenseProcess.bpmn20.xml")   .addClasspathResource("org/activiti/expenseProcess.png")   .deploy();

接下来,能够经过API来获取流程定义图片资源:       

  ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()                                                          .processDefinitionKey("expense")                                                          .singleResult();
 
String diagramResourceName = processDefinition.getDiagramResourceName();   InputStream imageStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), diagramResourceName);        

 

自动生成流程图片

在部署的状况下没有提供图片,在 上一节中描述,若是流程定义中包含必要的'图像交换'信息时,Activiti流程引擎竟会自动生成一个图像。    

  该资源能够按照部署时      提供流程图片彻底相同的方式获取。           

 

若是,由于某种缘由,在部署的时候,并不须要或者没必要要生成流程定义图片,那么就须要在流程引擎配置的属性中使用isCreateDiagramOnDeploy:         

<propertyname="createDiagramOnDeploy"value="false"/>

如今就不会生成流程定义图片。    

类别

部署和流程定义都是用户定义的类别。流程定义类别在BPMN文件中属性的初始化的值<definitions ... targetNamespace="yourCategory" ...     

部署类别是能够直接使用API进行指定的看起来想这样:

repositoryService     .createDeployment()     .category("yourCategory")     ...     .deploy();

Chapter 7. BPMN 2.0介绍

啥是BPMN?

  参考咱们的FAQ中的BPMN 2.0部分

定义一个流程

Note

文章假设你在使用Eclipse IDE来建立和编辑文件。   不过,其中只用到了Eclipse不多的特性。你能够使用喜欢的任何工具来建立包含BPMN 2.0的xml文件。      

  建立一个新的XML文件(右击任何项目选择“新建”->“其余”->“XML-XML文件”)并命名。   确认文件后缀为ends with .bpmn20.xml 或 .bpmn,   不然引擎没法发布。   

 

BPMN 2.0根节点是definitions节点。   这个元素中,能够定义多个流程定义(不过咱们建议每一个文件只包含一个流程定义,   能够简化开发过程当中的维护难度)。   一个空的流程定义看起来像下面这样。注意,definitions元素   最少也要包含xmlnstargetNamespace的声明。    targetNamespace能够是任意值,它用来对流程实例进行分类。   

<definitions   xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"   xmlns:activiti="http://activiti.org/bpmn"   targetNamespace="Examples">
 
<processid="myProcess"name="My First Process">     ..   </process>
</definitions>

 

你也能够选择添加线上的BPMN 2.0格式位置,   下面是ecilpse中的xml配置。   

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL                     http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd

 

process元素有两个属性:   

  • id:这个属性是必须的, 它对应着Activiti         ProcessDefinition对象的key属性。 ‘id能够用来启动流程定义的流程实例, 经过RuntimeServicestartProcessInstanceByKey方法。 这个方法会一直使用最新发布版本的流程定义(译者注:实际中通常都使用这种方式启动流程)。         

    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");

    注意,它和startProcessInstanceById方法不一样。 这个方法指望使用Activiti引擎在发布时自动生成的id。, 能够经过调用processDefinition.getId()方法得到这个值。 生成的id的格式为'key:version', 最大长度限制为64个字符, 若是你在启动时抛出了一个ActivitiException,说明生成的id太长了, 须要限制流程的key的长度。       

  • name:这个属性是可选的, 对应ProcessDefinitionname属性。 引擎本身不会使用这个属性,它能够用来在用户接口显示便于阅读的名称。          

 

快速起步:10分钟教程

  这张咱们会演示一个(很是简单)的业务流程,咱们会经过它介绍   一些基本的Activiti概念和API。    

前提

    教程假设你已经能安装并运行Activiti demo, 而且你使用了独立运行的H2服务器。修改db.properties,设置其中的 jdbc.url=jdbc:h2:tcp://localhost/activiti,而后根据H2的文档启动独立服务器。      

目标

    教程的目标是学习Activiti和一些基本的BPMN 2.0概念。 最终结果是一个简单的Java SE程序能够发布流程定义, 经过Activiti引擎API操做流程。 咱们也会使用一些Activiti相关的工具。固然,咱们在教程中所学的 也能够用于你构建本身的业务流程web应用。      

用例

用例很直接:咱们有一个公司,就叫BPMCorp。 在BPMCopr中,每月都要给公司领导一个金融报表。 由会计部门负责。 当报表完成时,一个上级领导须要审批文档, 而后才能发给全部领导。      

流程图

上面描述的业务流程能够用Activiti Designer 进行可视化设计。 而后,为了这个教程,咱们会手工编写XML,这样能够学到更多知识细节。 咱们流程的图形化BPMN 2.0标记看起来像这样:   

咱们看到有空开始事件(左侧圆圈), 后面是两个用户任务:         “制做月度财报”和         “验证月度财报”,最后是         空结束事件(右侧粗线圆圈)。      

XML内容

    业务流程的XML内容(FinancialReportProcess.bpmn20.xml)以下所示: 很容易找到流程的主要元素(点击连接能够了解BPMN 2.0结构的详细信息):        

  • (空)开始事件   是咱们流程的入口。            

  • 用户任务是流程中与操做者相关的任务声明。   注意第一个任务分配给accountancy组,   第二个任务分配给management组。   参考用户任务分配章节   了解更多关于用户任务分配人员和群组的问题。            

  •   当流程达到空结束事件就会结束。            

  •   这些元素都使用连线链接。   这些连线拥有sourcetarget属性,   定义了连线的方向。            

 

<definitionsid="definitions"   targetNamespace="http://activiti.org/bpmn20"   xmlns:activiti="http://activiti.org/bpmn"   xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
       
<processid="financialReport"name="Monthly financial report reminder process">
         
<startEventid="theStart"/>
         
<sequenceFlowid='flow1'sourceRef='theStart'targetRef='writeReportTask'/>
         
<userTaskid="writeReportTask"name="Write monthly financial report">             <documentation>               Write monthly financial report for publication to shareholders.             </documentation>             <potentialOwner>               <resourceAssignmentExpression>                 <formalExpression>accountancy</formalExpression>               </resourceAssignmentExpression>             </potentialOwner>           </userTask>
         
<sequenceFlowid='flow2'sourceRef='writeReportTask'targetRef='verifyReportTask'/>
         
<userTaskid="verifyReportTask"name="Verify monthly financial report">             <documentation>               Verify monthly financial report composed by the accountancy department.               This financial report is going to be sent to all the company shareholders.             </documentation>             <potentialOwner>               <resourceAssignmentExpression>                 <formalExpression>management</formalExpression>               </resourceAssignmentExpression>             </potentialOwner>           </userTask>
         
<sequenceFlowid='flow3'sourceRef='verifyReportTask'targetRef='theEnd'/>
         
<endEventid="theEnd"/>
       
</process>
</definitions>

 

启动一个流程实例

    如今咱们建立好了业务流程的流程定义。 有了这个流程定义,咱们能够建立流程实例了。 这时,一个流程实例对应了特定月度财报的建立和审批。 全部流程实例都共享同一个流程定义。      

    为了使用流程定义建立流程实例, 首先要发布业务流程, 这意味着两方面:        

  •   流程定义会保存到持久化的数据存储里,   是为你的Activiti引擎特别配置。因此部署好你的业务流程,   咱们就能确认引擎重启后还能找到流程定义。            

  • BPMN 2.0流程文件会解析成内存对象模型,   能够经过Activiti API操做。            

能够经过发布章节得到关于发布的更多信息。      

    就像章节里描述的同样,有不少种方式能够进行发布。 一种方式是经过下面的API。注意全部与Activiti引擎的交互都是经过services。        

Deployment deployment = repositoryService.createDeployment()   .addClasspathResource("FinancialReportProcess.bpmn20.xml")   .deploy();

 

如今咱们能够启动一个新流程实例, 使用咱们定义在流程定义里的id(对应XML文件中的process元素)。 注意这里的id对于Activiti来讲, 应该叫作key(译者注:通常在流程模型中使用的ID,在Activiti中都是Key,好比任务ID等...)。        

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");

这会建立一个流程实例,首先进入开始事件。 开始事件以后,它会沿着全部的外出连线(这里只有一条)执行, 到达第一个任务(“制做月度财报”)。 Activiti会把一个任务保存到数据库里。 这时,分配到这个任务的用户或群组会被解析,也会保存到数据库里。 须要注意,Activiti引擎会继续执行流程的环节,除非遇到一个 等待状态,好比用户任务。 在等待状态下,当前的流程实例的状态会保存到数据库中。 直到用户决定完成任务才能改变这个状态。这时,引擎会继续执行, 直到遇到下一个等待状态,或流程结束。 若是中间引擎重启或崩溃, 流程状态也会安全的保存在数据库里。      

    任务建立以后,startProcessInstanceByKey会在到达用户任务 这个等待状态以后才会返回。这时,任务分配给了一个组, 这意味着这个组是执行这个任务的候选组。      

    咱们如今把全部东西都放在一块儿,来建立一个简单的java程序。 建立一个eclipse项目,把Activiti的jar和依赖放到classpath下。 (这些均可以在Activiti发布包的libs目录下找到)。 在调用Activiti服务以前,咱们必须构造一个ProcessEngine, 它可让咱们访问服务。这里咱们使用'单独运行'的配置, 这会使用demo安装时的数据库来构建ProcessEngine。      

    你能够在这里下载流程定义XML。 这个文件包含了上面介绍的XML,也包含了必须的BPMN图像信息 以便在Activiti工具中能编辑流程。      

 

publicstaticvoid main(String[] args){
 
// Create Activiti process engine   ProcessEngine processEngine =ProcessEngineConfiguration     .createStandaloneProcessEngineConfiguration()     .buildProcessEngine();
 
// Get Activiti services   RepositoryService repositoryService = processEngine.getRepositoryService();   RuntimeService runtimeService = processEngine.getRuntimeService();
 
// Deploy the process definition   repositoryService.createDeployment()     .addClasspathResource("FinancialReportProcess.bpmn20.xml")     .deploy();
 
// Start a process instance   runtimeService.startProcessInstanceByKey("financialReport");}

 

任务列表

    咱们如今能够经过TaskService来得到任务了,添加如下逻辑:        

List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();

注意咱们传入的用户必须是accountancy组的一个成员, 要和流程定义中向对应:        

<potentialOwner>   <resourceAssignmentExpression>     <formalExpression>accountancy</formalExpression>   </resourceAssignmentExpression></potentialOwner>

咱们也能够使用群组名称,经过任务查询API来得到相关的结果。 如今能够在代码中添加以下逻辑:         

TaskService taskService = processEngine.getTaskService();List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();

 

由于咱们配置的ProcessEngine使用了与demo相同的数据, 咱们能够登陆到Activiti Explorer。 默认,accountancy(会计)组里没有任何人。使用kermit/kermit登陆,点击组,并建立一个新组。 而后点击用户,把组分配给fozzie。如今使用fozzie/fozzie登陆,如今咱们就能够启动咱们的业务流程了, 选择Processes页,在'月度财报''操做'列 点击'启动流程'。        

和上面介绍的那样,流程会执行到第一个用户任务。由于咱们以kermit登陆, 在启动流程实例以后,就能够看到有了一个新的待领任务。 选择任务页来查看这条新任务。 注意即便流程被其余人启动,任务仍是会被会计组里的全部人做为一个候选任务看到。        

 

领取任务

    如今一个会计要认领这个任务。 认领之后,这个用户就会成为任务的执行人 , 任务会从会计组的其余成员的任务列表中消失。 认领任务的代码以下所示:        

taskService.claim(task.getId(),"fozzie");

任务会进入认领任务人的我的任务列表中。        

List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();

 

在Activiti Explorer UI中,点击认领按钮,会执行相同的操做。 任务会移动到登陆用户的我的任务列表。 你也会看到任务的执行人已经变成当前登录的用户。       

 

完成任务

    如今会计能够开始进行财报的工做了。报告完成后, 他能够完成任务, 意味着任务所需的全部工做都完成了。        

taskService.complete(task.getId());

 

对于Activiti引擎,须要一个外部信息来让流程实例继续执行。 任务会把本身从运行库中删除。 流程会沿着单独一个外出连线执行,移动到第二个任务 ('审批报告')。 与第一个任务相同的机制会使用到第二个任务上, 不一样的是任务是分配给 management组。      

    在demo中,完成任务是经过点击任务列表中的完成按钮。 由于Fozzie不是会计,咱们先从Activiti Explorer注销 而后使用kermit登录(他是经理)。 第二个任务会进入未分配任务列表。      

结束流程

    审批任务能够像以前介绍的同样查询和领取。 完成第二个任务会让流程执行到结束事件,就会结束流程实例。 流程实例和全部相关的运行数据都会从数据库中删除。      

    登陆Activiti Explorer就能够进行验证, 能够看到保存流程运行数据的表中已经没有数据了。         

 

    经过程序,你也能够使用historyService判断流程已经结束了。        

HistoryService historyService = processEngine.getHistoryService();HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();System.out.println("Process instance end time: "+ historicProcessInstance.getEndTime());

 

代码总结

    把上述代码组合在一块儿,得到的代码以下所示 (这些代码考虑到你可能会在Activiti Explorer UI中启动一些流程实例。 这样,它会得到多个任务,而不是一个, 因此代码能够一直正常运行):        

publicclassTenMinuteTutorial{
 
publicstaticvoid main(String[] args){
   
// Create Activiti process engine     ProcessEngine processEngine =ProcessEngineConfiguration       .createStandaloneProcessEngineConfiguration()       .buildProcessEngine();
   
// Get Activiti services     RepositoryService repositoryService = processEngine.getRepositoryService();     RuntimeService runtimeService = processEngine.getRuntimeService();
   
// Deploy the process definition     repositoryService.createDeployment()       .addClasspathResource("FinancialReportProcess.bpmn20.xml")       .deploy();
   
// Start a process instance     String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();
   
// Get the first task     TaskService taskService = processEngine.getTaskService();     List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();     for(Task task : tasks){       System.out.println("Following task is available for accountancy group: "+ task.getName());
     
// claim it       taskService.claim(task.getId(),"fozzie");     }
   
// Verify Fozzie can now retrieve the task     tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();     for(Task task : tasks){       System.out.println("Task for fozzie: "+ task.getName());
     
// Complete the task       taskService.complete(task.getId());     }
   
System.out.println("Number of tasks for fozzie: "             + taskService.createTaskQuery().taskAssignee("fozzie").count());
   
// Retrieve and claim the second task     tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();     for(Task task : tasks){       System.out.println("Following task is available for accountancy group: "+ task.getName());       taskService.claim(task.getId(),"kermit");     }
   
// Completing the second task ends the process     for(Task task : tasks){       taskService.complete(task.getId());     }
   
// verify that the process is actually finished     HistoryService historyService = processEngine.getHistoryService();     HistoricProcessInstance historicProcessInstance =       historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();     System.out.println("Process instance end time: "+ historicProcessInstance.getEndTime());   }
}

 

这段代码包含在实例中的一个单元测试中(似的,你能够运行单元测试来测试你的流程。 参考单元测试章节来了解更多信息)。      

更多思考

    能够看到业务流程相对于现实来讲太简单了。 然而,你能够了解Activiti中的BPMN 2.0结构, 你能够考虑对业务流程进行如下方面的增强:        

  •   定义网关来实现决策环节。   这样,经理能够驳回财报,   从新给会计建立一个任务。            

  •   考虑使用变量,   这样咱们能够保存或引用报告,   把它显示到表单中。            

  •   在流程最后加入服务任务,   把报告发给每一个领导。            

  •               其余            

 

Chapter 8. BPMN 2.0结构

本章介绍Activiti支持的BPMN 2.0结构, 以及对BPMN标准的扩展。

自定义扩展

BPMN 2.0标准对于各方都是一个好东西。最终用户不用担忧会绑死在   供应商提供的专有解决方案上。   框架,特别是activiti这样的开源框架,能够提供相同功能   (甚至是更好的实现),足以和大的供应商媲美。   按照BPMN 2.0标准,从大供应商的解决方案迁移到activiti    只会通过一个简单而平滑的过程。     

  标准很差的一点是,它经常是不一样公司之间大量讨论和妥协的结果。   (并且一般是愿景)。   做为开发者去阅读流程定义的BPMN 2.0 xml时,有时会感受   用这种结构和方法去作事太麻烦了。   所以activiti把简化开发做为最优先的事情,咱们会使用一些被称为    'Activiti BPMN扩展'的功能。   这些扩展是新的结构或方法来简化对应的结构,   它们并不属于BPMN 2.0规范。     

  虽然BPMN 2.0规范清楚的指明了如何开发自定义扩展,   可是咱们还要确认一下几点:       

  •     自定义扩展的前提是 有简单的方法 转换成标准方法。 因此当你决定使用自定义扩展时,不用担忧没办法回头。           

  •     当使用自定义扩展时,总会清楚的指明使用了 新的XML元素,属性,等等。 好比会使用activiti:命名空间前缀。           

  •     这些扩展的目标是最终把它们加入到下一版本的BPMN规范中, 或者至少能够引发对特定BPMN结构的讨论。           

 

  所以不管是是否想要使用自定义扩展,这都取决于你。   不少因素会影响决定这个决定(图形编辑器,公司策略,等等)。   只是由于咱们相信标准里的一些功能能够更简单或更高校,   因此才决定提供自定义扩展。请对扩展给予咱们(正面或负面)的评价,   或者是对自定义扩展的心想法。   说不定有一天你的想法就会加入到规范中。     

事件(Event)

事件用来代表流程的生命周期中发生了什么事。 事件老是画成一个圆圈。 在BPMN 2.0中, 事件有两大分类:捕获(catching)触发(throwing) 事件。       

  • 捕获(Catching):当流程执行到事件, 它会等待被触发。触发的类型是由内部图表或XML中的类型声明来决定的。 捕获事件与触发事件在显示方面是根据内部图表是否被填充来区分的(白色的)。           

  • 触发(Throwing):当流程执行到事件, 会触发一个事件。触发的类型是由内部图表或XML中的类型声明来决定的。 触发事件与捕获事件在显示方面是根据内部图表是否被填充来区分的(被填充为黑色)。           

 

事件定义

事件定义决定了事件的语义。若是没有事件定义,这个事件就不作什么特别的事情。 没有设置事件定义的开始事件不会在启动流程时作任何事情。若是给开始事件添加了一个事件定义 (好比定时器事件定义)咱们就声明了开始流程的事件 "类型 " (这时定时器事件监听器会在某个时间被触发)。  

定时器事件定义

定时器事件是根据指定的时间触发的事件。能够用于           开始事件,               中间事件          或 边界事件

      定时器定义必须下面介绍的一个元素:              

  • timeDate。使用 ISO 8601 格式指定一个肯定的时间,触发事件的时间。示例:                      

    <timerEventDefinition>     <timeDate>2011-03-11T12:13:14</timeDate></timerEventDefinition>

     

  • timeDuration。指定定时器以前要等待多长时间, timeDuration能够设置为timerEventDefinition的子元素。 使用ISO 8601规定的格式 (由BPMN 2.0规定)。示例(等待10天)。                      

    <timerEventDefinition>     <timeDuration>P10D</timeDuration></timerEventDefinition>

     

  • timeCycle。指定重复执行的间隔,   能够用来按期启动流程实例,或为超时时间发送多个提醒。    timeCycle元素能够使用两种格式。第一种是    ISO 8601    标准的格式。示例(重复3次,每次间隔10小时):                          

    <timerEventDefinition>     <timeCycle>R3/PT10H</timeCycle></timerEventDefinition>

     

    另外,你能够使用cron表达式指定timeCycle,下面的例子是从整点开始,每5分钟执行一次:                          

    00/5***?

    请参考教程   来了解如何使用cron表达式。                      

    注意: 第一个数字表示秒,而不是像一般Unix cron中那样表示分钟。                      

    重复的时间周期能更好的处理相对时间,它能够计算一些特定的时间点   (好比,用户任务的开始时间),而cron表达式能够处理绝对时间 -    这对定时启动事件特别有用。

 

    你能够在定时器事件定义中使用表达式,这样你就能够经过流程变量来影响那个定时器定义。 流程定义必须包含ISO 8601(或cron)格式的字符串,以匹配对应的时间类型。  

  <boundaryEventid="escalationTimer"cancelActivity="true"attachedToRef="firstLineSupport">      <timerEventDefinition>       <timeDuration>${duration}</timeDuration>     </timerEventDefinition>   </boundaryEvent>   

 

注意: 只有启用job执行器以后,定时器才会被触发。   (activiti.cfg.xml中的jobExecutorActivate须要设置为true,   不过,默认job执行器是关闭的)。        

错误事件定义

  错误事件是由指定错误触发的。        

重要提醒:BPMN错误与Java异常彻底不同。   实际上,他俩一点儿共同点都没有。BPMN错误事件是为了对    业务异常建模。Java异常是要    用特定方式处理。

错误事件定义会引用一个error元素。下面是一个error元素的例子,引用了一个错误声明:  
<endEventid="myErrorEndEvent">   <errorEventDefinitionerrorRef="myError"/></endEvent>          
引用相同error元素的错误事件处理器会捕获这个错误。      

信号事件定义

信号事件会引用一个已命名的信号。信号全局范围的事件(广播语义)。 会发送给全部激活的处理器。     

信号事件定义使用signalEventDefinition元素。 signalRef属性会引用definitions根节点里定义的signal子元素。 下面是一个流程的实例,其中会抛出一个信号,并被中间事件捕获。

<definitions... >         <!-- declaration of the signal -->         <signalid="alertSignal"name="alert"/>
       
<processid="catchSignal">                 <intermediateThrowEventid="throwSignalEvent"name="Alert">                         <!-- signal event definition -->                         <signalEventDefinitionsignalRef="alertSignal"/>                 </intermediateThrowEvent>                 ...                 <intermediateCatchEventid="catchSignalEvent"name="On Alert">                         <!-- signal event definition -->                         <signalEventDefinitionsignalRef="alertSignal"/>                 </intermediateCatchEvent>                 ...         </process></definitions>

signalEventDefinition引用相同的signal元素。        

触发信号事件

既能够经过bpmn节点由流程实例触发一个信号,也能够经过API触发。 下面的org.activiti.engine.RuntimeService中的方法 能够用来手工触发一个信号。        

RuntimeService.signalEventReceived(String signalName);RuntimeService.signalEventReceived(String signalName,String executionId);                                 

signalEventReceived(String signalName);signalEventReceived(String signalName, String executionId);之间的区别是 第一个方法会把信号发送给全局全部订阅的处理器(广播语义), 第二个方法只把信息发送给指定的执行。         

捕获信号事件

信号事件能够被中间捕获信号事件或边界信息事件捕获。    

查询信号事件的订阅

能够查询全部订阅了特定信号事件的执行:    

 List<Execution> executions = runtimeService.createExecutionQuery()       .signalEventSubscriptionName("alert")       .list();                 

咱们能够使用signalEventReceived(String signalName, String executionId)方法 吧信号发送给这些执行。

信号事件范围

   默认,信号会在流程引擎范围内进行广播。就是说,    你能够在一个流程实例中抛出一个信号事件,其余不一样流程定义的流程实例    均可以监听到这个事件。        

然而,有时只但愿在同一个流程实例中响应这个信号事件。 好比一个场景是,流程实例中的同步机制,若是两个或更多活动是互斥的。        

   若是想要限制信号事件的范围,能够使用信号事件定义的scope 属性    (不是BPMN2.0的标准属性):            

<signalid="alertSignal"name="alert"activiti:scope"processInstance"/>

           The default value for this is attribute is "global".        

信号事件实例

下面是两个不一样流程使用信号交互的例子。第一个流程在保险规则更新或改变时启动。 在修改被参与者处理时,会触发一个信息,通知规则改变:    

这个时间会被全部感兴趣的流程实例捕获。下面是一个订阅这个事件的流程实例。    

注意:要了解信号事件是广播给全部 激活的处理器的。 这意味着在上面的例子中,全部流程实例都会接收到这个事件。 这就是咱们想要的。然而,有的状况下并不想要这种广播行为。 考虑下面的流程:    

上述流程描述的模式activiti并不支持。这种想法是执行“do something”任务时出现的错误,会被边界错误事件捕获, 而后使用信号传播给并发路径上的分支,进而中断"do something inparallel"任务。 目前,activiti实际运行的结果与指望一致。信号会传播给边界事件并中断任务。 可是,根据信号的广播含义,它也会传播给全部其余订阅了信号事件的流程实例。 因此,这就不是咱们想要的结果。    

注意: 信号事件不会执行任何与特定流程实例的联系。 若是你只想把一个信息发给指定的流程实例,须要手工关联,再使用 signalEventReceived(String signalName, String executionId)和对应的 查询机制。    

消息事件定义

消息事件会引用一个命名的消息。每一个消息都有名称和内容。和信号不一样, 消息事件总会直接发送个一个接受者。     

消息事件定义使用messageEventDefinition元素。 messageRef属性引用了definitions根节点下的 一个message子元素。下面是一个使用两个消息事件的流程例子, 开始事件和中间捕获事件分别声明和引用了两个消息事件。

<definitionsid="definitions"   xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"   xmlns:activiti="http://activiti.org/bpmn"   targetNamespace="Examples"   xmlns:tns="Examples">
 
<messageid="newInvoice"name="newInvoiceMessage"/>   <messageid="payment"name="paymentMessage"/>
 
<processid="invoiceProcess">
   
<startEventid="messageStart">         <messageEventDefinitionmessageRef="newInvoice"/>     </startEvent>     ...     <intermediateCatchEventid="paymentEvt">         <messageEventDefinitionmessageRef="payment"/>     </intermediateCatchEvent>     ...   </process>
</definitions>

 

触发消息事件

做为一个嵌入式的流程引擎,activiti不能真正接收一个消息。这些环境相关,与平台相关的活动 好比链接到JMS(Java消息服务)队列或主题或执行WebService或REST请求。 这个消息的接收是你要在应用或架构的一层实现的,流程引擎则内嵌其中。       

在你的应用接收一个消息以后,你必须决定如何处理它。 若是消息应该触发启动一个新流程实例, 在下面的RuntimeService的两个方法中选择一个执行:        

ProcessInstance startProcessInstanceByMessage(String messageName);ProcessInstance startProcessInstanceByMessage(String messageName,Map<String,Object> processVariables);ProcessInstance startProcessInstanceByMessage(String messageName,String businessKey,Map<String,Object> processVariables);            

这些方法容许使用对应的消息系统流程实例。         

若是消息须要被运行中的流程实例处理,首先要根据消息找到对应的流程实例 (参考下一节)而后触发这个等待中的流程。 RuntimeService提供了以下方法能够基于消息事件的订阅来触发流程继续执行:        

void messageEventReceived(String messageName,String executionId);void messageEventReceived(String messageName,String executionId,HashMap<String,Object> processVariables);    

 

查询消息事件的订阅

Activiti支持消息开始事件和中间消息事件。    
  • 消息开始事件的状况,消息事件订阅分配给一个特定的     process definition。这个消息订阅能够使用ProcessDefinitionQuery查询到:    

    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()       .messageEventSubscription("newCallCenterBooking")       .singleResult();                 

    由于同时只能有一个流程定义关联到消息的订阅点,查询老是返回0或一个结果。 若是流程定义更新了, 那么只有最新版本的流程定义会订阅到消息事件上。    

  • 中间捕获消息事件的状况,消息事件订阅会分配给特定的执行。 这个消息事件订阅能够使用ExecutionQuery查询到:    

    Execution execution = runtimeService.createExecutionQuery()       .messageEventSubscriptionName("paymentReceived")       .variableValueEquals("orderId", message.getOrderId())       .singleResult();

    这个查询能够调用对应的查询,一般是流程相关的信息 (这里,最多只能有一个流程实例对应着orderId)。    

消息事件实例

下面是一个使用两个不一样消息启动的流程实例:    

能够用在,流程须要不一样的方式来区分开始事件,然后最终会进入一样的路径。    

开始事件

开始事件用来指明流程在哪里开始。开始事件的类型(流程在接收事件时启动, 仍是在指定时间启动,等等),定义了流程如何启动, 这经过事件中不一样的小图表来展现。 在XML中,这些类型是经过声明不一样的子元素来区分的。    

     开始事件都是捕获事件: 最终这些事件都是(一直)等待着,直到对应的触发时机出现。    

     在开始事件中,能够设置下面的activiti特定属性:    

  • initiator:当流程启动时,把当前登陆的用户保存到哪一个变量名中。    示例以下:           

    <startEventid="request"activiti:initiator="initiator"/>

    登陆的用户必须使用IdentityService.setAuthenticatedUserId(String)方法设置,    并像这样包含在try-finally代码中:           

    try{   identityService.setAuthenticatedUserId("bono");   runtimeService.startProcessInstanceByKey("someProcessKey");}finally{   identityService.setAuthenticatedUserId(null);}

    这段代码来自Activiti Explorer,因此它能够和    Chapter 9, 表单一块儿结合使用。         

空开始事件

描述

  空开始事件技术上意味着没有指定启动流程实例的触发条件。   这就是说引擎不能预计何时流程实例会启动。   空开始事件用于,当流程实例要经过API启动的场景,   经过调用startProcessInstanceByXXX方法。          

ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();

 

注意: 子流程都有一个空开始事件。        

图形标记

    空开始事件显示成一个圆圈,没有内部图表(没有触发类型)     

 

XML结构

  空开始事件的XML结构是普通的开始事件定义,没有任何子元素   (其余开始事件类型都有一个子元素来声明本身的类型)          

<startEventid="start"name="my start event"/>

 

空开始事件的自定义扩展

formKey:引用用户在启动新流程实例时须要填写的表单模板,    更多信息能够参考表单章节。 实例:

<startEventid="request"activiti:formKey="org/activiti/examples/taskforms/request.form"/>

 

定时开始事件

描述

定时开始事件用来在指定的时间建立流程实例。 它能够同时用于只启动一次的流程 和应该在特定时间间隔启动屡次的流程。        

注意:子流程不能使用定时开始事件。        

注意:定时开始事件在流程发布后就会开始计算时间。   不须要调用startProcessInstanceByXXX,虽然也而已调用启动流程的方法,   可是那会致使调用startProcessInstanceByXXX时启动过多的流程。

注意:当包含定时开始事件的新版本流程部署时,    对应的上一个定时器就会被删除。这是由于一般不但愿自动启动旧版本流程的流程实例。

图形标记

    定时开始事件显示为了一个圆圈,内部是一个表。                

 

XML内容

  定时开始事件的XML内容是普通开始事件的声明,包含一个定时定义子元素。   请参考定时定义   查看配合细节。         

示例:流程会启动4次,每次间隔5分钟,从2011年3月11日,12:13开始计时。           

        <startEventid="theStart">             <timerEventDefinition>                 <timeCycle>R4/2011-03-11T12:13/PT5M</timeCycle>             </timerEventDefinition>         </startEvent>            

 

示例:流程会根据选中的时间启动一次。           

        <startEventid="theStart">             <timerEventDefinition>                 <timeDate>2011-03-11T12:13:14</timeDate>             </timerEventDefinition>         </startEvent>            

 

消息开始事件

描述

消息开始事件能够用其使用一个命名的消息来启动流程实例。 这样能够帮助咱们使用消息名称来选择正确的开始事件。         

发布包含一个或多个消息开始事件的流程定义时,须要考虑下面的条件:

  • 消息开始事件的名称在给定流程定义中不能重复。流程定义不能包含多个名称相同的消息开始事件。 若是两个或以上消息开始事件应用了相同的事件,或两个或以上消息事件引用的消息名称相同,activiti会在发布流程定义时抛出异常。

  • 消息开始事件的名称在全部已发布的流程定义中不能重复。 若是一个或多个消息开始事件引用了相同名称的消息,而这个消息开始事件已经部署到不一样的流程定义中, activiti就会在发布时抛出一个异常。

  • 流程版本:在发布新版本的流程定义时,以前订阅的消息订阅会被取消。 若是新版本中没有消息事件也会这样处理。

 

启动流程实例,消息开始事件能够使用 下列RuntimeService中的方法来触发:        

ProcessInstance startProcessInstanceByMessage(String messageName);ProcessInstance startProcessInstanceByMessage(String messageName,Map<String,Object> processVariables);ProcessInstance startProcessInstanceByMessage(String messageName,String businessKey,Map<String,Object< processVariables);                                 

这里的messageNamemessageEventDefinitionmessageRef属性引用的message元素的name属性。 启动流程实例时,要考虑一下因素:

  • 消息开始事件只支持顶级流程。消息开始事件不支持内嵌子流程。

  • 若是流程定义有多个消息开始事件,runtimeService.startProcessInstanceByMessage(...) 会选择对应的开始事件。

  • 若是流程定义有多个消息开始事件和一个空开始事件。 runtimeService.startProcessInstanceByKey(...)runtimeService.startProcessInstanceById(...)会使用空开始事件启动流程实例。

  • 若是流程定义有多个消息开始事件,并且没有空开始事件, runtimeService.startProcessInstanceByKey(...)runtimeService.startProcessInstanceById(...)会抛出异常。

  • 若是流程定义只有一个消息开始事件, runtimeService.startProcessInstanceByKey(...)runtimeService.startProcessInstanceById(...)会使用这个消息开始事件启动流程实例。

  • 若是流程被调用环节(callActivity)启动,消息开始事件只支持以下状况:

    • 在消息开始事件之外,还有一个单独的空开始事件

    • 流程只有一个消息开始事件,没有空开始事件。

     

 

图形标记

消息开始事件是一个圆圈,中间是一个消息事件图标。图标是白色未填充的,来表示捕获(接收)行为。                

 

XML内容

  消息开始事件的XML内容时在普通开始事件申请中包含一个    messageEventDefinition子元素:          

<definitionsid="definitions"   xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"   xmlns:activiti="http://activiti.org/bpmn"   targetNamespace="Examples"   xmlns:tns="Examples">
 
<messageid="newInvoice"name="newInvoiceMessage"/>
 
<processid="invoiceProcess">
   
<startEventid="messageStart">         <messageEventDefinitionmessageRef="tns:newInvoice"/>     </startEvent>     ...   </process>
</definitions>

 

信号开始事件

描述

signal开始事件,能够用来经过一个已命名的信号(signal)来启动一个流程实例。 信号能够在流程实例内部使用“中间信号抛出事务”触发, 也能够经过API(runtimService.signalEventReceivedXXX 方法)触发。两种状况下, 全部流程实例中拥有相同名称的signalStartEvent都会启动。            

                注意,在两种状况下,均可以选择同步或异步的方式启动流程实例。            

                必须向API传入signalName, 这是signal元素的name属性值, 它会被signalEventDefinitionsignalRef属性引用。            

图形标记

    信号开始事件显示为一个中间包含信号事件图标的圆圈。标记是无填充的,表示捕获(接收)行为。                

 

XML格式

signalStartEvent的XML格式是标准的startEvent声明,其中包含一个signalEventDefinition子元素:          

    <signalid="theSignal"name="The Signal"/>
   
<processid="processWithSignalStart1">         <startEventid="theStart">           <signalEventDefinitionid="theSignalEventDefinition"signalRef="theSignal"  />         </startEvent>         <sequenceFlowid="flow1"sourceRef="theStart"targetRef="theTask"/>         <userTaskid="theTask"name="Task in process A"/>         <sequenceFlowid="flow2"sourceRef="theTask"targetRef="theEnd"/>         <endEventid="theEnd"/>     </process>

 

错误开始事件

描述

错误开始事件能够用来触发一个事件子流程。              错误开始事件不能用来启动流程实例。         

           错误开始事件都是中断事件。         

图形标记

    错误开始事件是一个圆圈,包含一个错误事件标记。标记是白色未填充的,来表示捕获(接收)行为。                

 

XML内容

  错误开始事件的XML内容是普通开始事件定义中,包含一个    errorEventDefinition子元素。          

<startEventid="messageStart">         <errorEventDefinitionerrorRef="someError"/></startEvent>

 

结束事件

    结束事件表示(子)流程(分支)的结束。 结束事件都是触发事件。 这是说当流程达到结束事件,会触发一个结果。 结果的类型是经过事件的内部黑色图标表示的。 在XML内容中,是经过包含的子元素声明的。   

空结束事件

描述

  空结束事件意味着到达事件时不会指定抛出的结果。   这样,引擎会直接结束当前执行的分支,不会作其余事情。        

图形标记

  空结束事件是一个粗边圆圈,内部没有小图表(无结果类型)          

 

XML内容

  空结束事件的XML内容是普通结束事件定义,不包含子元素   (其余结束事件类型都会包含声明类型的子元素)。          

<endEventid="end"name="my end event"/>

 

错误结束事件

描述

  当流程执行到错误结束事件,   流程的当前分支就会结束,并抛出一个错误。   这个错误能够被对应的中间边界错误事件捕获。   若是找不到匹配的边界错误事件,就会抛出一个异常。     

图形标记

  错误结束事件是一个标准的结束事件(粗边圆圈),内部有错误图标。   错误图表是全黑的,表示触发语法。          

 

XML内容

  错误结束事件的内容是一个错误事件,   子元素为errorEventDefinition。          

<endEventid="myErrorEndEvent">   <errorEventDefinitionerrorRef="myError"/></endEvent>           

errorRef属性引用定义在流程外部的error元素:          

<errorid="myError"errorCode="123"/> ... <processid="myProcess"> ...           

errorerrorCode用来查找   匹配的捕获边界错误事件。   若是errorRef与任何error都不匹配,   就会使用errorRef来做为errorCode的缩写。   这是activiti特定的缩写。   更具体的说,见以下代码:                    

<errorid="myError"errorCode="error123"/> ... <processid="myProcess"> ...   <endEventid="myErrorEndEvent">     <errorEventDefinitionerrorRef="myError"/>   </endEvent>           

等同于                    

<endEventid="myErrorEndEvent">   <errorEventDefinitionerrorRef="error123"/></endEvent>           

 

注意 errorRef必须与BPMN 2.0格式相符, 必须是一个合法的QName。      

取消结束事件

[EXPERIMENTAL]

描述

取消结束事件只能与BPMN事务子流程结合使用。 当到达取消结束事件时,会抛出取消事件,它必须被取消边界事件捕获。 取消边界事件会取消事务,并触发补偿机制。     

图形标记

  取消结束事件显示为标准的结束事件(粗边圆圈),包含一个取消图标。   取消图标是全黑的,表示触发语法。          

 

XML内容

  取消结束事件内容是一个结束事件,   包含cancelEventDefinition子元素。          

<endEventid="myCancelEndEvent">   <cancelEventDefinition/></endEvent>           

 

边界事件

    边界事件都是捕获事件,它会附在一个环节上。 (边界事件不可能触发事件)。这意味着,当节点运行时, 事件会监听对应的触发类型。 当事件被捕获,节点就会中断, 同时执行事件的后续连线。   

    因此边界事件的定义方式都同样:

<boundaryEventid="myBoundaryEvent"attachedToRef="theActivity">       <XXXEventDefinition/></boundaryEvent>

 

边界事件使用以下方式进行定义:     

  •   惟一标识(流程范围)         

  •   使用caught属性   引用事件衣服的节点。   注意边界事件和它们附加的节点在同一级别上。   (好比,边界事件不是包含在节点内的)。         

  •   格式为XXXEventDefinition的XML子元素   (好比,TimerEventDefinitionErrorEventDefinition,等等)   定义了边界事件的类型。参考对应的边界事件类型,   得到更多细节。         

 

定时边界事件

描述

  定时边界事件就是一个暂停等待警告的时钟。当流程执行到绑定了边界事件的环节,   会启动一个定时器。   当定时器触发时(好比,必定时间以后),环节就会中断,   并沿着定时边界事件的外出连线继续执行。        

图形标记

  定时边界事件是一个标准的边界事件(边界上的一个圆圈),   内部是一个定时器小图标。          

 

XML内容

  定时器边界任务定义是一个正规的边界事件。          指定类型的子元素是timerEventDefinition元素。

<boundaryEventid="escalationTimer"cancelActivity="true"attachedToRef="firstLineSupport">    <timerEventDefinition>     <timeDuration>PT4H</timeDuration>   </timerEventDefinition></boundaryEvent>

请参考定时事件定义得到更多定时其配置的细节。        

  在流程图中,能够看到上述例子中的圆圈边线是虚线:          

          经典场景是发送一个升级邮件,可是不打断正常流程的执行。        

  由于BPMN 2.0中,中断和非中断的事件仍是有区别的。默认是中断事件。   非中断事件的状况,不会中断原始环节,那个环节还停留在原地。   对应的,会建立一个新分支,并沿着事件的流向继续执行。   在XML内容中,要把cancelActivity属性设置为false:

<boundaryEventid="escalationTimer"cancelActivity="false"attachedToRef="firstLineSupport"/>

 

注意:边界定时事件只能在job执行器启用时使用。   (好比,把activiti.cfg.xml中的jobExecutorActivate    设置为true,由于默认job执行器默认是禁用的)。        

边界事件的已知问题

    使用边界事件有一个已知的同步问题。 目前,不能边界事件后面不能有多条外出连线 (参考ACT-47)。 解决这个问题的方法是在一个连线后使用并发网关。        

 

错误边界事件

描述

  节点边界上的中间捕获错误事件,   或简写成边界错误事件,   它会捕获节点范围内抛出的错误。     

定义一个边界错误事件,大多用于内嵌子流程,   或调用节点,对于子流程的状况,它会为全部内部的节点建立一个做用范围。   错误是由错误结束事件抛出的。   这个错误会传递给上层做用域,直到找到一个错误事件定义向匹配的边界错误事件。     

  当捕获了错误事件时,边界任务绑定的节点就会销毁,   也会销毁内部全部的执行分支   (好比,同步节点,内嵌子流程,等等)。   流程执行会继续沿着边界事件的外出连线继续执行。     

图形标记

  边界错误事件显示成一个普通的中间事件(圆圈内部有一个小圆圈)   放在节点的标记上,内部有一个错误小图标。错误小图标是白色的,   表示它是一个捕获事件。          

 

Xml内容

  边界错误事件定义为普通的边界事件:          

<boundaryEventid="catchError"attachedToRef="mySubProcess">   <errorEventDefinitionerrorRef="myError"/></boundaryEvent>           

错误结束事件同样,    errorRef引用了process元素外部的一个错误定义:   

<errorid="myError"errorCode="123"/> ... <processid="myProcess"> ...           

 

errorCode用来匹配捕获的错误:          

  •     若是没有设置errorRef,边界错误事件会捕获 全部错误事件,不管错误的errorCode是什么。              

  •     若是设置了errorRef,并引用了一个已存的错误, 边界事件就只捕获错误代码与之相同的错误。              

  •     若是设置了errorRef,可是BPMN 2.0中没有定义错误errorRef就会当作errorCode使用 (和错误结束事件的用法相似)。              

 

实例

  下面的流程实例演示了如何使用错误结束事件。   当完成'审核盈利'这个用户任务是,若是没有提供足够的信息,   就会抛出错误,错误会被子流程的边界任务捕获,   全部'回顾销售'子流程中的全部节点都会销毁。   (即便'审核客户比率'尚未完成),   并建立一个'提供更多信息'的用户任务。          

 

  这个流程也放在demo中了。流程XML和单元测试能够在    org.activiti.examples.bpmn.event.error包下找到。        

信号边界事件

描述

  节点边界的中间捕获信号,   或简称为边界信号事件,   它会捕获信号定义引用的相同信号名的信号。     

注意:与其余事件(好比边界错误事件)不一样,边界信号事件不仅捕获 它绑定方位的信号。信号事件是一个全局的范围(广播语义),就是说信号能够在任何地方触发, 即使是不一样的流程实例。     

注意:和其余事件(好比边界错误事件)不一样,捕获信号后,不会中止信号的传播。 若是你有两个信号边界事件,它们捕获相同的信号事件,两个边界事件都会被触发, 即便它们在不一样的流程实例中。     

图形标记

  边界信号事件显示为普通的中间事件(圆圈里有个小圆圈),位置在节点的边缘,   内部有一个信号小图标。信号图标是白色的(未填充),   来表示捕获的意思。           

 

XML内容

    边界信号事件定义为普通的边界事件:         

<boundaryEventid="boundary"attachedToRef="task"cancelActivity="true">           <signalEventDefinitionsignalRef="alertSignal"/></boundaryEvent>                         

 

实例

    参考信号事件定义章节。         

消息边界事件

描述

  节点边界上的中间捕获消息,   或简称边界消息事件,根据引用的消息定义捕获相同消息名称的消息。     

图形标记

  边界消息事件显示成一个普通的中间事件(圆圈里有个小圆圈),位于节点边缘,   内部是一个消息小图标。消息图标是白色(无填充),   表示捕获语义。           

          注意,边界消息事件多是中断(右侧)或非中断(左侧)的。         

XML内容

    边界消息事件定义为标准的边界事件:     

<boundaryEventid="boundary"attachedToRef="task"cancelActivity="true">           <messageEventDefinitionmessageRef="newCustomerMessage"/></boundaryEvent>                         

 

实例

    参考消息事件定义章节。         

取消边界事件

[EXPERIMENTAL]

描述

  在事务性子流程的边界上的中间捕获取消,   或简称为边界取消事件 cancel event,   当事务取消时触发。当取消边界事件触发时,首先中断当前做用域的全部执行。   而后开始补偿事务内的全部激活的补偿边界事件。   补偿是同步执行的。例如,离开事务钱,边界事务会等待补偿执行完毕。   当补偿完成后,事务子流程会沿着取消边界事务的外出连线继续执行。     

注意:每一个事务子流程只能有一个取消边界事件。     

注意:若是事务子流程包含内嵌子流程,补偿只会触发已经成功完成的子流程。     

注意:若是取消边界子流程对应的事务子流程配置为多实例, 若是一个实例触发了取消,就会取消全部实例。     instances.     

图形标记

  取消边界事件显示为了一个普通的中间事件(圆圈里套小圆圈),在节点的边缘,   内部是一个取消小图标。取消图标是白色(无填充),   代表是捕获语义。           

 

XML内容

    取消边界事件定义为普通边界事件:         

<boundaryEventid="boundary"attachedToRef="transaction">           <cancelEventDefinition/></boundaryEvent>                         

由于取消边界事件都是中断的,因此不须要使用cancelActivity属性。       

补偿边界事件

[EXPERIMENTAL]

描述

  节点边界的中间捕获补偿,   或简称为补偿边界事件,   能够用来设置一个节点的补偿处理器。     

补偿边界事件必须使用直接引用设置惟一的补偿处理器。     

补偿边界事件与其余边界事件的策略不一样。 其余边界事件(好比信号边界事件)当到达关联的节点就会被激活。 离开节点时,就会挂起,对应的事件订阅也会取消。 补偿边界事件则不一样。补偿边界事件在关联的节点成功完成时激活。 当补偿事件触发或对应流程实例结束时,事件订阅才会删除。 它遵循以下规则:    

  • 补偿触发时,补偿边界事件对应的补偿处理器会调用相同次数,根据它对应的节点的成功次数。    

  • 若是补偿边界事件关联到多实例节点, 补偿事件会订阅每一个实例。    

  • 若是补偿边界事件关联的节点中包含循环, 补偿事件会在每次节点执行时进行订阅。    

  • 若是流程实例结束,订阅的补偿事件都会结束。    

 

注意:补偿边界事件不支持内嵌子流程。     

图形标记

  补偿边界事件显示为标准中间事件(圆圈里套圆圈),位于节点边缘,   内部有一个补偿小图标。补偿图标是白色的(无填充),   表示捕获语义。另外,下面的图形演示了使用无方向的关联,   为边界事件设置补偿处理器。           

 

XML内容

    补偿边界事件定义为标准边界事件:         

<boundaryEventid="compensateBookHotelEvt"attachedToRef="bookHotel">           <compensateEventDefinition/></boundaryEvent>
<associationassociationDirection="One"id="a1"  sourceRef="compensateBookHotelEvt"targetRef="undoBookHotel"/>
<serviceTaskid="undoBookHotel"isForCompensation="true"activiti:class="..."/>

由于补偿边界事件在节点成功完成后激活, 因此不支持cancelActivity属性。       

中间捕获事件

全部中间捕获事件都使用一样的方式定义:

<intermediateCatchEventid="myIntermediateCatchEvent">       <XXXEventDefinition/></intermediateCatchEvent>

 

中间捕获事件的定义包括     

  •   惟一标识(流程范围内)         

  •   一个结构为XXXEventDefinition的XML子元素   (好比TimerEventDefinition等)   定义了中间捕获事件的类型。参考特定的捕获事件类型,   得到更多详情。         

 

定时中间捕获事件

描述

  定时中间事件做为一个监听器。当执行到达捕获事件节点,   就会启动一个定时器。   当定时器触发(好比,一段时间以后),流程就会沿着定时中间事件的外出节点继续执行。        

图形标记

    定时器中间事件显示成标准中间捕获事件,内部是一个定时器小图标。                

 

XML内容

  定时器中间事件定义为标准中间捕获事件。   指定类型的子元素为timerEventDefinition元素。                 

        <intermediateCatchEventid="timer">             <timerEventDefinition>                 <timeDuration>PT5M</timeDuration>             </timerEventDefinition>         </intermediateCatchEvent>                  

参考定时器事件定义了解配置信息。              

信号中间捕获事件

描述

  中间捕获信号事件   经过引用信号定义来捕获相同信号名称的信号。     

注意:与其余事件(好比错误事件)不一样,信号不会在捕获以后被消费。 若是你有两个激活的信号边界事件捕获相同的信号事件,两个边界事件都会被触发, 即使它们在不一样的流程实例中。     

图形标记

  中间信号捕获事件显示为一个普通的中间事件(圆圈套圆圈),   内部有一个信号小图标。信号小图标是白色的(无填充),   表示捕获语义。           

 

XML内容

信号中间事件定义为普通的中间捕获事件。 对应类型的子元素是signalEventDefinition元素。                 

<intermediateCatchEventid="signal">         <signalEventDefinitionsignalRef="newCustomerSignal"/></intermediateCatchEvent>                  

 

实例

        参考信号事件定义章节。         

消息中间捕获事件

描述

  一个中间捕获消息事件,捕获特定名称的消息。     

图形标记

  中间捕获消息事件显示为普通中间事件(圆圈套圆圈),   内部是一个消息小图标。消息图标是白色的(无填充),   表示捕获语义。           

 

XML内容

消息中间事件定义为标准中间捕获事件。 指定类型的子元素是messageEventDefinition元素。                 

<intermediateCatchEventid="message">         <messageEventDefinitionsignalRef="newCustomerMessage"/></intermediateCatchEvent>                  

 

实例

        参考消息事件定义章节。         

内部触发事件

全部内部触发事件的定义都是一样的:

<intermediateThrowEventid="myIntermediateThrowEvent">       <XXXEventDefinition/></intermediateThrowEvent>

 

内部触发事件定义包含     

  •   惟一标识(流程范围)         

  •   使用格式为XXXEventDefinition的XML子元素   (好比signalEventDefinition等)   定义中间触发事件的类型。   参考对应触发事件的类型,了解更多信息。         

 

中间触发空事件

  下面的流程图演示了一个空中间触发事件的例子,   它一般用于表示流程中的某个状态。          

  经过添加执行监听器,就能够很好地监控一些KPI。

<intermediateThrowEventid="noneEvent">   <extensionElements>     <activiti:executionListenerclass="org.activiti.engine.test.bpmn.event.IntermediateNoneEventTest$MyExecutionListener"event="start"/>   </extensionElements></intermediateThrowEvent>

这里你能够添加本身的代码,把事件发送给BAM工具或DWH。引擎不会为这个事件作任何事情,   它直接径直经过。        

信号中间触发事件

描述

  中间触发信号事件为定义的信号抛出一个信号事件。     

在activiti中,信号会广播到全部激活的处理器中(好比,因此捕获信号事件)。 信号能够经过同步和异步方式发布。    

  • 默认配置下,信号是同步发送的。就是说, 抛出事件的流程实例会等到信号发送给全部捕获流程实例才继续执行。 捕获流程实例也会在触发流程实例的同一个事务中执行, 意味着若是某个监听流程出现了技术问题(抛出异常),全部相关的实例都会失败。    

  • 信号也能够异步发送。这时它会在到达抛出信号事件后决定哪些处理器是激活的。 对这些激活的处理器,会保存一个异步提醒消息(任务),并发送给jobExecutor。    

 

图形标记

  中间信号触发事件显示为普通中间事件(圆圈套圆圈),   内部又一个信号小图标。信号图标是黑色的(有填充),   表示触发语义。           

 

XML内容

     消息中间事件定义为标准中间触发事件。 指定类型的子元素是signalEventDefinition元素。                 

<intermediateThrowEventid="signal">         <signalEventDefinitionsignalRef="newCustomerSignal"/></intermediateThrowEvent>                  

 

异步信号事件以下所示:       

<intermediateThrowEventid="signal">         <signalEventDefinitionsignalRef="newCustomerSignal"activiti:async="true"/></intermediateThrowEvent>                  

 

实例

        参考信号事件定义章节。         

补偿中间触发事件

[EXPERIMENTAL]

描述

  中间触发补偿事件   能够用来触发补偿。     

触发补偿: 补偿能够由特定节点或包含补偿事件的做用域触发。 补偿是经过分配给节点的补偿处理器来完成的。   

  • 当补偿由节点触发,对应的补偿处理器会根据节点成功完成的次数执行相同次数。   

  • 若是补偿由当前做用域触发,当前做用域的全部节点都会执行补偿, 也包含并发分支。   

  • 补偿的触发是继承式的:若是执行补偿的节点是子流程,补偿会做用到子流程中包含的全部节点。 若是子流程是内嵌节点,补偿会递归触发。 然而,补偿不会传播到流程的上层: 若是补偿在子流程中触发,不会传播到子流程范围外。 bpmn规范定义,由节点触发的流程只会做用到“子流程同一级别”。

  • activiti的补偿执行次序与流程执行顺序相反。 觉得着最后完成的节点会最早执行补偿,诸如此类。

  • 中间触发补偿事件能够用来补偿成功完成的事务性子流程。

 

注意: 若是补偿被一个包含子流程的做用域触发,子流程还包含了关联补偿处理器的节点, 补偿只会传播到子流程,若是它已经成功完成了。 若是子流程中的节点也完成了,并关联了补偿处理器, 若是子流程包含的这些节点尚未完成,就不会执行补偿处理器。 参考下面实例:

这个流程中,咱们有两个并发分支,一些分支时内嵌子流程,一个是“使用信用卡”节点。 假设两个分支都启动了,第一个分支等待用户完成“审核预约”任务。第二个分支执行“使用信用卡”节点, 并发生了一个错误,这致使“取消预约”事件,并触发补偿。 这时,并发子流程尚未结束,意味着补偿事件不会传播给子流程, 因此“取消旅店预约”这个补偿处理器不会执行。 若是用户任务(就是内嵌子流程)在“取消预约”以前完成了, 补偿就会传播给内嵌子流程。

流程变量: 当补偿内嵌子流程时,用来执行补偿处理器的分支能够访问子流程的本地流程实例, 由于这时它是子流程完成的分支。 为了实现这个功能,流程变量的快照会分配给分支(为执行子流程而建立的分支)。 为此,有如下限制条件:

  • 补偿处理器没法访问子流程内部建立的,添加到同步分支的变量。

  • 分配给分支的流程变量在继承关系上层的(分配给流程实例的流程变量没有包含在快照中): 补偿触发时,补偿处理器经过它们所在的地方访问这些流程变量。

  • 变量快照只用于内嵌子流程,不适用其余节点。

 

已知限制:

  • waitForCompletion="false"还不支持。当补偿使用中间触发补偿事件触发时, 事件没有等待,在补偿成功结束后。  

  • 补偿本身由并发分支执行。并发分支的执行顺序与被补偿的节点完成次序相反。 将来activiti可能支持选项来顺序执行补偿。

  • 补偿不会传播给callActivity调用的子流程实例。

 

图形标记

  中间补偿触发事件显示为标准中间事件(圆圈套圆圈),   内部是一个补偿小图标。补偿图标是黑色的(有填充),   表示触发语义。           

 

Xml内容

补偿中间事件定义为普通的中间触发事件。 对应类型的子元素是compensateEventDefinition元素。                 

<intermediateThrowEventid="throwCompensation">         <compensateEventDefinition/></intermediateThrowEvent>

另外,可选参数activityRef能够用来触发特定做用域/节点的补偿:                  

<intermediateThrowEventid="throwCompensation">         <compensateEventDefinitionactivityRef="bookHotel"/></intermediateThrowEvent>

 

顺序流

描述

  顺序流是链接两个流程节点的连线。   流程执行完一个节点后,会沿着节点的全部外出顺序流继续执行。   就是说,BPMN 2.0默认的行为就是并发的:   两个外出顺序流会创造两个单独的,并发流程分支。        

图形标记

  顺序流显示为从起点到终点的箭头。   箭头老是指向终点。          

 

XML内容

  顺序流须要流程范围内惟一的id,   以及对起点与    终点元素的引用。          

<sequenceFlowid="flow1"sourceRef="theStart"targetRef="theTask"/>

 

条件顺序流

描述

  能够为顺序流定义一个条件。离开一个BPMN 2.0节点时,   默认会计算外出顺序流的条件。   若是条件结果为true,    就会选择外出顺序流继续执行。当多条顺序流被选中时,   就会建立多条分支,   流程会继续以并行方式继续执行。        

注意:上面的讨论仅涉及BPMN 2.0节点(和事件),   不包括网关。网关会用特定的方式处理顺序流中的条件,   这与网关类型相关。        

图形标记

  条件顺序流显示为一个正常的顺序流,不过在起点有一个菱形。   条件表达式也会显示在顺序流上。          

 

XML内容

  条件顺序流定义为一个正常的顺序流,   包含conditionExpression子元素。   注意目前只支持tFormalExpressions,   若是没有设置xsi:type="",    就会默认值支持目前支持的表达式类型。          

<sequenceFlowid="flow"sourceRef="theStart"targetRef="theTask">   <conditionExpressionxsi:type="tFormalExpression">     <![CDATA[${order.price > 100 && order.price < 250}]]>   </conditionExpression></sequenceFlow>

 

当前条件表达式只能使用UEL,   能够参考表达式章节获取更多信息。   使用的表达式须要返回boolean值,不然会在解析表达式时抛出异常。          

  •     下面的例子引用了流程变量的数据, 经过getter调用JavaBean。              

     

    <conditionExpressionxsi:type="tFormalExpression">   <![CDATA[${order.price > 100 && order.price < 250}]]> </conditionExpression>

     

  • 这个例子经过调用方法返回一个boolean值。

    <conditionExpressionxsi:type="tFormalExpression">   <![CDATA[${order.isStandardOrder()}]]> </conditionExpression>

     

 

在activiti发布包中,包含如下流程实例,使用了值和方法表达式   (参考org.activiti.examples.bpmn.expression)包):          

 

默认顺序流

描述

  全部的BPMN 2.0任务和网关均可以设置一个默认顺序流。   只有在节点的其余外出顺序流不能被选中是,才会使用它做为外出顺序流继续执行。   默认顺序流的条件设置不会生效。        

图形标记

  默认顺序流显示为了普通顺序流,起点有一个“斜线”标记。          

 

XML内容

  默认顺序流经过对应节点的default属性定义。   下面的XML代码演示了排他网关设置了默认顺序流flow 2。   只有当conditionAconditionB都返回false时,   才会选择它做为外出连线继续执行。          

<exclusiveGatewayid="exclusiveGw"name="Exclusive Gateway"default="flow2"/><sequenceFlowid="flow1"sourceRef="exclusiveGw"targetRef="task1">   <conditionExpressionxsi:type="tFormalExpression">${conditionA}</conditionExpression></sequenceFlow><sequenceFlowid="flow2"sourceRef="exclusiveGw"targetRef="task2"/><sequenceFlowid="flow3"sourceRef="exclusiveGw"targetRef="task3">   <conditionExpressionxsi:type="tFormalExpression">${conditionB}</conditionExpression></sequenceFlow>           

对应下面的图形显示:          

 

网关

    网关用来控制流程的流向(或像BPMN 2.0里描述的那样,流程的tokens。) 网关能够消费也能够生成token。      

    网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型。        

 

排他网关

描述

  排他网关(也叫异或(XOR)网关,或更技术性的叫法    基于数据的排他网关),   用来在流程中实现决策。   当流程执行到这个网关,全部外出顺序流都会被处理一遍。   其中条件解析为true的顺序流(或者没有设置条件,概念上在顺序流上定义了一个'true')   会被选中,让流程继续运行。        

注意这里的外出顺序流   与BPMN 2.0一般的概念是不一样的。一般状况下,全部条件结果为true的顺序流   都会被选中,以并行方式执行,但排他网关只会选择一条顺序流执行。   就是说,虽然多个顺序流的条件结果为true,   那么XML中的第一个顺序流(也只有这一条)会被选中,并用来继续运行流程。   若是没有选中任何顺序流,会抛出一个异常。        

图形标记

  排他网关显示成一个普通网关(好比,菱形图形),   内部是一个“X”图标,表示异或(XOR)语义。   注意,没有内部图标的网关,默认为排他网关。    BPMN 2.0规范不容许在同一个流程定义中同时使用没有X和有X的菱形图形。          

 

XML内容

  排他网关的XML内容是很直接的:用一行定义了网关,   条件表达式定义在外出顺序流中。   参考条件顺序流   得到这些表达式的可用配置。       

      参考下面模型实例:          

          它对应的XML内容以下:          

<exclusiveGatewayid="exclusiveGw"name="Exclusive Gateway"/>
<sequenceFlowid="flow2"sourceRef="exclusiveGw"targetRef="theTask1">   <conditionExpressionxsi:type="tFormalExpression">${input == 1}</conditionExpression></sequenceFlow>
<sequenceFlowid="flow3"sourceRef="exclusiveGw"targetRef="theTask2">   <conditionExpressionxsi:type="tFormalExpression">${input == 2}</conditionExpression></sequenceFlow>
<sequenceFlowid="flow4"sourceRef="exclusiveGw"targetRef="theTask3">   <conditionExpressionxsi:type="tFormalExpression">${input == 3}</conditionExpression></sequenceFlow>

 

并行网关

描述

  网关也能够表示流程中的并行状况。最简单的并行网关是    并行网关,它容许将流程    成多条分支,也能够把多条分支    汇聚到一块儿。           of execution.        

并行网关的功能是基于进入和外出的顺序流的:          

  • 分支: 并行后的全部外出顺序流,为每一个顺序流都建立一个并发分支。              

  • 汇聚: 全部到达并行网关,在此等待的进入分支, 直到全部进入顺序流的分支都到达之后, 流程就会经过汇聚网关。              

  注意,若是同一个并行网关有多个进入和多个外出顺序流,   它就同时具备分支和汇聚功能。   这时,网关会先汇聚全部进入的顺序流,而后再切分红多个并行分支。        

与其余网关的主要区别是,并行网关不会解析条件。   即便顺序流中定义了条件,也会被忽略。        

图形标记

  并行网关显示成一个普通网关(菱形)内部是一个“加号”图标,   表示“与(AND)”语义。          

 

XML内容

  定义并行网关只须要一行XML:          

<parallelGatewayid="myParallelGateway"/>

实际发生的行为(分支,聚合,同时分支聚合),   要根据并行网关的顺序流来决定。        

  参考以下代码:

    <startEventid="theStart"/>     <sequenceFlowid="flow1"sourceRef="theStart"targetRef="fork"/>
   
<parallelGatewayid="fork"/>     <sequenceFlowsourceRef="fork"targetRef="receivePayment"/>     <sequenceFlowsourceRef="fork"targetRef="shipOrder"/>
   
<userTaskid="receivePayment"name="Receive Payment"/>     <sequenceFlowsourceRef="receivePayment"targetRef="join"/>
   
<userTaskid="shipOrder"name="Ship Order"/>     <sequenceFlowsourceRef="shipOrder"targetRef="join"/>
   
<parallelGatewayid="join"/>     <sequenceFlowsourceRef="join"targetRef="archiveOrder"/>
   
<userTaskid="archiveOrder"name="Archive Order"/>     <sequenceFlowsourceRef="archiveOrder"targetRef="theEnd"/>
   
<endEventid="theEnd"/>

 

上面例子中,流程启动以后,会建立两个任务:          

ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");TaskQuery query = taskService.createTaskQuery()                          .processInstanceId(pi.getId())                          .orderByTaskName()                          .asc();
List<Task> tasks = query.list(); assertEquals(2, tasks.size());
Task task1 = tasks.get(0); assertEquals("Receive Payment", task1.getName());Task task2 = tasks.get(1); assertEquals("Ship Order", task2.getName());

当两个任务都完成时,第二个并行网关会汇聚两个分支,由于它只有一条外出连线,   不会建立并行分支,   只会建立归档订单任务。        

  注意并行网关不须要是“平衡的”(好比,   对应并行网关的进入和外出节点数目相等)。   并行网关只是等待全部进入顺序流,并为每一个外出顺序流建立并发分支,   不会受到其余流程节点的影响。   因此下面的流程在BPMN 2.0中是合法的:          

 

包含网关

描述

包含网关能够看作是排他网关和并行网关的结合体。   和排他网关同样,你能够在外出顺序流上定义条件,包含网关会解析它们。   可是主要的区别是包含网关能够选择多于一条顺序流,这和并行网关同样。        

  包含网关的功能是基于进入和外出顺序流的:          

  • 分支: 全部外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每一个顺序流建立一个分支。              

  • 汇聚: 全部并行分支到达包含网关,会进入等待章台, 直到每一个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不一样。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚以后,流程会穿过包含网关继续执行。              

  注意,若是同一个包含节点拥有多个进入和外出顺序流,   它就会同时含有分支和汇聚功能。   这时,网关会先汇聚全部拥有流程token的进入顺序流,   再根据条件判断结果为true的外出顺序流,为它们生成多条并行分支。        

图形标记

  并行网关显示为一个普通网关(菱形),内部包含一个圆圈图标。          

 

XML内容

  定义一个包含网关须要一行XML:          

<inclusiveGatewayid="myInclusiveGateway"/>

实际的行为(分支,汇聚或同时分支汇聚),   是由链接在包含网关的顺序流决定的。        

  参考以下代码:

    <startEventid="theStart"/>     <sequenceFlowid="flow1"sourceRef="theStart"targetRef="fork"/>
   
<inclusiveGatewayid="fork"/>     <sequenceFlowsourceRef="fork"targetRef="receivePayment">     <conditionExpressionxsi:type="tFormalExpression">${paymentReceived == false}</conditionExpression>     </sequenceFlow>     <sequenceFlowsourceRef="fork"targetRef="shipOrder">     <conditionExpressionxsi:type="tFormalExpression">${shipOrder == true}</conditionExpression>     </sequenceFlow>
   
<userTaskid="receivePayment"name="Receive Payment"/>     <sequenceFlowsourceRef="receivePayment"targetRef="join"/>
   
<userTaskid="shipOrder"name="Ship Order"/>     <sequenceFlowsourceRef="shipOrder"targetRef="join"/>
   
<inclusiveGatewayid="join"/>     <sequenceFlowsourceRef="join"targetRef="archiveOrder"/>
   
<userTaskid="archiveOrder"name="Archive Order"/>     <sequenceFlowsourceRef="archiveOrder"targetRef="theEnd"/>
   
<endEventid="theEnd"/>

 

在上面的例子中,流程开始以后,若是流程变量为paymentReceived == false和shipOrder == true,   就会建立两个任务。若是,只有一个流程变量为true,就会只建立一个任务。   若是没有条件为true,就会抛出一个异常。   若是想避免异常,能够定义一个默认顺序流。下面的例子中,会建立一个任务,发货任务:          

HashMap<String,Object> variableMap =newHashMap<String,Object>();           variableMap.put("receivedPayment",true);           variableMap.put("shipOrder",true);           ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");TaskQuery query = taskService.createTaskQuery()                          .processInstanceId(pi.getId())                          .orderByTaskName()                          .asc();
List<Task> tasks = query.list(); assertEquals(1, tasks.size());
Task task = tasks.get(0); assertEquals("Ship Order", task.getName());

当任务完成后,第二个包含网关会汇聚两个分支,   由于只有一个外出顺序流,因此不会建立并行分支,   只有归档订单任务会被激活。        

  注意,包含网关不须要“平衡”(好比,   对应包含网关的进入和外出数目须要相等)。   包含网关会等待全部进入顺序流完成,   并为每一个外出顺序流建立并行分支,   不会受到流程中其余元素的影响。        

基于事件网关

描述

基于事件网关容许根据事件判断流向。网关的每一个外出顺序流都要链接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。 与此同时,会为每一个外出顺序流建立相对的事件订阅。        

注意基于事件网关的外出顺序流和普通顺序流不一样。这些顺序流不会真的"执行"。 相反,它们让流程引擎去决定执行到基于事件网关的流程须要订阅哪些事件。 要考虑如下条件:       

  • 基于事件网关必须有两条或以上外出顺序流。       

  • 基于事件网关后,只能使用intermediateCatchEvent类型。 (activiti不支持基于事件网关后链接ReceiveTask。)       

  • 链接到基于事件网关的intermediateCatchEvent只能有一条进入顺序流。       

 

图形标记

  基于事件网关和其余BPMN网关同样显示成一个菱形,   内部包含指定图标。          

 

XML内容

  用来定义基于事件网关的XML元素是eventBasedGateway。        

实例

下面的流程是一个使用基于事件网关的例子。当流程执行到基于事件网关时, 流程会暂停执行。与此同时,流程实例会订阅警告信号事件,并建立一个10分钟后触发的定时器。 这会产生流程引擎为一个信号事件等待10分钟的效果。若是10分钟内发出信号,定时器就会取消,流程会沿着信号执行。 若是信号没有出现,流程会沿着定时器的方向前进,信号订阅会被取消。      

 

<definitionsid="definitions"         xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"         xmlns:activiti="http://activiti.org/bpmn"         targetNamespace="Examples">
       
<signalid="alertSignal"name="alert"/>
       
<processid="catchSignal">
               
<startEventid="start"/>
               
<sequenceFlowsourceRef="start"targetRef="gw1"/>
               
<eventBasedGatewayid="gw1"/>
               
<sequenceFlowsourceRef="gw1"targetRef="signalEvent"/>                 <sequenceFlowsourceRef="gw1"targetRef="timerEvent"/>
               
<intermediateCatchEventid="signalEvent"name="Alert">                         <signalEventDefinitionsignalRef="alertSignal"/>                 </intermediateCatchEvent>
               
<intermediateCatchEventid="timerEvent"name="Alert">                         <timerEventDefinition>                                 <timeDuration>PT10M</timeDuration>                         </timerEventDefinition>                 </intermediateCatchEvent>
               
<sequenceFlowsourceRef="timerEvent"targetRef="exGw1"/>                 <sequenceFlowsourceRef="signalEvent"targetRef="task"/>
               
<userTaskid="task"name="Handle alert"/>
               
<exclusiveGatewayid="exGw1"/>
               
<sequenceFlowsourceRef="task"targetRef="exGw1"/>                 <sequenceFlowsourceRef="exGw1"targetRef="end"/>
               
<endEventid="end"/></process></definitions>

 

任务

用户任务

描述

   用户任务用来设置必须由人员完成的工做。    当流程执行到用户任务,会建立一个新任务,    并把这个新任务加入到分配人或群组的任务列表中。  

图形标记

   用户任务显示成一个普通任务(圆角矩形),左上角有一个小用户图标。    

 

XML内容

XML中的用户任务定义以下。id属性是必须的。    name属性是可选的。   

<userTaskid="theTask"name="Important task"/>                                   

 

用户任务也能够设置描述。实际上全部BPMN 2.0元素均可以设置描述。 添加documentation元素能够定义描述。   

<userTaskid="theTask"name="Schedule meeting">   <documentation>           Schedule an engineering meeting for next week with the new hire.   </documentation>

描述文本能够经过标准的java方法来得到:   

task.getDescription()

 

持续时间

    任务能够用一个字段来描述任务的持续时间。能够使用查询API来对持续时间进行搜索, 根据在时间以前或以后进行搜索。              

    咱们提供了一个节点扩展,在任务定义中设置一个表达式, 这样在任务建立时就能够为它设置初始持续时间。表达式应该是java.util.Datejava.util.String (ISO8601格式),ISO8601 持续时间 (好比PT50M)或null。 例如:你能够在流程中使用上述格式输入日期,或在前一个服务任务中计算一个时间。 这里使用了持续时间,持续时间会基于当前时间进行计算,再经过给定的时间段累加。 好比,使用"PT30M"做为持续时间,任务就会从如今开始持续30分钟。              

<userTaskid="theTask"name="Important task"activiti:dueDate="${dateVariable}"/>

任务的持续时间也能够经过TaskService修改,   或在TaskListener中经过传入的DelegateTask参数修改。            

用户分配

  用户任务能够直接分配给一个用户。   这能够经过humanPerformer元素定义。    humanPerformer定义须要一个    resourceAssignmentExpression来实际定义用户。   当前,只支持formalExpressions。   

<process ... >
  ...
 
<userTaskid='theTask'name='important task'>     <humanPerformer>       <resourceAssignmentExpression>         <formalExpression>kermit</formalExpression>       </resourceAssignmentExpression>     </humanPerformer>   </userTask>

 

只有一个用户能够坐拥任务的执行者分配给用户。   在activiti中,用户叫作执行者。   拥有执行者的用户不会出如今其余人的任务列表中,   只能出现执行者的我的任务列表中。

  直接分配给用户的任务能够经过TaskService像下面这样获取:   

List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();

 

任务也能够加入到人员的候选任务列表中。   这时,须要使用potentialOwner元素。   用法和humanPerformer元素相似。注意它须要指定表达式中的每一个项目是人员仍是群组   (引擎猜不出来)。          

<process ... >
  ...
 
<userTaskid='theTask'name='important task'>     <potentialOwner>       <resourceAssignmentExpression>         <formalExpression>user(kermit), group(management)</formalExpression>       </resourceAssignmentExpression>     </potentialOwner>   </userTask>

 

使用potentialOwner元素定义的任务,能够像下面这样获取   (使用TaskQuery的发那个发与查询设置了执行者的任务相似):          

 List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");

这会获取全部kermit为候选人的任务,   例如:表达式中包含user(kermit)。   这也会得到全部分配包含kermit这个成员的群组   (好比,group(management),前提是kermit是这个组的成员,   而且使用了activiti的帐号组件)。   用户所在的群组是在运行阶段获取的,它们能够经过    IdentityService进行管理。        

  若是没有显示指定设置的是用户仍是群组,   引擎会默认当作群组处理。因此下面的设置与使用group(accountancy)效果同样。          

<formalExpression>accountancy</formalExpression>

 

Activiti对任务分配的扩展

    当分配不复杂时,用户和组的设置很是麻烦。 为避免复杂性,能够使用用户任务的自定义扩展。          

 

  • assignee属性:这个自定义扩展能够直接把用户任务分配给指定用户。                  

    <userTaskid="theTask"name="my task"activiti:assignee="kermit"/>

    它和使用上面定义的humanPerformer    效果彻底同样。                

  • candidateUsers属性:这个自定义扩展能够为任务设置候选人。                  

    <userTaskid="theTask"name="my task"activiti:candidateUsers="kermit, gonzo"/>

    它和使用上面定义的potentialOwner    效果彻底同样。   注意它不须要像使用potentialOwner经过user(kermit)声明,   由于这个属性只能用于人员。                

  • candidateGroups属性:这个自定义扩展能够为任务设置候选组。                  

    <userTaskid="theTask"name="my task"activiti:candidateGroups="management, accountancy"/>

    它和使用上面定义的potentialOwner    效果彻底同样。   注意它不须要像使用potentialOwner经过group(management)声明,   由于这个属性只能用于群组。                

  • candidateUserscandidateGroups 能够同时设置在同一个用户任务中。                

 

    注意:虽然activiti提供了一个帐号管理组件, 也提供了IdentityService, 可是帐号组件不会检测设置的用户是否村爱。 它嵌入到应用中,也容许activiti与其余已存的帐户管理方案集成。          

    若是上面的方式还不知足需求,还能够使用建立事件的任务监听器 来实现自定义的分配逻辑:            

<userTaskid="task1"name="My task">   <extensionElements>     <activiti:taskListenerevent="create"class="org.activiti.MyAssignmentHandler"/>   </extensionElements></userTask>

DelegateTask会传递给TaskListener的实现, 经过它能够设置执行人,候选人和候选组:            

publicclassMyAssignmentHandlerimplementsTaskListener{
 
publicvoid notify(DelegateTask delegateTask){     // Execute custom identity lookups here
   
// and then for example call following methods:     delegateTask.setAssignee("kermit");     delegateTask.addCandidateUser("fozzie");     delegateTask.addCandidateGroup("management");     ...   }
}

 

使用spring时,能够使用向上面章节中介绍的自定义分配属性, 使用表达式任务监听器设置为spring代理的bean, 让这个监听器监放任务的建立事件。 下面的例子中,执行者会经过调用ldapService这个spring bean的findManagerOfEmployee方法得到。 流程变量emp会做为参数传递给bean。            

<userTaskid="task"name="My Task"activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>

也能够用来设置候选人和候选组:            

<userTaskid="task"name="My Task"activiti:candidateUsers="${ldapService.findAllSales()}"/>

注意方法返回类型只能为StringCollection<String> (对应候选人和候选组):            

publicclassFakeLdapService{
 
publicString findManagerForEmployee(String employee){     return"Kermit The Frog";   }
 
publicList<String> findAllSales(){     returnArrays.asList("kermit","gonzo","fozzie");   }
}

 

脚本任务

描述

  脚本任务时一个自动节点。当流程到达脚本任务,   会执行对应的脚本。     

图形标记

  脚本任务显示为标准BPMN 2.0任务(圆角矩形),   左上角有一个脚本小图标。       

 

XML内容

  脚本任务定义须要指定script        和scriptFormat

<scriptTaskid="theScriptTask"name="Execute script"scriptFormat="groovy">   <script>     sum =0     for( i in inputArray ){       sum += i     }   </script></scriptTask>

 

scriptFormat的值必须兼容    JSR-223。   (java平台的脚本语言)。默认Javascript会包含在JDK中,不须要额外的依赖。   若是你想使用其余(JSR-223兼容)的脚本引擎,   须要把对应的jar添加到classpath下,并使用合适的名称。   好比,activiti单元测试常用groovy,   由于语法比java简单太多。     

  注意,groovy脚本引擎放在groovy-all.jar中。在2.0版本以前,   脚本引擎是groovy jar的一部分。这样,须要添加以下依赖:       

<dependency>       <groupId>org.codehaus.groovy</groupId>       <artifactId>groovy-all</artifactId>       <version>2.x.x<version></dependency>

 

脚本中的变量

  到达脚本任务的流程能够访问的全部流程变量,均可以在脚本中使用。   实例中,脚本变量'inputArray'实际上是流程变量   (整数数组)。

<script>     sum =0     for( i in inputArray){       sum += i     }</script>

 

也能够在脚本中设置流程变量,直接调用        execution.setVariable("variableName", variableValue)。   默认,不会自动保存变量(注意:activiti 5.12以前存在这个问题)。   能够在脚本中自动保存任何变量。   (好比上例中的sum),只要把scriptTask    的autoStoreVariables属性设置为true。   然而,最佳实践是不要用它,而是显示调用execution.setVariable(),   由于一些当前版本的JDK对于一些脚本语言,没法实现自动保存变量。       参考这里得到更多信息。

 

<scriptTaskid="script"scriptFormat="JavaScript"activiti:autoStoreVariables="false">

参数默认为false,意思是若是没有为脚本任务定义设置参数,   全部声明的变量将只存在于脚本执行的阶段。     

  如何在脚本中设置变量的例子:

<script>     def scriptVar ="test123"     execution.setVariable("myVar", scriptVar)</script>

 

注意:下面这些命名已被占用,不能用做变量名:    out, out:print, lang:import, context, elcontext。     

脚本结果

  脚本任务的返回值能够经过制定流程变量的名称,分配给已存或一个新流程变量,   使用脚本任务定义的'activiti:resultVariable'属性。   任何已存的流程变量都会被脚本执行的结果覆盖。   若是没有指定返回变量名,脚本的返回值会被忽略。

<scriptTaskid="theScriptTask"name="Execute script"scriptFormat="juel"activiti:resultVariable="myVar">   <script>#{echo}</script></scriptTask>

上例中,脚本的结果(表达式'#{echo}'的值)   在脚本完成后,会设置到'myVar'变量中。     

Java服务任务

描述

java服务任务用来调用外部java类。      

图形标记

    服务任务显示为圆角矩形,左上角有一个齿轮小图标。        

 

XML内容

     有4钟方法来声明java调用逻辑:       

  • 实现JavaDelegate或ActivityBehavior

  • 执行解析代理对象的表达式

  • 调用一个方法表达式

  • 调用一直值表达式

     执行一个在流程执行中调用的类, 须要在'activiti:class'属性中设置全类名。

<serviceTaskid="javaService"              name="My Java Service Task"              activiti:class="org.activiti.MyJavaDelegate"/>

参考实现章节 了解更多使用类的信息。       

     也能够使用表达式调用一个对象。对象必须遵循一些规则, 并使用activiti:class属性进行建立。 (了解更多)。         

 <serviceTaskid="serviceTask"activiti:delegateExpression="${delegateExpressionBean}"/>

这里,delegateExpressionBean是一个实现了JavaDelegate接口的bean, 它定义在实例的spring容器中。       

     要指定执行的UEL方法表达式, 须要使用activiti:expression

<serviceTaskid="javaService"              name="My Java Service Task"              activiti:expression="#{printer.printMessage()}"/>

方法printMessage(无参数)会调用 名为printer对象的方法。       

     也能够为表达式中的方法传递参数。      

<serviceTaskid="javaService"              name="My Java Service Task"              activiti:expression="#{printer.printMessage(execution, myVar)}"/>

这会调用名为printer对象上的方法printMessage。 第一个参数是DelegateExecution,在表达式环境中默认名称为execution。 第二个参数传递的是当前流程的名为myVar的变量。       

     要指定执行的UEL值表达式, 须要使用activiti:expression属性。

<serviceTaskid="javaService"              name="My Java Service Task"              activiti:expression="#{split.ready}"/>

ready属性的getter方法,getReady(无参数), 会做用于名为split的bean上。 这个对象会被解析为流程对象和 (若是合适)spring环境中的对象。       

实现

    要在流程执行中实现一个调用的类,这个类须要实现org.activiti.engine.delegate.JavaDelegate接口, 并在execute方法中提供对应的业务逻辑。 当流程执行到特定阶段,它会指定方法中定义好的业务逻辑, 并按照默认BPMN 2.0中的方式离开节点。      

    让咱们建立一个java类的例子,它能够流程变量中字符串转换为大写。 这个类须要实现org.activiti.engine.delegate.JavaDelegate接口, 这要求咱们实现execute(DelegateExecution)方法。 它包含的业务逻辑会被引擎调用。流程实例信息,如流程变量和其余信息, 能够经过 DelegateExecution 接口访问和操做(点击对应操做的javadoc的连接,得到更多信息)。

publicclassToUppercaseimplementsJavaDelegate{
 
publicvoid execute(DelegateExecution execution)throwsException{     Stringvar=(String) execution.getVariable("input");     var=var.toUpperCase();     execution.setVariable("input",var);   }
}

 

注意:serviceTask定义的class只会建立一个java类的实例。 全部流程实例都会共享相同的类实例,并调用execute(DelegateExecution)。 这意味着,类不能使用任何成员变量,必须是线程安全的,它必须能模拟在不一样线程中执行。 这也影响着属性注入的处理方式。      

    流程定义中引用的类(好比,使用activiti:class不会        在部署时实例化。只有当流程第一次执行到使用类的时候, 类的实例才会被建立。若是找不到类,会抛出一个ActivitiException。 这个缘由是部署环境(更确切是的classpath)和真实环境每每是不一样的。 好比当使用ant或业务归档上传到Activiti Explorer来发布流程 classpath没有包含引用的类。      

[内部:非公共实现类]      也能够提供实现         org.activiti.engine.impl.pvm.delegate.ActivityBehavior接口的类。 实现能够访问更强大的ActivityExecution, 它能够影响流程的流向。注意,这不是一个很好的实践, 应该尽可能避免。因此,建议只有在高级状况下而且你确切知道你要作什么的状况下, 再使用ActivityBehavior接口。      

属性注入

  能够为代理类的属性注入数据。支持以下类型的注入:          

  • 固定的字符串

  • 表达式

 

  若是有效的话,数值会经过代理类的setter方法注入,遵循java bean的命名规范(好比fistName属性对应setFirstName(...)方法)。   若是属性没有对应的setter方法,数值会直接注入到私有属性中。   一些环境的SecurityManager不容许修改私有属性,因此最好仍是把你想注入的属性暴露出对应的setter方法来。    不管流程定义中的数据是什么类型,注入目标的属性类型都应该是    org.activiti.engine.delegate.Expression        

  下面代码演示了如何把一个常量注入到属性中。   属性注入能够使用'class'属性。   注意咱们须要定义一个'extensionElements' XML元素,   在声明实际的属性注入以前,这是BPMN 2.0 XML格式要求的。          

<serviceTaskid="javaService"     name="Java service invocation"     activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">     <extensionElements>       <activiti:fieldname="text"stringValue="Hello World"/>   </extensionElements></serviceTask>           

ToUpperCaseFieldInjected类有一个text属性,   类型是org.activiti.engine.delegate.Expression。   调用text.getValue(execution)时,会返回定义的字符串Hello World。        

  也能够使用长文字(好比,内嵌的email),能够使用'activiti:string'子元素:          

<serviceTaskid="javaService"     name="Java service invocation"     activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">   <extensionElements>     <activiti:fieldname="text">         <activiti:string>           Hello World       </activiti:string>     </activiti:field>   </extensionElements></serviceTask>           

 

能够使用表达式,实如今运行期动态解析注入的值。这些表达式能够使用流程变量或spring定义的bean(若是使用了spring)。 像服务任务实现里说的那样,服务任务中的java类实例会在全部流程实例中共享。 为了动态注入属性的值,咱们能够在org.activiti.engine.delegate.Expression中使用值和方法表达式, 它会使用传递给execute方法的DelegateExecution参数进行解析。

<serviceTaskid="javaService"name="Java service invocation"   activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected">
 
<extensionElements>     <activiti:fieldname="text1">       <activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression>     </activiti:field>     <activiti:fieldname="text2">        <activiti:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</activiti:expression>     </activiti:field>   </ extensionElements> </ serviceTask>

 

下面的例子中,注入了表达式,并使用在传入的当前DelegateExecution解析它们。  完整代码能够参考org.activiti.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection

publicclassReverseStringsFieldInjectedimplementsJavaDelegate{
 
privateExpression text1;   privateExpression text2;
 
publicvoid execute(DelegateExecution execution){     String value1 =(String) text1.getValue(execution);     execution.setVariable("var1",newStringBuffer(value1).reverse().toString());
   
String value2 =(String) text2.getValue(execution);     execution.setVariable("var2",newStringBuffer(value2).reverse().toString());   }}

 

另外,你也能够把表达式设置成一个属性,而不是字元素,让XML更简单一些。

<activiti:fieldname="text1"expression="${genderBean.getGenderString(gender)}"/><activiti:fieldname="text1"expression="Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}"/>

 

由于java类实例会被重用,注入只会发生一次,当服务任务调用第一次的时候。 当你的代码中的属性改变了,值也不会从新注入, 因此你应该把它们看作是不变的,不用修改它们。                   

服务任务结果

   服务流程返回的结果(使用表达式的服务任务)能够分配给已经存在的或新的流程变量,    能够经过指定服务任务定义的'activiti:resultVariable'属性来实现。    指定的路程比那两的值会被服务流程的返回结果覆盖。    若是没有指定返回变量名,就会忽略返回结果。

<serviceTaskid="aMethodExpressionServiceTask"     activiti:expression="#{myService.doSomething()}"     activiti:resultVariable="myVar"/>

在上面的例子中,服务流程的返回值(在'myService'上调用'doSomething()'方法的返回值,     myService多是流程变量,也多是spring的bean),会设置到名为'myVar'的流程变量里,    在服务执行完成以后。         

处理异常

    执行自定义逻辑时,经常须要捕获对应的业务异常,在流程内部进行处理。 activiti提供了不一样的方式来处理这个问题。      

抛出BPMN Errors

能够在服务任务或脚本任务的代码里抛出BPMN error。 为了实现这个,要从JavaDelegate,脚本,表达式和代理表达式中抛出名为 BpmnError的特殊ActivitiExeption。 引擎会捕获这个异常,把它转发到对应的错误处理中。 好比,边界错误事件或错误事件子流程。            

publicclassThrowBpmnErrorDelegateimplementsJavaDelegate{
 
publicvoid execute(DelegateExecution execution)throwsException{     try{       executeBusinessLogic();     }catch(BusinessException e){       thrownewBpmnError("BusinessExceptionOccured");     }   }
}

构造参数是错误代码,会被用来决定 哪一个错误处理器会来响应这个错误。 参考边界错误事件 得到更多捕获BPMN error的信息。          

这个机制应该只用于业务失败, 它应该被流程定义中设置的边界错误事件或错误事件子流程处理。 技术上的错误应该使用其余异常类型,一般不会在流程里处理。          

异常顺序流

[内部,公开实现类]             另外一种选择是在一些异常发生时,让路程进入其余路径。 下面的代码演示了如何实现。            

<serviceTaskid="javaService"   name="Java service invocation"   activiti:class="org.activiti.ThrowsExceptionBehavior"></serviceTask>
<sequenceFlowid="no-exception"sourceRef="javaService"targetRef="theEnd"/><sequenceFlowid="exception"sourceRef="javaService"targetRef="fixException"/>

这里的服务任务有两个外出顺序流,分别叫exceptionno-exception。异常出现时会使用顺序流的id来决定流向:            

publicclassThrowsExceptionBehaviorimplementsActivityBehavior{
 
publicvoid execute(ActivityExecution execution)throwsException{     Stringvar=(String) execution.getVariable("var");
   
PvmTransition transition =null;     try{       executeLogic(var);       transition = execution.getActivity().findOutgoingTransition("no-exception");     }catch(Exception e){       transition = execution.getActivity().findOutgoingTransition("exception");     }     execution.take(transition);   }
}

 

在JavaDelegate里使用activiti服务

    一些场景下,须要在java服务任务中使用activiti服务 (好比,经过RuntimeService启动流程实例,而callActivity不知足你的需求)。 org.activiti.engine.delegate.DelegateExecution容许经过 org.activiti.engine.EngineServices接口直接得到这些服务:            

publicclassStartProcessInstanceTestDelegateimplementsJavaDelegate{
 
publicvoid execute(DelegateExecution execution)throwsException{     RuntimeService runtimeService = execution.getEngineServices().getRuntimeService();     runtimeService.startProcessInstanceByKey("myProcess");   }
}            

全部activiti服务的API均可以经过这个接口得到。            

使用这些API调用出现的全部数据改变,都是在当前事务中的。 在像spring和CDI这样的依赖注入环境也会起做用,不管是否启用了JTA数据源。 好比,下面的代码功能与上面的代码一致, 这是RuntimeService是经过依赖注入得到的,而不是经过org.activiti.engine.EngineServices接口。            

@Component("startProcessInstanceDelegate")publicclassStartProcessInstanceTestDelegateWithInjection{
   
@Autowired     privateRuntimeService runtimeService;
   
publicvoid startProcess(){       runtimeService.startProcessInstanceByKey("oneTaskProcess");     }
}            

 

重要技术提示:由于服务调用是在当前事务里, 数据的产生或改变,在服务任务执行完以前,尚未提交到数据库。 全部API对于数据库数据的操做,意味着未提交的操做在服务任务的API调用中都是不可见的。          

Web Service任务

[EXPERIMENTAL]

描述

Web Service任务能够用来同步调用一个外部的Web service。

图形标记

Web Service任务与Java服务任务显示效果同样。

 

XML内容

要使用Web Service咱们须要导入它的操做和类型。 能够自动使用import标签来指定Web Service的WSDL:       

<importimportType="http://schemas.xmlsoap.org/wsdl/"         location="http://localhost:63081/counter?wsdl"         namespace="http://webservice.activiti.org/"/>

上面的声明告诉activiti导入WSDL定义,但没有建立item定义和消息。 假设咱们想调用一个名为'prettyPrint'的方法, 咱们必须建立为请求和响应信息对应的消息和item定义:

<messageid="prettyPrintCountRequestMessage"itemRef="tns:prettyPrintCountRequestItem"/><messageid="prettyPrintCountResponseMessage"itemRef="tns:prettyPrintCountResponseItem"/>
<itemDefinitionid="prettyPrintCountRequestItem"structureRef="counter:prettyPrintCount"/><itemDefinitionid="prettyPrintCountResponseItem"structureRef="counter:prettyPrintCountResponse"/>

在申请服务任务以前,咱们必须定义实际引用Web Service的BPMN接口和操做。 基本上,咱们定义接口和必要的操做。对每一个奥作咱们都会重用上面定义的信息做为输入和输出。 好比,下面定义了'counter'接口和'prettyPrintCountOperation'操做:

<interfacename="Counter Interface"implementationRef="counter:Counter">         <operationid="prettyPrintCountOperation"name="prettyPrintCount Operation"                         implementationRef="counter:prettyPrintCount">                 <inMessageRef>tns:prettyPrintCountRequestMessage</inMessageRef>                 <outMessageRef>tns:prettyPrintCountResponseMessage</outMessageRef>         </operation></interface>

而后咱们能够定义Web Service任务使用##WebService实现, 并引用Web Service操做。

<serviceTaskid="webService"         name="Web service invocation"         implementation="##WebService"         operationRef="tns:prettyPrintCountOperation">

Web Service任务IO规范

除非咱们使用简化方式处理数据输入和输出关联(以下所示),每一个Web Service任务能够定义任务的输入输出IO规范。 配置方式与BPMN 2.0彻底兼容,下面格式化后的例子,咱们根据以前定义item定义,定义了输入和输出。

<ioSpecification>         <dataInputitemSubjectRef="tns:prettyPrintCountRequestItem"id="dataInputOfServiceTask"/>         <dataOutputitemSubjectRef="tns:prettyPrintCountResponseItem"id="dataOutputOfServiceTask"/>         <inputSet>                 <dataInputRefs>dataInputOfServiceTask</dataInputRefs>         </inputSet>         <outputSet>                 <dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>         </outputSet></ioSpecification>

Web Service任务数据输入关联

有两种方式指定数据输入关联:

  • 使用表达式

  • 使用简化方式

要使用表达式指定数据输入关联,咱们须要定义来源和目的item,并指定每一个item属性之间的对应关系。 下面的例子中咱们分配了这些item的前缀和后缀:       

<dataInputAssociation>         <sourceRef>dataInputOfProcess</sourceRef>         <targetRef>dataInputOfServiceTask</targetRef>         <assignment>                 <from>${dataInputOfProcess.prefix}</from>                 <to>${dataInputOfServiceTask.prefix}</to>         </assignment>         <assignment>                 <from>${dataInputOfProcess.suffix}</from>                 <to>${dataInputOfServiceTask.suffix}</to>         </assignment></dataInputAssociation>

另外,咱们能够使用更简单的简化方式。'sourceRef'元素是activiti的变量名, 'targetRef'元素是item定义的一个属性。在下面的例子中,咱们把'PrefixVariable'变量的值分配给'field'属性, 把'SuffixVariable'变量的值分配给'suffix'属性。       

<dataInputAssociation>         <sourceRef>PrefixVariable</sourceRef>         <targetRef>prefix</targetRef></dataInputAssociation><dataInputAssociation>         <sourceRef>SuffixVariable</sourceRef>         <targetRef>suffix</targetRef></dataInputAssociation>

Web Service任务数据输出关联

有两种方式指定数据输出关联:

  • 使用表达式

  • 使用简化方式

要使用表达式指定数据输出关联,咱们须要定义目的变量和来源表达式。 方法和数据输入关联彻底同样:       

<dataOutputAssociation>         <targetRef>dataOutputOfProcess</targetRef>         <transformation>${dataOutputOfServiceTask.prettyPrint}</transformation></dataOutputAssociation>

另外,咱们能够使用更简单的简化方式。'sourceRef'元素是item定义的一个属性, 'targetRef'元素是activiti的变量名。 方法和数据输入关联彻底同样:       

<dataOutputAssociation>         <sourceRef>prettyPrint</sourceRef>         <targetRef>OutputVariable</targetRef></dataOutputAssociation>

业务规则任务

[EXPERIMENTAL]

描述

业务规则用户用来同步执行一个或多个规则。activiti使用drools规则引擎执行业务规则。 目前,包含业务规则的.drl文件必须和流程定义一块儿发布,流程定义里包含了执行这些规则的业务规则任务。 意味着流程使用的全部.drl文件都必须打包在流程BAR文件里,好比任务表单。 更多使用Drools Expert建立业务规则的信息,请参考JBoss Drools的文档。

  若是想要使用你的规则任务的实现,好比,由于你想用不一样方式使用drools,或你想使用彻底不一样的规则引擎,   你能够使用BusinessRuleTask上的class或表达式属性,它用起来就和    ServiceTask同样。

图形标记

业务规则任务使用一个表格小图标进行显示。

 

XML内容

要执行部署流程定义的BAR文件中的一个或多个业务规则,咱们须要定义输入和输出变量。 对于输入变量定义,能够使用逗号分隔的一些流程变量。 输出变量定义智能包含一个变量名,,它会把执行业务规则后返回的对象保存到对应的流程变量中。 注意,结果变量会包含一个对象列表。若是没有指定输出变量名称,默认会使用 org.activiti.engine.rules.OUTPUT。   

下面的业务规则任务会执行和流程定义一块儿部署的素有业务规则:        

<processid="simpleBusinessRuleProcess">
 
<startEventid="theStart"/>   <sequenceFlowsourceRef="theStart"targetRef="businessRuleTask"/>
 
<businessRuleTaskid="businessRuleTask"activiti:ruleVariablesInput="${order}"       activiti:resultVariable="rulesOutput"/>
 
<sequenceFlowsourceRef="businessRuleTask"targetRef="theEnd"/>
 
<endEventid="theEnd"/>
</process>                          

 

业务规则任务也能够配置成只执行部署的.drl文件中的一些规则。 这时要设置逗号分隔的规则名。

<businessRuleTaskid="businessRuleTask"activiti:ruleVariablesInput="${order}"       activiti:rules="rule1, rule2"/>                           

这时,只会执行rule1和rule2。   

你也能够定义哪些规则不用执行。

<businessRuleTaskid="businessRuleTask"activiti:ruleVariablesInput="${order}"       activiti:rules="rule1, rule2"exclude="true"/>                           

这时除了rule1和rule2之外,全部部署到流程定义同一个BAR文件中的规则都会执行。

像以前提到的,能够用一个选项修改BusinessRuleTask的实现:

<businessRuleTaskid="businessRuleTask"activiti:class="${MyRuleServiceDelegate}"/>                           

注意BusinessRuleTask的功能和ServiceTask同样,可是咱们使用BusinessRuleTask的图标来表示   咱们在这里要执行业务规则。

邮件任务

activiti强化了业务流程,支持了自动邮件任务,它能够发送邮件给一个或多个参与者, 包括支持cc, bcc, HTML内容等等。 注意邮件任务不是BPMN 2.0规范定义的官方任务。 (它也没有对应的图标)。 所以,activiti中邮件任务是用专门的服务任务实现的。   

邮件服务器配置

activiti引擎要经过支持SMTP功能的外部邮件服务器发送邮件。   为了实际发送邮件,引擎穾知道如何访问邮件服务器。   下面的配置能够设置到activiti.cfg.xml配置文件中:       

Table 8.1. 邮件服务器配置

属性 是否必须 描述
mailServerHost 邮件服务器的主机名(好比:mail.mycorp.com)。默认为localhost
mailServerPort 是,若是没有使用默认端口 邮件服务器上的SMTP传输端口。默认为25
mailServerDefaultFrom 若是用户没有指定发送邮件的邮件地址,默认设置的发送者的邮件地址。默认为activiti@activiti.org
mailServerUsername 若是服务器须要 一些邮件服务器须要认证才能发送邮件。默认不设置。
mailServerPassword 若是服务器须要 一些邮件服务器须要认证才能发送邮件。默认不设置。
mailServerUseSSL 若是服务器须要 一些邮件服务器须要ssl交互。默认为false。
mailServerUseTLS 若是服务器须要 一些邮件服务器(好比gmail)须要支持TLS。默认为false。

 

定义一个邮件任务

  邮件任务是一个专用的服务任务,   这个服务任务的type设置为'mail'。       

<serviceTaskid="sendMail"activiti:type="mail">                       

 

邮件任务是经过属性注入进行配置的。   全部这些属性均可以使用EL表达式,能够在流程执行中解析。   下面的属性均可以设置:       

Table 8.2. 邮件服务器配置

属性 是否必须 描述
to 邮件的接受者。能够使用逗号分隔多个接受者
from 邮件发送者的地址。若是不提供,会使用默认配置的地址。
subject 邮件的主题
cc 邮件抄送人。能够使用逗号分隔多个接收者
bcc 邮件暗送人。能够使用逗号分隔多个接收者
charset 能够修改邮件的字符集,对不少非英语语言是必须设置的。                 
html 做为邮件内容的HTML。
text 邮件的内容,在须要使用原始文字(非富文本)的邮件时使用。 能够与html一块儿使用,对于不支持富客户端的邮件客户端。 客户端会降级到仅显示文本的方式。                 
htmlVar 使用对应的流程变量做为e-mail的内容。它和html的不一样之处是它内容中包含的表达式会在mail任务发送以前被替换掉。
textVar 使用对应的流程变量做为e-mail的纯文本内容。它和html的不一样之处是它内容中包含的表达式会在mail任务发送以前被替换掉。

 

使用实例

  下面的XML演示了使用邮件任务的例子。       

<serviceTaskid="sendMail"activiti:type="mail">   <extensionElements>     <activiti:fieldname="from"stringValue="order-shipping@thecompany.com"/>     <activiti:fieldname="to"expression="${recipient}"/>     <activiti:fieldname="subject"expression="Your order ${orderId} has been shipped"/>     <activiti:fieldname="html">       <activiti:expression>         <![CDATA[           <html>             <body>               Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},<br/><br/>
              As of ${now}, your order has been
<b>processed and shipped</b>.<br/><br/>
              Kind regards,
<br/>
              TheCompany.            
</body>           </html>         ]]>       </activiti:expression>     </activiti:field>   </extensionElements></serviceTask>                      

结果以下:       

 

Mule任务

mule任务能够向mule发送消息,以强化activiti的集成能力。 注意mule任务不是BPMN 2.0规范定义的官方任务。 (它也没有对应的图标)。 所以,activiti中mule任务是用专门的服务任务实现的。   

定义一个mule任务

mule任务是一个专用的服务任务,   这个服务任务的type设置为'mule'。       

<serviceTaskid="sendMule"activiti:type="mule">                       

 

  mule任务是经过属性注入进行配置的。   全部这些属性均可以使用EL表达式,能够在流程执行中解析。   下面的属性均可以设置:       

Table 8.3. Mule服务器配置

属性 是否必须 描述
endpointUrl 但愿调用的Mule终端
language 你要使用解析荷载表达式(payloadExpression)属性的语言。
payloadExpression 做为消息荷载的表达式。
resultVariable 将要保存调用结果的变量名称。

 

应用实例

  下面是一个使用mule任务的例子。       

  <extensionElements>     <activiti:fieldname="endpointUrl">       <activiti:string>vm://in</activiti:string>     </activiti:field>     <activiti:fieldname="language">       <activiti:string>juel</activiti:string>     </activiti:field>     <activiti:fieldname="payloadExpression">       <activiti:string>"hi"</activiti:string>     </activiti:field>     <activiti:fieldname="resultVariable">       <activiti:string>theVariable</activiti:string>     </activiti:field>   </extensionElements>                       

 

Camel任务

Camel任务能够从Camel发送和介绍消息,由此强化了activiti的集成功能。 注意camel任务不是BPMN 2.0规范定义的官方任务。 (它也没有对应的图标)。 在activiti中,camel任务时由专用的服务任务实现的。 要使用camel任务功能时,也要记得吧activiti camel包含到项目里。      

定义camel任务

camel任务是一个专用的服务任务,   这个服务任务的type设置为'camel'。          

<serviceTaskid="sendCamel"activiti:type="camel">           

 

流程定义只须要在服务任务中定义camel类型。   集成逻辑都会代理给camel容器。默认activiti引擎会在spring容器中查找camelContext bean。    camelContext定义了camel容器加载的路由规则。下面的例子中路由规则是从指定的java包下加载的。   可是你也能够经过spring配置直接定义路由规则。         

<camelContextid="camelContext"xmlns="http://camel.apache.org/schema/spring">   <packageScan>     <package>org.activiti.camel.route</package>   </packageScan></camelContext>

 

若是想了解更多关于camel路由的信息,能够访问Camel的网站。   在这里只经过很小的例子演示了基础的概念。   在第一个例子中,咱们会经过activiti工做流实现最简单的Camel调用。咱们称其为SimpleCamelCall。

若是想定义多个Camel环境bean,而且(或者)想使用不一样的bean名称,能够重载CamelTask的定义,以下所示:

<serviceTaskid="serviceTask1"activiti:type="camel">         <extensionElements>                 <activiti:fieldname="camelContext"stringValue="customCamelContext"/>         </extensionElements></serviceTask>                 

 

简单Camel调用

这个例子对应的文件均可以在activiti camel模块的org.activiti.camel.examples.simpleCamelCall包下找到。咱们的目标是简单激活一个特定的camel路由。 首先,咱们须要一个Spring环境,它要包含以前介绍的路由。这些文件的目的以下:

<camelContextid="camelContext"xmlns="http://camel.apache.org/schema/spring">         <packageScan>                 <package>org.activiti.camel.examples.simpleCamelCall</package>         </packageScan></camelContext>          
包含名为SimpleCamelCallRoute的路由的类文件,放在PackageScan标签的扫描目录下。    下面就是路由的定义:         
publicclassSimpleCamelCallRouteextendsRouteBuilder{
 
@Override   publicvoid configure()throwsException{
         
from("activiti:SimpleCamelCallProcess:simpleCall").to("log: org.activiti.camel.examples.SimpleCamelCall");   }}
这个规则仅仅打印消息体,不会作其余事情。注意终端的格式。它包含三部分:

Table 8.4. 终端URL:

部分 说明
终端URL 引用activiti终端
SimpleCamelCallProcess 流程名
simpleCall 流程中的Camel服务

OK,咱们的规则已经配置好,也可让Camel使用了。 如今看工做流部分。工做流看起来像这样:          
<processid="SimpleCamelCallProcess">         <startEventid="start"/>         <sequenceFlowid="flow1"sourceRef="start"targetRef="simpleCall"/>                          <serviceTaskid="simpleCall"activiti:type="camel"/>                          <sequenceFlowid="flow2"sourceRef="simpleCall"targetRef="end"/>         <endEventid="end"/></process>           
在serviceTask部分,它只注明服务的类型是Camel,目标规则名为simpleCall。这与上面的activiti终端相匹配。初始化流程后,咱们会看到一个空的日志。 好,咱们已经完成了这个最简单的例子了。      

乒乓实例

咱们的例子成功执行了,可是Camel和Activiti之间没有任何交互,并且这样作也没有任何优点。在这个例子里,咱们尝试向Camel发送和接收数据。 咱们发送一个字符串,camel进行一些处理,而后返回结果。 发送部分很简单,咱们把变量里的消息发送给camel。这里是咱们的调用代码:

@Deploymentpublicvoid testPingPong(){   Map<String,Object> variables =newHashMap<String,Object>();
  variables
.put("input","Hello");   Map<String,String> outputMap =newHashMap<String,String>();   variables.put("outputMap", outputMap);
  runtimeService
.startProcessInstanceByKey("PingPongProcess", variables);   assertEquals(1, outputMap.size());   assertNotNull(outputMap.get("outputValue"));   assertEquals("Hello World", outputMap.get("outputValue"));}        

变量"input"是Camel规则的实际输入,outputMap会记录camel返回的结果。流程应该像是这样:

<processid="PingPongProcess">   <startEventid="start"/>   <sequenceFlowid="flow1"sourceRef="start"targetRef="ping"/>   <serviceTaskid="ping"activiti:type="camel"/>   <sequenceFlowid="flow2"sourceRef="ping"targetRef="saveOutput"/>   <serviceTaskid="saveOutput"  activiti:class="org.activiti.camel.examples.pingPong.SaveOutput"/>   <sequenceFlowid="flow3"sourceRef="saveOutput"targetRef="end"/>   <endEventid="end"/></process>     

注意,SaveOuput这个serviceTask,会把"Output"变量的值从上下文保存到上面提到的OutputMap中。 如今,咱们必须了解变量是如何发送给Camel,再返回的。这里就要涉及到camel实际执行的行为了。 变量提交给camel的方法是由CamelBehavior控制的。这里咱们使用默认的配置,其余的会在后面说起。 使用这些代码,咱们就能够配置一个指望的camel行为:

<serviceTaskid="serviceTask1"activiti:type="camel">   <extensionElements>     <activiti:fieldname="camelBehaviorClass"stringValue="org.activiti.camel.impl.CamelBehaviorCamelBodyImpl"/>   </extensionElements></serviceTask>         

若是你没有特别指定一个行为,就会使用org.activiti.camel.impl.CamelBehaviorDefaultImpl。 这个行为会把变量复制成名称相同的Camel属性。 在返回时,不管选择什么行为,若是camel消息体是一个map,每一个元素都会复制成一个变量, 不然整个对象会复制到指定名称为"camelBody"的变量中。 了解这些后,就能够看看咱们第二个例子的camel规则了:

@Overridepublicvoid configure()throwsException{   from("activiti:PingPongProcess:ping").transform().simple("${property.input} World");}         

在这个规则中,字符串"world"会被添加到"input"属性的后面,结果会写入消息体。 这时能够检查javaServiceTask中的"camelBody"变量,复制到"outputMap"中,并在testcase进行判断。 如今这个例子是在默认的行为下运行的,而后咱们看一块儿其余的方案。 在启动的全部camel规则中,流程实例id会复制到camel的名为"PROCESS_ID_PROPERTY"的属性中。 后续能够用它关联流程实例和camel规则。他也能够在camel规则中直接使用。

Activiti中能够使用三种不一样的行为。这些行为能够经过在规则URL中指定对应的环节来实现覆盖。 这里有一个在URL中覆盖现存行为的例子:

from("activiti:asyncCamelProcess:serviceTaskAsync2?copyVariablesToProperties=true").                 

下面的表格提供了三种camel行为的概述:

Table 8.5. 已有的camel行为:

行为 URL 描述
CamelBehaviorDefaultImpl copyVariablesToProperties 把Activiti变量复制为Camel属性
CamelBehaviorCamelBodyImpl copyCamelBodyToBody 只把名为"camelBody"Activiti变量复制成camel的消息体
CamelBehaviorBodyAsMapImpl copyVariablesToBodyAsMap 把activiti的全部变量复制到一个map里,做为Camel的消息体


上面的表格解释和activiti变量如何传递给camel。下面的表格解释和camel的变量如何返回给activiti。 它只能配置在规则URL中。

Table 8.6. 已有的camel行为:

Url 描述  
默认 若是Camel消息体是一个map,把每一个元素复制成activiti的变量,不然把整个camel消息体做为activiti的"camelBody"变量。  
copyVariablesFromProperties 将Camel属性以相同名称复制为Activiti变量  
copyCamelBodyToBodyAsString 和默认同样,可是若是camel消息体不是map时,先把它转换成字符串,再设置为"camelBody"。  
isCopyVariablesFromHeader 额外把camel头部以相同名称复制成Activiti变量  


例子的源码放在activiti-camel模块的org.activiti.camel.examples.pingPong包下。

异步乒乓实例

以前的例子都是同步的。流程会等到camel规则返回以后才会中止。 一些状况下,咱们须要activiti工做流继续运行。这时camelServiceTask的异步功能就特别有用。 你能够经过设置camelServiceTask的async属性来启用这个功能。

<serviceTaskid="serviceAsyncPing"activiti:type="camel"activiti:async="true"/>                 

经过设置这个功能,camel规则会被activiti的jobExecutor异步执行。 当你在camel规则中定义了一个队列,activiti流程会在camelServiceTask执行时继续运行。 camel规则会以彻底异步的方式执行。 若是你想在什么地方等待camelServiceTask的返回值,你能够使用一个receiveTask。

<receiveTaskid="receiveAsyncPing"name="Wait State"/>                 

 

流程实例会等到接收一个signal,好比来自camel。在camel中你能够发送一个signal给流程实例,经过对应的activiti终端发送消息。
 from("activiti:asyncPingProcess:serviceAsyncPing").to("activiti:asyncPingProcess:receiveAsyncPing");                 
对于一个经常使用的终端,会使用冒号分隔的三个部分:
  •    常量字符串"activiti"   

  • 流程名称   

  •     接收任务名   

从camel规则中实例化工做流

以前的全部例子中,activiti工做流会先启动,而后在流程中启动camel规则。 也能够使用另一种方法。在已经启动的camel规则中启动一个工做流。 这会触发一个receiveTask十分相似,除了最后的部分。这是一个实例规则:

from("direct:start").to("activiti:camelProcess");         

咱们看到url有两个部分,第一个部分是常量字符串"activiti",第二部分是流程的名称。 很明显,流程应该已经部署完成,而且是能够启动的。

手工任务

描述

手工任务定义了BPM引擎外部的任务。   用来表示工做须要某人完成,而引擎不须要知道,也没有对应的系统和UI接口。   对于引擎,手工任务是直接经过的活动,   流程到达它以后会自动向下执行。     

图形标记

  手工任务显示为一个圆角矩形,左上角是一个手型小图标。       

 

XML内容

 

<manualTaskid="myManualTask"name="Call client for more information"/>

 

Java接收任务

描述

  接收任务是一个简单任务,它会等待对应消息的到达。   当前,咱们只实现了这个任务的java语义。   当流程达到接收任务,流程状态会保存到存储里。   意味着流程会等待在这个等待状态,   直到引擎接收了一个特定的消息,   这会触发流程穿过接收任务继续执行。     

图形标记

  接收任务显示为一个任务(圆角矩形),右上角有一个消息小标记。   消息是白色的(黑色图标表示发送语义)          

 

XML内容

 

<receiveTaskid="waitState"name="wait"/>    

 

要在接收任务等待的流程实例继续执行,   能够调用runtimeService.signal(executionId),传递接收任务上流程的id。   下面的代码演示了实际是如何工做的:          

ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");Execution execution = runtimeService.createExecutionQuery()   .processInstanceId(pi.getId())   .activityId("waitState")   .singleResult(); assertNotNull(execution);
runtimeService.signal(execution.getId());          

 

Shell任务

描述

shell任务能够执行shell脚本和命令。 注意shell任务不是BPMN 2.0规范定义的官方任务。 (它也没有对应的图标)。

定义shell任务

shell任务是一个专用的服务任务, 这个服务任务的type设置为'shell'。       

<serviceTaskid="shellEcho"activiti:type="shell">                       

 

  shell任务使用属性注入进行配置。   全部属性均可以包含EL表达式,会在流程执行过程当中解析。   能够配置如下属性:       

Table 8.7. Shell任务参数配置

属性 是否必须 类型 描述 默认值
command String 执行的shell命令  
arg0-5 String 参数0至5  
wait true/false 是否须要等待到shell进程结束 true
redirectError true/false 把标准错误打印到标准流中 false
cleanEnv true/false shell进行不继承当前环境 false
outputVariable String 保存输出的变量名 不会记录输出结果
errorCodeVariable String 包含结果错误代码的变量名 不会注册错误级别
directory String shell进程的默认目录 当前目录

 

应用实例

  下面的代码演示了使用shell任务的实例。它会执行shell脚本"cmd /c echo EchoTest",等到它结束,再把输出结果保存到resultVar中。       

<serviceTaskid="shellEcho"activiti:type="shell">   <extensionElements>     <activiti:fieldname="command"stringValue="cmd"/>     <activiti:fieldname="arg1"stringValue="/c"/>     <activiti:fieldname="arg2"stringValue="echo"/>     <activiti:fieldname="arg3"stringValue="EchoTest"/>     <activiti:fieldname="wait"stringValue="true"/>     <activiti:fieldname="outputVariable"stringValue="resultVar"/>   </extensionElements></serviceTask>                       

 

执行监听器

兼容性提醒:在发布5.3后,咱们发现执行监听器,   任务监听器,表达式仍是非公开API。这些类在org.activiti.engine.impl...的子包,   包名中有一个impl。       org.activiti.engine.impl.pvm.delegate.ExecutionListener,       org.activiti.engine.impl.pvm.delegate.TaskListener and       org.activiti.engine.impl.pvm.el.Expression已经废弃了。   从如今开始,应该使用org.activiti.engine.delegate.ExecutionListener,       org.activiti.engine.delegate.TaskListenerorg.activiti.engine.delegate.Expression。   在新的公开API中,删除了ExecutionListenerExecution.getEventSource()。   由于已经设置了废弃编译警告,因此已存的代码应该能够正常运行。可是要考虑切换到新的公共API接口   (包名中没有.impl.)。      

执行监听器能够执行外部java代码或执行表达式,当流程定义中发生了某个事件。   能够捕获的事件有:      

  • 流程实例的启动和结束。

  • 选中一条连线。

  • 节点的开始和结束。

  • 网关的开始和结束。

  • 中间事件的开始和结束。

  • 开始时间结束或结束事件开始。

 

    下面的流程定义包含了3个流程监听器:        

  <processid="executionListenersProcess">
   
<extensionElements>       <activiti:executionListenerclass="org.activiti.examples.bpmn.executionlistener.ExampleExecutionListenerOne"event="start"/>     </extensionElements>
   
<startEventid="theStart"/>     <sequenceFlowsourceRef="theStart"targetRef="firstTask"/>
   
<userTaskid="firstTask"/>     <sequenceFlowsourceRef="firstTask"targetRef="secondTask">     <extensionElements>       <activiti:executionListenerclass="org.activiti.examples.bpmn.executionListener.ExampleExecutionListenerTwo"/>     </extensionElements>     </sequenceFlow>
   
<userTaskid="secondTask">     <extensionElements>       <activiti:executionListenerexpression="${myPojo.myMethod(execution.event)}"event="end"/>     </extensionElements>     </userTask>     <sequenceFlowsourceRef="secondTask"targetRef="thirdTask"/>
   
<userTaskid="thirdTask"/>     <sequenceFlowsourceRef="thirdTask"targetRef="theEnd"/>
   
<endEventid="theEnd"/>
 
</process>

 

第一个流程监听器监听流程开始。监听器是一个外部java类(像是ExampleExecutionListenerOne), 须要实现org.activiti.engine.delegate.ExecutionListener接口。 当事件发生时(这里是end事件), 会调用notify(ExecutionListenerExecution execution)方法。        

publicclassExampleExecutionListenerOneimplementsExecutionListener{
 
publicvoid notify(ExecutionListenerExecution execution)throwsException{     execution.setVariable("variableSetInExecutionListener","firstValue");     execution.setVariable("eventReceived", execution.getEventName());   }}

也能够使用实现org.activiti.engine.delegate.JavaDelegate接口的代理类。 代理类能够在结构中重用,好比serviceTask的代理。        

   第二个流程监听器在连线执行时调用。注意这个listener元素不能定义event,    由于连线只能触发take事件。     为连线定义的监听器的event属性会被忽略。        

  最后一个流程监听器在节点secondTask结束时调用。这里使用expression    代替class来在事件触发时执行/调用。        

<activiti:executionListenerexpression="${myPojo.myMethod(execution.eventName)}"event="end"/>

和其余表达式同样,流程变量能够处理和使用。由于流程实现对象有一个保存事件名称的属性,   能够在方法中使用execution.eventName获的事件名称。        

  流程监听器也支持使用delegateExpression,           和服务任务相同。          

<activiti:executionListenerevent="start"delegateExpression="${myExecutionListenerBean}"/>

 

在activiti 5.12中,咱们也介绍了新的流程监听器,org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener。   这个脚本流程监听器能够为某个流程监听事件执行一段脚本。

<activiti:executionListenerevent="start"class="org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener">   <activiti:fieldname="script">     <activiti:string>       def bar = "BAR";  // local variable       foo = "FOO"; // pushes variable to execution context       execution.setVariable("var1", "test"); // test access to execution instance       bar // implicit return value     </activiti:string>   </activiti:field>   <activiti:fieldname="language"stringValue="groovy"/>   <activiti:fieldname="resultVariable"stringValue="myVar"/><activiti:executionListener>

 

流程监听器的属性注入

使用流程监听器时,能够配置class属性,能够使用属性注入。 这和使用服务任务属性注入相同, 参考它能够得到属性注入的不少信息。        

下面的代码演示了使用了属性注入的流程监听器的流程的简单例子。        

 <processid="executionListenersProcess">     <extensionElements>       <activiti:executionListenerclass="org.activiti.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener"event="start">         <activiti:fieldname="fixedValue"stringValue="Yes, I am "/>         <activiti:fieldname="dynamicValue"expression="${myVar}"/>       </activiti:executionListener>     </extensionElements>
   
<startEventid="theStart"/>     <sequenceFlowsourceRef="theStart"targetRef="firstTask"/>
   
<userTaskid="firstTask"/>     <sequenceFlowsourceRef="firstTask"targetRef="theEnd"/>
   
<endEventid="theEnd"/>   </process>        

 

 

publicclassExampleFieldInjectedExecutionListenerimplementsExecutionListener{
 
privateExpression fixedValue;
 
privateExpression dynamicValue;

 
publicvoid notify(ExecutionListenerExecution execution)throwsException{     execution.setVariable("var",fixedValue.getValue(execution).toString()+dynamicValue.getValue(execution).toString());   }}          

ExampleFieldInjectedExecutionListener类串联了两个注入的属性。   (一个是固定的,一个是动态的),把他们保存到流程变量'var'中。        

 

@Deployment(resources ={"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})publicvoid testExecutionListenerFieldInjection(){   Map<String,Object> variables =newHashMap<String,Object>();   variables.put("myVar","listening!");
 
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);
 
Object varSetByListener = runtimeService.getVariable(processInstance.getId(),"var");   assertNotNull(varSetByListener);   assertTrue(varSetByListener instanceofString);
 
// Result is a concatenation of fixed injected field and injected expression   assertEquals("Yes, I am listening!", varSetByListener);}        

 

任务监听器

任务监听器能够在发生对应的任务相关事件时执行自定义java逻辑   或表达式。    

  任务监听器只能添加到流程定义中的用户任务中。   注意它必须定义在BPMN 2.0 extensionElements的子元素中,   并使用activiti命名空间,由于任务监听器是activiti独有的结构。      

<userTaskid="myTask"name="My Task">   <extensionElements>     <activiti:taskListenerevent="create"class="org.activiti.MyTaskCreateListener"/>   </extensionElements></userTask>

任务监听器支持如下属性:      

  • event(必选):任务监听器会被调用的任务类型。 可能的类型为:            

    • create:任务建立并设置全部属性后触发。                

    • assignment:任务分配给一些人时触发。   当流程到达userTask, assignment事件   会在create事件以前发生。   这样的顺序彷佛不天然,可是缘由很简单:当得到create时间时,   咱们想得到任务的全部属性,包括执行人。                

    • complete:当任务完成,并还没有从运行数据中删除时触发。                

    • delete:只在任务删除以前发生。   注意在经过completeTask正常完成时,也会执行。                

     

  • class:必须调用的代理类。 这个类必须实现org.activiti.engine.impl.pvm.delegate.TaskListener接口。            

    publicclassMyTaskCreateListenerimplementsTaskListener{
     
    publicvoid notify(DelegateTask delegateTask){     // Custom logic goes here   }
    }

    能够使用属性注入把流程变量或执行传递给代理类。 注意代理类的实例是在部署时建立的 (和activiti中其余类代理的状况同样),这意味着全部流程实例都会共享同一个实例。          

  • expression:(没法同时与class属性一块儿使用): 指定事件发生时执行的表达式。 能够把DelegateTask对象和事件名称(使用task.eventName) 做为参数传递给调用的对象。            

    <activiti:taskListenerevent="create"expression="${myObject.callMethod(task, task.eventName)}"/>

     

  • delegateExpression能够指定一个表达式,解析一个实现了TaskListener接口的对象, 这与服务任务一致。            

    <activiti:taskListenerevent="create"delegateExpression="${myTaskListenerBean}"/>

     

  • 在activiti 5.12中,咱们也介绍了新的任务监听器,org.activiti.engine.impl.bpmn.listener.ScriptTaskListener。   脚本任务监听器能够为任务监听器事件执行脚本。

    <activiti:taskListenerevent="complete"class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener">   <activiti:fieldname="script">     <activiti:string>       def bar = "BAR";  // local variable       foo = "FOO"; // pushes variable to execution context       task.setOwner("kermit"); // test access to task instance       bar // implicit return value     </activiti:string>   </activiti:field>   <activiti:fieldname="language"stringValue="groovy"/>   <activiti:fieldname="resultVariable"stringValue="myVar"/><activiti:taskListener>

     

 

多实例(循环)

描述

多实例节点是在业务流程中定义重复环节的一个方法。   从开发角度讲,多实例和循环是同样的:   它能够根据给定的集合,为每一个元素执行一个环节甚至一个完整的子流程,    既能够顺序依次执行也能够并发同步执行。        

多实例是在一个普通的节点上添加了额外的属性定义   (因此叫作'多实例特性'),这样运行时节点就会执行屡次。   下面的节点均可以成为一个多实例节点:          

网关事件    不能设置多实例。        

  根据规范的要求,每一个上级流程为每一个实例建立分支时都要提供以下变量:          

  • nrOfInstances:实例总数

  • nrOfActiveInstances:当前活动的,好比,还没完成的,实例数量。 对于顺序执行的多实例,值一直为1。

  • nrOfCompletedInstances:已经完成实例的数目。

  能够经过execution.getVariable(x)方法得到这些变量。        

  另外,每一个建立的分支都会有分支级别的本地变量(好比,其余实例不可见,   不会保存到流程实例级别):          

  • loopCounter:表示特定实例的在循环的索引值。能够使用activiti的elementIndexVariable属性修改loopCounter的变量名。

 

图形标记

  若是节点是多实例的,会在节点底部显示三条短线。   三条线表示实例会并行执行。   三条线表示顺序执行。          

 

Xml内容

要把一个节点设置为多实例,节点xml元素必须设置一个multiInstanceLoopCharacteristics子元素。         

<multiInstanceLoopCharacteristicsisSequential="false|true">  ... </multiInstanceLoopCharacteristics>

isSequential属性表示节点是进行 顺序执行仍是并行执行。        

  实例的数量会在进入节点时计算一次。   有一些方法配置它。一种方法是使用loopCardinality子元素直接指定一个数字。          

<multiInstanceLoopCharacteristicsisSequential="false|true">   <loopCardinality>5</loopCardinality></multiInstanceLoopCharacteristics>

也能够使用结果为整数的表达式:          

<multiInstanceLoopCharacteristicsisSequential="false|true">   <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality></multiInstanceLoopCharacteristics>

 

另外一个定义实例数目的方法是,经过loopDataInputRef子元素,设置一个类型为集合的流程变量名。   对于集合中的每一个元素,都会建立一个实例。   也能够经过inputDataItem子元素指定集合。   下面的代码演示了这些配置:          

<userTaskid="miTasks"name="My Task ${loopCounter}"activiti:assignee="${assignee}">   <multiInstanceLoopCharacteristicsisSequential="false">     <loopDataInputRef>assigneeList</loopDataInputRef>     <inputDataItemname="assignee"/>   </multiInstanceLoopCharacteristics></userTask>

假设assigneeList变量包含这些值[kermit, gonzo, foziee]。   在上面代码中,三个用户任务会同时建立。每一个分支都会拥有一个用名为assignee的流程变量,   这个变量会包含集合中的对应元素,在例子中会用来设置用户任务的分配者。        

loopDataInputRefinputDataItem的缺点是1)名字很差记,    2)根据BPMN 2.0格式定义,它们不能包含表达式。activiti经过在    multiInstanceCharacteristics中设置    collection和    elementVariable属性解决了这个问题:          

<userTaskid="miTasks"name="My Task"activiti:assignee="${assignee}">   <multiInstanceLoopCharacteristicsisSequential="true"      activiti:collection="${myService.resolveUsersForTask()}"activiti:elementVariable="assignee">   </multiInstanceLoopCharacteristics></userTask>

 

多实例节点在全部实例都完成时才会结束。也能够指定一个表达式在每一个实例结束时执行。   若是表达式返回true,全部其余的实例都会销毁,多实例节点也会结束,流程会继续执行。   这个表达式必须定义在completionCondition子元素中。          

<userTaskid="miTasks"name="My Task"activiti:assignee="${assignee}">   <multiInstanceLoopCharacteristicsisSequential="false"      activiti:collection="assigneeList"activiti:elementVariable="assignee">     <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>   </multiInstanceLoopCharacteristics></userTask>

在这里例子中,会为assigneeList集合的每一个元素建立一个并行的实例。   当60%的任务完成时,其余任务就会删除,流程继续执行。        

边界事件和多实例

  由于多实例是一个普通节点,它也能够在边缘使用边界事件。   对于中断型边界事件,当捕获事件时,全部激活的实例都会销毁。   参考如下多实例子流程:          

  这里,子流程的全部实例都会在定时器触发时销毁,不管有多少实例,   也无论内部哪一个节点没有完成。        

补偿处理器

描述

[EXPERIMENTAL]

  若是一个节点用来补偿另外一个节点的业务,它能够声明为一个补偿处理器。 补偿处理器不包含普通的流,只在补偿事件触发时执行。        

补偿处理器不能包含进入和外出顺序流。        

补偿处理器必须使用直接关联分配给一个补偿边界事件。        

图形标志

  若是节点是补偿处理器,补偿事件图标会显示在中间底部区域。下面的流程图显示了一个服务任务,附加了一个补偿边界事件,   并分配了一个补偿处理器。   注意"cancel hotel reservation"服务任务中间底部区域显示的补偿处理器图标。          

 

XML内容

为了声明做为补偿处理器的节点,咱们须要把 isForCompensation设置为 true:     
<serviceTaskid="undoBookHotel"isForCompensation="true"activiti:class="..."></serviceTask>

子流程和调用节点

子流程

描述

子流程(Sub-process)是一个包含其余节点,网关,事件等等的节点。   它本身就是一个流程,同时是更大流程的一部分。    子流程是彻底定义在父流程里的   (这就是为何叫作内嵌子流程)。     

  子流程有两种主要场景:       

  •     子流程能够使用继承式建模。 不少建模工具的子流程能够折叠, 把子流程的内部细节隐藏,显示一个高级别的端对端的业务流程总览。           

  •     子流程会建立一个新的事件做用域。 子流程运行过程当中抛出的事件,能够被子流程边缘定义的 边界事件捕获, 这样就能够建立一个仅限于这个子流程的事件做用范围。           

 

  使用子流程要考虑以下限制:       

  •     子流程只能包含一个空开始事件, 不能使用其余类型的开始事件。子路程必须 至少有一个结束节点。注意,BPMN 2.0规范容许忽略子流程的 开始和结束节点,可是当前activiti的实现并不支持。           

  • 顺序流不能跨越子流程的边界。           

 

图形标记

  子流程显示为标准的节点,圆角矩形。   这时子流程是折叠的,只显示名称和一个加号标记,   展现了高级别的流程总览:          

 

  这时子流程是展开的,子流程的步骤都显示在子流程边界内:          

 

  使用子流程的主要缘由,是定义对应事件的做用域。   下面流程模型演示了这个功能:调查软件/调查引荐任务须要同步执行,   两个任务须要在同时完成,在二线支持解决以前。   这里,定时器的做用域(好比,节点须要及时完成)是由子流程限制的。          

 

XML内容

  子流程定义为subprocess元素。   全部节点,网关,事件,等等。它是子流程的一部分,须要放在这个元素里。          

<subProcessid="subProcess">
 
<startEventid="subProcessStart"/>
  ... other Sub-Process elements ...
 
<endEventid="subProcessEnd"/>
 
</subProcess>          

 

事件子流程

描述

  事件子流程是BPMN 2.0中的新元素。事件子流程是由事件触发的子流程。   事件子流程能够添加到流程级别或任意子流程级别。用于触发事件子流程的事件是使用开始事件配置的。   为此,事件子流程是不支持空开始事件的。   事件子流程能够被消息事件,错误事件,信号事件,定时器事件,或补偿事件触发。   开始事件的订阅在包含事件子流程的做用域(流程实例或子流程)建立时就会建立。   看成用域销毁也会删除订阅。     

事件子流程能够是中断的或非中断的。一个中断的子流程会取消当前做用域内的全部流程。 非中断事件子流程会建立那一个新的同步分支。中断事件子流程只会被每一个激活状态的宿主触发一次, 非中断事件子流程能够触发屡次。子流程是不是终端的,配置使用事件子流程的开始事件配置。   

事件子流程不能有任何进入和外出流程。当事件触发一个事件子流程时,输入顺序流是没有意义的。 当事件子流程结束时,不管当前做用域已经结束了(中断事件子流程的状况), 或为非中断子流程生成同步分支会结束。   

当前的限制:   

  • activiti只支持中断事件子流程。   

  • activiti只支持使用错误开始事件或消息开始事件的事件子流程。   

 

图像标记

  事件子流程能够显示为边框为虚线的内嵌子流程。          

 

XML内容

事件子流程的XML内容与内嵌子流程是同样的。 另外,要把triggeredByEvent属性设置为true

<subProcessid="eventSubProcess"triggeredByEvent="true">         ... </subProcess>

 

实例

下面是一个使用错误开始事件触发的事件子流程的实例。事件子流程是放在“流程级别”的, 意思是,做用于流程实例: 

事件子流程的XML以下所示:

<subProcessid="eventSubProcess"triggeredByEvent="true">         <startEventid="catchError">                 <errorEventDefinitionerrorRef="error"/>         </startEvent>         <sequenceFlowid="flow2"sourceRef="catchError"targetRef="taskAfterErrorCatch"/>         <userTaskid="taskAfterErrorCatch"name="Provide additional data"/></subProcess>

 

如上面所述,事件子流程也能够添加成内嵌子流程。若是添加为内嵌子流程,它实际上是边界事件的一种替代方案。 考虑下面两个流程图。两种状况内嵌子流程会抛出一个错误事件。两种状况错误都会被捕获并使用一个用户任务处理。       

 

相对于:

 

两种场景都会执行相同的任务。然而,两种建模的方式是不一样的:       

  • 内嵌子流程是使用与执行做用域宿主相同的流程执行的。意思是内嵌子流程能够访问它做用域内的内部变量。 当使用边界事件时,执行内嵌子流程的流程会删除,并生成一个流程根据边界事件的顺序流继续执行。 这意味着内嵌子流程建立的变量再也不起做用了。       

  • 当使用事件子流程时,事件是彻底由它添加的子流程处理的。 当使用边界事件时,事件由父流程处理。       

这两个不一样点能够帮助咱们决定是使用边界事件仍是内嵌事件子流程来解决特定的流程建模/实现问题。        

事务子流程

[EXPERIMENTAL]

描述

  事务子流程是内嵌子流程,能够用来把多个流程放到一个事务里。   事务是一个逻辑单元,能够把一些单独的节点放在一块儿,这样它们就能够一块儿成功或一块儿失败。     

事务可能的结果:     事务能够有三种可能的结果:    

  • 事务成功,若是没有取消也没有由于问题终结。若是事务子流程是成功的, 就会使用外出顺序流继续执行。 若是流程后来抛出了一个补偿事件,成功的事务可能被补偿。   

    注意:和普通内嵌子流程同样,事务可能在成功后, 使用中间补偿事件进行补偿。   

     

  • 事务取消,若是流程到达取消结束事件。这时, 全部流程都会终结和删除。触发补偿的一个单独的流程,会经过取消边界事件继续执行。 在补偿完成以后,事务子流程会使用取消边界事务的外出顺序流向下执行。    

  • 事务被问题结束,若是跑出了一个错误事件, 并且没有在事务子流程中捕获。(若是错误被事务子流程的边界事件处理了,也会这样应用。) 这时,不会执行补偿。    

下面的图形演示了三种不一样的结果:    

 

与ACID事务的关系:必定不要吧bpmn事务子流程与技术(ACID)事务相混淆。 bpmn事务子流程不是技术事务领域的东西。要理解activiti中的事务管理,请参考 并发与事务。 bpmn事务和技术事务有如下不一样点:   

  • ACID事务通常是短时间的,bpmn事务可能持续几小时,几天,甚至几个月才能完成。 (考虑事务中包含的节点可能有用户任务,通常人员响应的时间比应用时间要长。或者, 或者,在其余状况下,bpmn事务可能要等待发生一些事务事件,就像要根据某种次序执行。) 这种操做一般要相比更新数据库的一条数据,或把一条信息保存到事务性队列中,消耗更长的时间来完成。   

  • 由于不能在整个业务节点的过程当中保持一个技术性的事务,因此bpmn事务通常要跨越多个ACID事务。   

  • 由于bpmn事务会跨越多个ACID事务,因此会丧失ACID的特性。好比,考虑上述例子。 假设“约定旅店”和“刷信用卡”操做在单独的ACID事务中执行。 也假设“预约旅店”节点已经成功了。如今咱们处于一个中间不稳定状态,由于咱们预约了酒店,可是尚未刷信用卡。 如今,在一个ACID事务中,咱们要依次执行不一样的操做,也会有一个中间不稳定状态。 不一样的是,这个中间状态对事务的外部是可见的。 好比,若是经过外部预约服务进行了预约,其余使用相同预约服务的部分就能够看到旅店被预约了。 这意味着实现业务事务时,咱们彻底失去了隔离属性 (注:咱们也常常放弃隔离性,来为ACID事务得到更高的并发,可是咱们能够彻底控制,中间不稳定状态也只持续很短的时间)。   

  • bpmn业务事务也不能使用一般的方式回滚。由于它跨越了多个事务,bpmn事务取消时一些ACID事务可能已经提交了。 这时,它们不能被回滚了。   

 

由于bpmn事务实际上运行时间很长,缺少隔离性和回滚机制都须要被区别对待。 实际上,这里也没有更好的办法在特定领域处理这些问题:   

  • 使用补偿执行回滚。若是事务范围抛出了取消事件,会影响已经执行成功的节点, 并使用补偿处理器执行补偿。   

  • 隔离性的缺少一般使用特定领域的解决方法来解决。好比,上面的例子中, 一个旅店房间可能会展现给第二个客户,在咱们确认第一个客户付费以前。 虽然这可能与业务预期不符,预约服务可能选择容许一些过分的预定。   

  • 另外,由于事务会由于风险而中断,预约服务必须处理这种状况,已经预约了旅店,可是一直没有付款的状况。 (由于事务被中断了)。这时预约服务须要选择一个策略,在旅店房间预约超过最大容许时间后, 若是尚未付款,预约就会取消。   

综上所述:ACID处理的是一般问题(回滚,隔离级别和启发式结果), 在实现业务事务时,咱们须要找到特定领域的解决方案来处理这些问题。   

目前的限制:   

  • bpmn规范要求流程引擎能根据底层事务的协议处理事件,好比若是底层协议触发了取消事件,事务就会取消。 做为内嵌引擎,activiti目前不支持这项功能。(对此形成的后果,能够参考下面的一致性讨论)。   

 

ACID事务顶层的一致性和优化并发: bpmn事务保证一致性,要么全部节点都成功,或者一些节点成功,对其余成功的节点进行补偿。 不管哪一种方式,都会有一致性的结果。不过要讨论一些activiti内部的状况,bpmn事务的一致性模型是叠加在流程的一致性模型之上的。 activiti执行流程是事务性的。并发使用了乐观锁。在activiti中,bpmn错误,取消和补偿事件都创建在一样的acid事务与乐观锁之上。 好比,取消结束事件只能触发它实际到达的补偿。若是以前服务任务抛出了未声明的异常。或者, 补偿处理器的效果没法提交,若是底层的acid事务的参与者把事务设置成必须回滚。 或者当两个并发流程到达了取消结束事件,可能会触发两次补偿,并由于乐观锁异常失败。 全部这些都说明activiti中实现bpmn事务时,相同的规则也做用域普通的流程和子流程。 因此为了保证一致性,重要的是使用一种方式考虑实现乐观事务性的执行模型。   

图形标记

  事务子流程显示为内嵌子流程,使用双线边框。          

 

XML内容

事务子流程使用transaction标签:

<transactionid="myTransaction">         ... </transaction>

 

实例

下面是事务子流程的实例:       

 

调用活动(子流程)

描述

bpmn 2.0区分了普通子流程,   也叫作内嵌子流程,和调用节点,看起来很类似。   上概念上讲,当流程抵达及诶单时,二者都会调用子流程。     

  不一样点是调用节点引用流程定义外部的一个流程,子流程   会内嵌到原始的流程定义中。使用调用节点的主要场景是须要重用流程定义,   这个流程定义须要被不少其余流程定义调用的时候。     

当流程执行到调用节点,会建立一个新分支,它是到达调用节点的流程的分支。   这个分支会用来执行子流程,默认建立并行子流程,就像一个普通的流程。   上级流程会等待子流程完成,而后才会继续向下执行。     

图形标记

  调用节点显示与子流程相同,   不过是粗边框(不管是折叠和展开的)。   根据不一样的建模工具,调用节点也能够展开,可是显示为折叠的子流程。          

 

XML内容

A call activity is a regular activity, that requires a calledElement          that references a process definition by its key.          In practice, this means that the id of the process is          used in the calledElement.

<callActivityid="callCheckCreditProcess"name="Check credit"calledElement="checkCreditProcess"/>

 

注意,子流程的流程定义是在执行阶段解析的。   就是说子流程能够与调用的流程分开部署,若是须要的话。        

传递变量

  能够把流程变量传递给子流程,反之亦然。数据会复制给子流程,当它启动的时候,   并在它结束的时候复制回主流程。

<callActivityid="callSubProcess"calledElement="checkCreditProcess">   <extensionElements>           <activiti:insource="someVariableInMainProcess"target="nameOfVariableInSubProcess"/>           <activiti:outsource="someVariableInSubProcss"target="nameOfVariableInMainProcess"/>   </extensionElements></callActivity>

咱们使用activiti扩展来简化BPMN标准元素调用dataInputAssociation和    dataOutputAssociation,这只在你使用BPMN 2.0标准方式声明流程变量才管用。        

  这里也能够使用表达式:

<callActivityid="callSubProcess"calledElement="checkCreditProcess">         <extensionElements>           <activiti:insourceExpression="${x+5}"" target="y" />           <activiti:outsource="${y+5}"target="z"/>         </extensionElements></callActivity>

最后z = y + 5 = x + 5 + 5        

实例

下面的流程图演示了简单订单处理。先判断客户端信用,这可能与不少其余流程相同。    检查信用阶段这里设计成调用节点。          

  流程看起来像下面这样:

<startEventid="theStart"/><sequenceFlowid="flow1"sourceRef="theStart"targetRef="receiveOrder"/>
<manualTaskid="receiveOrder"name="Receive Order"/><sequenceFlowid="flow2"sourceRef="receiveOrder"targetRef="callCheckCreditProcess"/>
<callActivityid="callCheckCreditProcess"name="Check credit"calledElement="checkCreditProcess"/><sequenceFlowid="flow3"sourceRef="callCheckCreditProcess"targetRef="prepareAndShipTask"/>
<userTaskid="prepareAndShipTask"name="Prepare and Ship"/><sequenceFlowid="flow4"sourceRef="prepareAndShipTask"targetRef="end"/>
<endEventid="end"/>

子流程看起来像下面这样:          

  子流程的流程定义没有什么特别的。   它也能够单独使用,不用其余流程调用。        

事务和并发

异步操做

activiti经过事务方式执行流程,能够根据你的需求定制。如今开始看一下activiti一般是如何处理事务的。 若是触发了activiti的操做(好比,开始流程,完成任务,触发流程继续执行), activiti会推动流程,直到每一个分支都进入等待状态。更抽象的说,它会流程图执行深度优先搜索, 若是每一个分支都遇到等待状态,就会返回。等待状态是"稍后"须要执行任务, 就是说activiti会把当前状态保存到数据库中,而后等待下一次触发。 触发可能来自外部,好比用户任务或接收到一个消息,也可能来自activiti自己,好比咱们设置了定时器事件。 下面图片展现了这种操做:        

咱们能够看到包含用户任务,服务任务和定时器事件的流程。完成用户任务,和校验地址是在同一个工做单元中, 因此它们的成功和失败是原子性的。意味着若是服务任务抛出异常,咱们要回滚当前事务, 这样流程会退回到用户任务,用户任务就依然在数据库里。 这就是activiti默认的行为。在(1)中应用或客户端线程完成任务。这会执行服务,流程推动,直到遇到一个等待状态, 这里就是定时器(2)。而后它会返回给调用者(3),并提交事务(若是事务是由activiti开启的)。    

有的时候,这不是咱们想要的。有时咱们须要本身控制流程中事务的边界,这样就能把业务逻辑包裹在一块儿。 这就须要使用异步执行了。参考下面的流程(判断):        

此次咱们完成了用户任务,生成一个发票,把发票发送给客户。 此次生成发票不在同一个工做单元内了,因此咱们不想对用户任务进行回滚,若是生成发票出错了。 因此,咱们想让activiti实现的是完成用户任务(1),提交事务,返回给调用者应用。而后在后台的线程中,异步执行生成发票。 后台线程就是activiti的job执行器(实际上是一个线程池)周期对数据库的job进行扫描。 因此后面的场景,当咱们到达"generate invoice"任务,咱们为activiti建立一个稍后执行的job"消息", 并把它保存到数据库。 job会被job执行器获取并执行。咱们也会给本地job执行器一个提醒,告诉它有一个新job,来增长性能。

要想使用这个特性,咱们要使用activiti:async="true"扩展。例子中,服务任务看起来就是这样:        

<serviceTaskid="service1"name="Generate Invoice"activiti:class="my.custom.Delegate"activiti:async="true"/>         

        activiti:async能够使用到以下bpmn任务类型中:         task, serviceTask, scriptTask, businessRuleTask, sendTask, receiveTask, userTask, subProcess, callActivity    

对于userTask,receiveTask和其余等待装填,异步执行的做用是让开始流程监听器运行在一个单独的线程/事务中。    

排他任务

从activiti 5.9开始,JobExecutor能保证同一个流程实例中的job不会并发执行。为啥呢?        

为何要使用排他任务?

参考以下流程定义:

咱们有一个并行网关,后面有三个服务任务,它们都设置为异步执行。这样会添加三个job到数据库里。 一旦job进入数据库,它就能够被jobExecutor执行了。JobExecutor会获取job,把它们代理到工做线程的线程池中,会在那里真正执行job。 就是说,使用异步执行,你能够吧任务分配给这个线程池(在集群环境,可能会使用多个线程池)。这一般是个好事情。 然而它也会产生问题:一致性。考虑一下服务任务后的汇聚。 当服务任务完成后,咱们到达并发汇聚节点,须要决定是等待其余分支,仍是继续向下执行。 就是说,对每一个到达并行汇聚的分支,咱们都须要判断是继续仍是等待其余分支的一个或多个分支。

为何这就是问题了呢?由于服务任务配置成使用异步执行,可能相关的job都在同一时间被获取,被JobExecutor分配给不一样的工做线程执行。 结果是三个单独的服务执行使用的事务在到达并发汇聚时可能重叠。若是出现了这个问题,这些事务是互相不可见的, 其余事务同时到达了相同的并发汇聚,假设它们都在等待其余分支。然而,每一个事务都假设它们在等待其余分支, 因此没有分支会越过并发汇聚继续执行,流程实例会一直在等待状态,没法继续执行。          

activiti是如何解决这个问题的?activiti使用了乐观锁。当咱们基于判断的数据看起来不是最新的时 (由于其余事务可能在咱们提交以前进行了修改,咱们会在每一个事务里增长数据库同一行的版本)。这时,第一个提交的事务会成功, 其余会由于乐观锁异常致使失败。这就解决了咱们上面讨论的流程的问题:若是多个分支同步到达并行汇聚, 它们会假设它们都在登陆,并增长它们父流程的版本号(流程实例)而后尝试提交。 第一个分支会成功提交,其余分支会由于乐观锁致使失败。由于流程是被job触发的,   activiti会尝试在等待一段时间后尝试执行同一个job,想这段时间能够同步网关的状态。          

这是一个很好的解决方案吗?像咱们看到的同样,乐观锁容许activiti避免非一致性。它肯定咱们不会“堵在汇聚网关”, 意思是:或者全部分支都经过网关,或者数据库中的job正在尝试经过。然而,虽然这是一个对于持久性和一致性的完美解决方案, 但对于上层来讲不必定是指望的行为:           

  • activiti只会对同一个job重试估计次数(默认配置为3)。以后,job还会在数据库里,可是不会再重试了。   意味着这个操做必须手工执行job的触发。         

  •   若是job有非事务方面的效果,它不会由于失败的事务回滚。好比,若是“预约演唱会门票”服务没有与activiti共享事务,   重试job可能致使咱们预约了过多门票。         

 

在activiti中,咱们推荐了新的概念,并已经在jbpm4中实现了,叫作“排他job”。         

什么是排他job?

对于一个流程实例,排他任务不能同时执行两个。考虑上面的流程: 若是咱们把服务任务申请为排他任务,JobExecutor会保证对应的job不会并发执行。 相反,它会保证不管何时获取一个流程实例的排他任务,都会把同一个流程实例的其余任务都取出来,放在同一个工做线程中执行。 它保证job是顺序执行的。       

如何启用这个特性?从activiti 5.9开始,排他任务已是默认配置了。因此异步执行和定时器事件默认都是排他任务。 另外,若是你想把job设置为非塔牌,能够使用activiti:exclusive="false"进行配置。 好比,下面的服务任务就是异步可是非排他的。       

<serviceTaskid="service"activiti:expression="${myService.performBooking(hotel, dates)}"activiti:async="true"activiti:exclusive="false"/>                         

 

这是一个好方案吗? 有一些人文咱们这是不是一个好方案。他们的结论会帮你在并发和性能问题方面节省时间。 这个问题上须要考虑两件事情:          

  •   若是是你专家而且知道本身在作什么时(理解“为何排他任务”这章的内容),也能够关闭这个功能,   不然,对于大多数使用异步执行和定时器的用户来讲,这个功能是没问题的。         

  •   它也没有性能问题,在高负载的状况下性能是个问题。高负载意味着JobExecutor的全部工做线程都一直在忙碌着。   使用排他任务,activiti能够简单的分布不一样的负载。排他任务意味着同一个流程实例的异步执行会由相同的线程顺序执行。   可是要考虑:若是你有多个流程实例时。   全部其余流程实例的job也会分配给其余线程同步执行。   意味着虽然activiti不会同时执行一个流程实例的排他job,可是还会同步执行多个流程实例的一步执行。 经过一个整体的预测,在大多数场景下,它都会让单独的实例运行的更迅速。 并且,对于同一流程实例中的job,须要用到的数据也会利用执行的集群节点的缓存。 若是任务没有在同一个节点执行,数据就必须每次从数据库从新读取了。         

 

流程实例受权

  默认全部人在部署的流程定义上启动一个新流程实例。经过流程初始化受权功能定义的用户和组,web客户端能够限制哪些用户能够启动一个新流程实例。    注意:activiti引擎 不会校验受权定义。   这个功能只是为减轻web客户端开发者实现校验规则的难度。   设置方法与用户任务用户分配相似。   用户或组能够使用<activiti:potentialStarter>标签分配为流程的默认启动者。下面是一个例子: 
   <processid="potentialStarter">      <extensionElements>        <activiti:potentialStarter>          <resourceAssignmentExpression>            <formalExpression>group2, group(group3), user(user3)</formalExpression>          </resourceAssignmentExpression>        </activiti:potentialStarter>      </extensionElements>    <startEventid="theStart"/>    ...  
上面的XML中,user(user3)是直接引用了用户user3,group(group3)是引用了组group3。若是没显示设置,默认认为是群组。 也能够使用<process>标签的属性,<activiti:candidateStarterUsers>和<activiti:candidateStarterGroups>。 下面是一个例子:  
      <processid="potentialStarter"activiti:candidateStarterUsers="user1, user2"                                         activiti:candidateStarterGroups="group1">       ...   

能够同时使用这两个属性。

定义流程初始化受权后,开发者能够使用以下方法得到受权定义。 这些代码能够得到给定的用户能够启动哪些流程定义:   

      processDefinitions = repositoryService.createProcessDefinitionQuery().startableByUser("userxxx").list();    

 

也能够得到指定流程定义设置的潜在启动者对应的IdentityLink。   

      identityLinks = repositoryService.getIdentityLinksForProcessDefinition("processDefinitionId");    

 

下面例子演示了如何得到能够启动给定流程的用户列表:   

      List<User> authorizedUsers =  identityService().createUserQuery().potentialStarter("processDefinitionId").list();    

 

相同的方式,得到能够启动给定流程配置的群组:   

      List<Group> authorizedGroups =  identityService().createGroupQuery().potentialStarter("processDefinitionId").list();    

 

数据对象

[试验功能]    

       BPMN提供了一种功能,能够在流程定义或子流程中定义数据对象。根据BPMN规范,流程定义能够包含复杂XML结构,   能够导入XSD定义。对于Activiti来讲,做为Activiti首次支持的数据对象,能够支持以下的XSD类型:    

        <dataObjectid="dObj1"name="StringTest"itemSubjectRef="xsd:string"/>       
        <dataObjectid="dObj2"name="BooleanTest"itemSubjectRef="xsd:boolean"/>       
        <dataObjectid="dObj3"name="DateTest"itemSubjectRef="xsd:datetime"/>       
        <dataObjectid="dObj4"name="DoubleTest"itemSubjectRef="xsd:double"/>       
        <dataObjectid="dObj5"name="IntegerTest"itemSubjectRef="xsd:int"/>       
        <dataObjectid="dObj6"name="LongTest"itemSubjectRef="xsd:long"/>       

数据对象定义会自动转换为流程变量,名称与'name'属性对应。   除了数据对象的定义以外,activiti也支持使用扩展元素来为这个变量赋予默认值。下面的BPMN片断就是对应的例子:    

        <processid="dataObjectScope"name="Data Object Scope"isExecutable="true">           <dataObjectid="dObj123"name="StringTest123"itemSubjectRef="xsd:string">             <extensionElements>               <activiti:value>Testing123</activiti:value>             </extensionElements>           </dataObject>       

1

Chapter 9. 表单

Activiti提供了一种方便并且灵活的方式在业务流程中以手工方式添加表单。咱们对表单的支持有2种方式: 经过表单属性对内置表单进行渲染和外置表单进行渲染。

表单属性

业务流程相关联的全部信息要么是包含自身的流程变量,要么是经过流程变量的引用。Activiti支持存储复杂的Java对象做为流程变量,如 序列化对象, Jpa实体对象或者整个XML文档做为字符串。    

用户是在启动一个流程和完成用户任务时,与流程进行交互的。表单须要某个UI技术渲染以后才可以与用户进行交互。为了可以使用不一样UI技术变得容易,流程定义包含一个对流程变量中复杂的Java类型对象到一个'properties'Map<String,String>类型的转换逻辑。    

使用Activiti API的方法查看公开的属性信息。而后,任意UI技术都可以在这些属性上面构建一个表单。该属性专门(而且更多局限性)为流程变量提供了一个视图。 表单所须要显示的属性能够从下面例子中的返回值FormData中获取。    

StartFormDataFormService.getStartFormData(String processDefinitionId)

    or

TaskFormdataFormService.getTaskFormData(String taskId)

.    

在默认状况下,内置的表单引擎,'sees'这些变量就像对待流程变量同样。若是任务表单属性和流程变量是一对一的关系,那么任务表单属性就不须要进行申明了,例如,下面的申明:    

<startEventid="start"/>

当执行到开始事件时,全部的流程变量都是可用的,但    

formService.getStartFormData(String processDefinitionId).getFormProperties()

会是一个空值,由于没有定义具体的映射。    

在上面的实例中,全部被提交的属性都将会做为流程变量被存储在Activiti使用的数据库中。这意味着在一个表单中新添加一个简单的input输入字段,也会做为一个新的变量被存储。    

属性来自于流程变量,但不必定非要做为流程变量存储起来,例如,一个流程变量多是JPA实体如类Address。在某种UI技术中使用的表单属性StreetName可能会关联到一个表达式 #{address.street}。    

相似的,用户提交的表单属性应该做为流程变量进行存储或者使用UEL值表达式将其做为流程变量的一个嵌套属性进行存储,例如#{address.street}。    

一样的,提交的表单属性默认的行为是做为流程变量进行存储,除非一个 formProperty 申明了其余的规则。

类型转换一样也能够应用于表单数据和流程变量之间处理的一部分。

例如:

<userTaskid="task">   <extensionElements>     <activiti:formPropertyid="room"/>     <activiti:formPropertyid="duration"type="long"/>     <activiti:formPropertyid="speaker"variable="SpeakerName"writable="false"/>     <activiti:formPropertyid="street"expression="#{address.street}"required="true"/>   </extensionElements></userTask>
  • 表单属性 room 将会被映射为String类型流程变量 room。   

  •   表单属性 duration 将会被映射为java.lang.Long类型流程变量 duration。   

  •   表单属性speaker将会被映射为流程变量 SpeakerName。它只可以在TaskFormData对象中使用。若是   属性speaker提交,将会抛出一个ActivitiException的异常。相似的,将其属性设置为readable="false",该属性就会在FormData进行排除,可是在提交后仍然会对其进行处理。   

  •   表单属性street将会映射为Java Bean address的属性street 做为String类型的流程变量。       当提交的表单属性并无提供而且 required="true" 时,那么就会抛出一个异常。   

表单数据也能够做为FormData的一部分提供类型元数据。该FormData能够从StartFormData FormService.getStartFormData(String processDefinitionId)TaskFormdata FormService.getTaskFormData(String taskId)方法的返回值中获取。

咱们支持如下的几种表单属性类型:

  • string (org.activiti.engine.impl.form.StringFormType)

  • long (org.activiti.engine.impl.form.LongFormType)

  • enum (org.activiti.engine.impl.form.EnumFormType)

  • date (org.activiti.engine.impl.form.DateFormType)

  • boolean (org.activiti.engine.impl.form.BooleanFormType)

对于申明每个表单属性,如下的FormProperty信息能够经过List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()     和 List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()获取。    

publicinterfaceFormProperty{   /**
  the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}    * or {@link FormService#submitTaskFormData(String, java.util.Map)} */
  String getId();   /** the display label */   String getName();   /** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */   FormType getType();   /** optional value that should be used to display in this property */   String getValue();   /** is this property read to be displayed in the form and made accessible with the methods    * {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */   boolean isReadable();   /** is this property expected when a user submits the form? */   boolean isWritable();   /** is this property a required input field */   boolean isRequired();}

例子:

<startEventid="start">   <extensionElements>     <activiti:formPropertyid="speaker"       name="Speaker"       variable="SpeakerName"       type="string"/>
   
<activiti:formPropertyid="start"       type="date"       datePattern="dd-MMM-yyyy"/>
   
<activiti:formPropertyid="direction"type="enum">       <activiti:valueid="left"name="Go Left"/>       <activiti:valueid="right"name="Go Right"/>       <activiti:valueid="up"name="Go Up"/>       <activiti:valueid="down"name="Go Down"/>     </activiti:formProperty>
 
</extensionElements></startEvent>

全部的表单属性的信息都是能够经过API进行访问的。能够经过 formProperty.getType().getName()获取类型的名称。 甚至能够经过 formProperty.getType().getInformation("datePattern")获取日期的匹配方式。经过 formProperty.getType().getInformation("values")能够获取到枚举值。

Activiti控制台支持表单属性而且能够根据表单定义对表单进行渲染。例以下面的XML片断      

<startEvent ... >   <extensionElements>     <activiti:formPropertyid="numberOfDays"name="Number of days"value="${numberOfDays}"type="long"required="true"/>     <activiti:formPropertyid="startDate"name="First day of holiday (dd-MM-yyy)"value="${startDate}"datePattern="dd-MM-yyyy hh:mm"type="date"required="true"/>     <activiti:formPropertyid="vacationMotivation"name="Motivation"value="${vacationMotivation}"type="string"/>   </extensionElements></userTask>       

当使用 Activiti控制台时,它将会被渲染成流程的启动表单。     

 

外置表单的渲染

该API一样也容许你执行Activiti流程引擎以外的方式渲染你本身的任务表单。这些步骤说明你能够用你本身的方式对任务表单进行渲染。

本质上,全部须要渲染的表单属性都是经过2个服务方法中的一个进行装配的:     StartFormData FormService.getStartFormData(String processDefinitionId)     和 TaskFormdata FormService.getTaskFormData(String taskId).    

表单属性能够经过 ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)    and void FormService.submitStartFormData(String taskId, Map<String,String> properties)2种方式进行提交。

想要了解更多表单属性如何映射为流程变量,能够查看 the section called “表单属性”  

你能够将任何表单模版资源放进你要部署的业务文档之中(若是你想要将它们按照流程的版本进行存储)。它将会在部署中做为一种可用的资源。你能够使用String ProcessDefinition.getDeploymentId()InputStream RepositoryService.getResourceAsStream(String deploymentId, String resourceName); 获取部署上去的表单模版。这样就能够获取你的表单模版定义文件 ,那么你就能够在你本身的应用中渲染/显示你的表单。    

你也能够使用该功能获取任务表单以外的其余的部署资源用于其余的目的。

属性<userTask activiti:formKey="..."经过API   String FormService.getStartFormData(String processDefinitionId).getFormKey()     和 String FormService.getTaskFormData(String taskId).getFormKey()暴露出来的。 你能够使用这个存储你部署的模版中的全名(例如org/activiti/example/form/my-custom-form.xml),可是这并非必须的。 例如,你能够在表单属性中存储一个通用的key,而后运用一种算法或者换转去获得你实际使用的模版。当你想要经过不一样UI技术渲染不能的表单,这可能更加方便,例如,使用正常屏幕大小的web应用程序的表单, 移动手机小屏幕的表单和甚至多是IM表单和email表单模版。    

Chapter 10. JPA

你能够使用JPA实体做为流程变量,而且能够这样作:    

  • 基于流程变量更新已有的JPA实体,它能够在用户任务的表单中填写或者由服务任务生成。

  • 重用已有的领域模型不须要编写显示的服务获取实体或者更新实体的值。

  • 根据已有实体的属性作出判断(网关即分支聚合)。

  • ...

 

要求

  只支持符合如下要求的实体:      

  • 实体应该使用JPA注解进行配置,咱们支持字段和属性访问两种方式。@MappedSuperclass也可以被使用。

  • 实体中应该有一个使用@Id注解的主键,不支持复合主键 (@EmbeddedId@IdClass)。Id 字段/属性可以使用JPA规范支持的任意类型:            原生态数据类型和他们的包装类型(boolean除外),String, BigInteger, BigDecimal,             java.util.Datejava.sql.Date.          

 

配置

为了可以使用JPA的实体,引擎必须有一个对EntityManagerFactory的引用。 这能够经过配置引用或者提供一个持久化单元名称。做为变量的JPA实体将会被自动检测并进行相应的处理

下面例子中的配置是使用jpaPersistenceUnitName:        

<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
   
<!-- 数据库的配置 -->     <propertyname="databaseSchemaUpdate"value="true"/>     <propertyname="jdbcUrl"value="jdbc:h2:mem:JpaVariableTest;DB_CLOSE_DELAY=1000"/>
   
<propertyname="jpaPersistenceUnitName"value="activiti-jpa-pu"/>     <propertyname="jpaHandleTransaction"value="true"/>     <propertyname="jpaCloseEntityManager"value="true"/>
   
<!-- job executor configurations -->     <propertyname="jobExecutorActivate"value="false"/>
   
<!-- mail server configurations -->     <propertyname="mailServerPort"value="5025"/></bean>        

接下来例子中的配置提供了一个咱们自定义的EntityManagerFactory(在这个例子中,使用了OpenJPA 实体管理器)。注意该代码片断仅仅包含与例子相关的beans,去掉了其余beans。OpenJPA实体管理的完整并能够使用的例子能够在activiti-spring-examples(/activiti-spring/src/test/java/org/activiti/spring/test/jpa/JPASpringTest.java)中找到。        

<beanid="entityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">   <propertyname="persistenceUnitManager"ref="pum"/>   <propertyname="jpaVendorAdapter">     <beanclass="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter">       <propertyname="databasePlatform"value="org.apache.openjpa.jdbc.sql.H2Dictionary"/>     </bean>   </property></bean>
<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">   <propertyname="dataSource"ref="dataSource"/>   <propertyname="transactionManager"ref="transactionManager"/>   <propertyname="databaseSchemaUpdate"value="true"/>   <propertyname="jpaEntityManagerFactory"ref="entityManagerFactory"/>   <propertyname="jpaHandleTransaction"value="true"/>   <propertyname="jpaCloseEntityManager"value="true"/>   <propertyname="jobExecutorActivate"value="false"/></bean>        

一样的配置也能够在编程式建立一个引擎时完成,例如:        

ProcessEngine processEngine =ProcessEngineConfiguration   .createProcessEngineConfigurationFromResourceDefault()   .setJpaPersistenceUnitName("activiti-pu")   .buildProcessEngine();         

 

配置属性:        

  • jpaPersistenceUnitName:    使用持久化单元的名称(要确保该持久化单元在类路径下是可用的)。根据该规范,默认的路径是/META-INF/persistence.xml)。要么使用 jpaEntityManagerFactory 或者jpaPersistenceUnitName。            

  • jpaEntityManagerFactory:    一个实现了javax.persistence.EntityManagerFactory的bean的引用。它将被用来加载实体而且刷新更新。要么使用jpaEntityManagerFactory 或者jpaPersistenceUnitName。            

  • jpaHandleTransaction:    在被使用的EntityManager 实例上,该标记表示流程引擎是否须要开始和提交/回滚事物。当使用Java事物API(JTA)时,设置为false。            

  • jpaCloseEntityManager:    该标记表示流程引擎是否应该关闭从 EntityManagerFactory获取的 EntityManager的实例。当EntityManager 是由容器管理的时候须要设置为false(例如 当使用并非单一事物做用域的扩展持久化上下文的时候)。            

 

用法

简单例子

  使用JPA变量的例子能够在        JPAVariableTest中找到。咱们将会一步一步的解释JPAVariableTest.testUpdateJPAEntityValues。   

首先,咱们须要建立一个基于META-INF/persistence.xmlEntityManagerFactory做为咱们的持久化单元。它包含持久化单元中全部的类和一些供应商特定的配置。      

咱们将使用一个简单的实体做为测试,其中包含有一个id和String 类型的value属性,这也将会被持久化。在容许测试以前,咱们建立一个实体而且保存它。        

@Entity(name ="JPA_ENTITY_FIELD")publicclassFieldAccessJPAEntity{
 
@Id   @Column(name ="ID_")   privateLong id;
 
privateString value;
 
publicFieldAccessJPAEntity(){     // Empty constructor needed for JPA   }
 
publicLong getId(){     return id;   }
 
publicvoid setId(Long id){     this.id = id;   }
 
publicString getValue(){     return value;   }
 
publicvoid setValue(String value){     this.value = value;   }}        

 

咱们启动一个新的流程实例,添加一个实体做为变量。至于其余的变量,它们将会被存储在流程引擎的持久化数据库中。下一次获取该变量的时候,它将会根据该类和存储Id从EntityManager中加载。        

Map<String,Object> variables =newHashMap<String,Object>(); variables.put("entityToUpdate", entityToUpdate);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("UpdateJPAValuesProcess", variables);        

 

在咱们的流程定义中的第一个节点是一个服务任务,它将会调用entityToUpdate上的setValue方法,它其实就是咱们以前在启动流程实例时候设置的JPA变量而且它将会从当前流程引擎的上下文关联的EntityManager中加载。        

<serviceTaskid='theTask'name='updateJPAEntityTask'activiti:expression="${entityToUpdate.setValue('updatedValue')}"/>         

 

当完成服务任务时,流程实例将会停留在流程定义中定义的用户任务环节上。这时咱们就能够查看该流程实例。与此同时,EntityManager已经被刷新了而且改变的实体已经被保存进数据库中。当咱们获取entityToUpdate的变量value时,该实体将会被再次加载而且咱们获取该实体属性的 将会是 updatedValue。        

// Servicetask in process 'UpdateJPAValuesProcess' should have set value on entityToUpdate.Object updatedEntity = runtimeService.getVariable(processInstance.getId(),"entityToUpdate"); assertTrue(updatedEntity instanceofFieldAccessJPAEntity); assertEquals("updatedValue",((FieldAccessJPAEntity)updatedEntity).getValue());         

 

查询JPA流程变量

你能够查询某一JPA实体做为变量的ProcessInstances 和 Executions 。         注意,在ProcessInstanceQueryExecutionQuery查询中仅仅variableValueEquals(name, entity) 支持JPA实体变量 。        方法 variableValueNotEquals, variableValueGreaterThan, variableValueGreaterThanOrEqual, variableValueLessThan         和 variableValueLessThanOrEqual并不被支持而且传递JPA实体值的时候会抛出一个ActivitiException。        

 ProcessInstance result = runtimeService.createProcessInstanceQuery().variableValueEquals("entityToQuery", entityToQuery).singleResult();         

 

使用Spring beans和JPA结合的高级例子

  一个更加高级的例子,JPASpringTest,能够在activiti-spring-examples中找到。它描述了以下简单的使用状况:        

  • 已经存在了一个使用JPA实体的Spring-bean,它用来存储贷款申请。

  • 使用Activiti,咱们能够经过已经存在的bean获取已经使用的实体,并使用它做为变量用于咱们的流程中。

    按照下面的步骤定义流程:               

    •   服务任务,建立一个新的贷款申请,使用已经存在的LoanRequestBean 接受启动流程时候的变量(例如 能够来自流程启动时候的表单)。   使用activiti:resultVariable(它做为一个变量对表达式返回的结果进行存储)将建立出来的实体做为变量进行存储。                  

    • 用户任务,容许经理查看贷款申请,并填入审批意见(赞成/不一样意),审批意见将做为一个boolean变量approvedByManager进行存储

    • 服务任务,更新贷款申请实体所以该实体与流程保持同步

    •   根据贷款申请实体变量approved的值,将利用惟一网关(BPMN2规范)自动决定下一步该选择那一条路径:当申请批准,流程结束。不然,一个额外的任务将会使用   (发送拒绝信),因此这样就是可让拒绝信手动通知客户。                  

     

请注意该流程并不包含任何表单,由于它仅仅被用于单元测试。        

 

 

<?xml version="1.0" encoding="UTF-8"?><definitionsid="taskAssigneeExample"   xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xmlns:activiti="http://activiti.org/bpmn"   targetNamespace="org.activiti.examples">
 
<processid="LoanRequestProcess"name="Process creating and handling loan request">     <startEventid='theStart'/>     <sequenceFlowid='flow1'sourceRef='theStart'targetRef='createLoanRequest'/>
   
<serviceTaskid='createLoanRequest'name='Create loan request'       activiti:expression="${loanRequestBean.newLoanRequest(customerName, amount)}"       activiti:resultVariable="loanRequest"/>     <sequenceFlowid='flow2'sourceRef='createLoanRequest'targetRef='approveTask'/>
   
<userTaskid="approveTask"name="Approve request"/>     <sequenceFlowid='flow3'sourceRef='approveTask'targetRef='approveOrDissaprove'/>
   
<serviceTaskid='approveOrDissaprove'name='Store decision'       activiti:expression="${loanRequest.setApproved(approvedByManager)}"/>     <sequenceFlowid='flow4'sourceRef='approveOrDissaprove'targetRef='exclusiveGw'/>
   
<exclusiveGatewayid="exclusiveGw"name="Exclusive Gateway approval"/>     <sequenceFlowid="endFlow1"sourceRef="exclusiveGw"targetRef="theEnd">       <conditionExpressionxsi:type="tFormalExpression">${loanRequest.approved}</conditionExpression>     </sequenceFlow>     <sequenceFlowid="endFlow2"sourceRef="exclusiveGw"targetRef="sendRejectionLetter">       <conditionExpressionxsi:type="tFormalExpression">${!loanRequest.approved}</conditionExpression>     </sequenceFlow>
   
<userTaskid="sendRejectionLetter"name="Send rejection letter"/>     <sequenceFlowid='flow5'sourceRef='sendRejectionLetter'targetRef='theOtherEnd'/>
   
<endEventid='theEnd'/>     <endEventid='theOtherEnd'/>   </process>
</definitions>
       

 

虽然上面的例子很是的简单,可是它却展现了JPA结合Spring和参数化方法表达式的强大优点。这样全部的流程就不须要自定义java代码(固然,除了Spring bean以外)   而且大幅度的加快了流程部署。      

Chapter 11. 历史

历史是一个组件,它能够捕获发生在进程执行中的信息并永久的保存,与运行时数据不一样的是,当流程实例运行完成以后它还会存在于数据库中。  

有5个历史实体对象:    

  • HistoricProcessInstances 包含当前和已经结束的流程实例信息。

  • HistoricVariableInstances 包含最新的流程变量或任务变量。

  • HistoricActivityInstances 包含一个活动(流程上的节点)的执行信息 。

  • HistoricTaskInstances 包含关于当前和过去的(已完成或已删除)任务实例信息。

  • HistoricDetails 包含历史流程实例、活动实例、任务实例的各类信息。

 

因为数据库中保存着历史信息以及正在运行的流程实例信息,就要考虑怎样尽可能少的对运行中的流程实例数据进行访问的方式查询这些表来保证执行的性能。  

稍后, 这个信息体如今Activiti Explorer。同时它也是报告将生成的信息。  

查询历史

在API中, 提供了对这5种实体的查询方法。类HistoryService 提供了如下几种方法       createHistoricProcessInstanceQuery(), createHistoricVariableInstanceQuery(), createHistoricActivityInstanceQuery(),       createHistoricDetailQuery()createHistoricTaskInstanceQuery()。    

下面是一些API中查询历史信息的例子. 这些方法的详细描述能够在 ZAjavadocs中找到, 在包org.activiti.engine.history

HistoricProcessInstanceQuery

流程实例。       

获取流程定义ID是'XXX'、已经结束、花费时间最长(持续时间最长)的10个HistoricProcessInstances          

historyService.createHistoricProcessInstanceQuery()   .finished()   .processDefinitionId("XXX")   .orderByProcessInstanceDuration().desc()   .listPage(0,10);

 

HistoricVariableInstanceQuery

在ID为'xxx'、已经结束的流程实例中查询全部HistoricVariableInstances,并按变量名排序          

historyService.createHistoricVariableInstanceQuery()   .processInstanceId("XXX")   .orderByVariableName.desc()   .list();

 

HistoricActivityInstanceQuery

获取全部已经结束的流程定义ID为’XXX'而且类型是'serviceTask'中的最后一个 HistoricActivityInstance  

historyService.createHistoricActivityInstanceQuery()   .activityType("serviceTask")   .processDefinitionId("XXX")   .finished()   .orderByHistoricActivityInstanceEndTime().desc()   .listPage(0,1);

 

HistoricDetailQuery

下个例子, 获取全部id为123的流程实例中产量的可变动新信息。这个查询只会返回 HistoricVariableUpdates. 注意一些变量名可能包含多个 HistoricVariableUpdate     实体, 每次流程运行时会更新变量。 你能够用 orderByTime (变量被更新的时间) 或者 orderByVariableRevision (运行更新时变量的版本)来排序查询.  

historyService.createHistoricDetailQuery()   .variableUpdates()   .processInstanceId("123")   .orderByVariableName().asc()   .list()

 

这个例子获取全部流程实例ID为123的流程中,提交任务或者启动流程时的form-properties 。 这个查询只会返回 HistoricFormPropertiess 。  

historyService.createHistoricDetailQuery()   .formProperties()   .processInstanceId("123")   .orderByVariableName().asc()   .list()

 

最后这个例子获取全部在执行ID为123的任务时的变量更新。 返回所有在任务中设置的变量(任务局部变量) HistoricVariableUpdates , 不是流程实例变量。

historyService.createHistoricDetailQuery()   .variableUpdates()   .taskId("123")   .orderByVariableName().asc()   .list()   

任务局部变量能够用 TaskService 设置或者使用 DelegateTask, 在TaskListener里设置:  

taskService.setVariableLocal("123","myVariable","Variable value");

 

publicvoid notify(DelegateTask delegateTask){   delegateTask.setVariableLocal("myVariable","Variable value");}

 

HistoricTaskInstanceQuery

获取全部任务中10个花费时间最长(持续时间最长)并已经结束的 HistoricTaskInstances 。          

historyService.createHistoricTaskInstanceQuery()   .finished()   .orderByHistoricTaskInstanceDuration().desc()   .listPage(0,10);

 

获取删除缘由包含"无效",最后分配给用户"kermit"的 HistoricTaskInstances。          

historyService.createHistoricTaskInstanceQuery()   .finished()   .taskDeleteReasonLike("%invalid%")   .taskAssignee("kermit")   .listPage(0,10);

 

历史配置

历史级别能够用编写代码的方法配置, 用枚举类型org.activiti.engine.impl.history.HistoryLevel (或者在5.11以前定义在ProcessEngineConfiguration中的常量 HISTORY_*):

ProcessEngine processEngine =ProcessEngineConfiguration   .createProcessEngineConfigurationFromResourceDefault()   .setHistory(HistoryLevel.AUDIT.getKey())   .buildProcessEngine();       

级别能够在配置文件 activiti.cfg.xml 或者在 spring-context中配置:

<beanid="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">   <propertyname="history"value="audit"/>   ... </bean>

历史信息级别能够配置成如下几种:   

  • none: 忽略全部历史存档。这是流程执行时性能最好的状态,但没有任何历史信息可用。

  • activity: 保存全部流程实例信息和活动实例信息。     在流程实例结束时, 最后一个流程实例中的最新的变量值将赋值给历史变量。 不会保存过程当中的详细信息。

  • audit: 这个是默认值. 它保存全部流程实例信息,      活动信息, 保证全部的变量和提交的表单属性保持同步     这样全部用户交互信息都是可追溯的,能够用来审计。

  • full: 这个是最高级别的历史信息存档,一样也是最慢的。  这个级别存储发生在审核以及全部其它细节的信息, 主要是更新流程变量。

在Activiti 5.11以前, 历史级别都存在数据库中 (表 ACT_GE_PROPERTY,属性名为 historyLevel). 从Activiti 5.11开始, 这个值再也不用,而且从数据库中忽略或者删除掉。 这个配置能够在两次启动间修改, 由于是在启动以前修改的,因此不会抛出异常.

审计目的的历史

配置audit级别之上    。 全部经过     FormService.submitStartFormData(String processDefinitionId, Map<String, String> properties)    and FormService.submitTaskFormData(String taskId, Map<String, String> properties)     方法提交的属性都会被记录。    

表单属性能够经过API查询,以下:

historyService       .createHistoricDetailQuery()       .formProperties()       ...       .list();

上面的例子只有类型为 HistoricFormProperty的详细信息会被查询出来。    

若是你在调用IdentityService.setAuthenticatedUserId(String)提交以前设置了认证用户,那么提交表单的用户将被保存在历史信息中并能够在开始表单中 使用HistoricProcessInstance.getStartUserId()获取,在任务表单中用HistoricActivityInstance.getAssignee()获取。    

Chapter 12. Eclipse Designer

Activiti 同时还有个Eclipse 插件,Activiti Eclipse Designer ,可用于图形化建模、测试、部署 BPMN 2.0的流程。  

Installation

如下安装指南在 Eclipse Indigo. 须注意Eclipse 的Helio 是支持NOT 的.    

打开 Help -> Install New Software. 在以下面板中      , 点击 Add 按钮, 而后填入下列字段:      

  • Name: Activiti BPMN 2.0 designer

  • Location:             http://activiti.org/designer/update/

 

务必选中 "Contact all updates sites.."      , 由于它会检查全部当前安装所须要的插件并能够被Eclipse下载.    

Activiti Designer 编辑器的特性

 

  •           建立Activiti 项目和图形.         

     

  • 当建立一个ACTIVITI表格的时候,Activiti Designer会创建一个BPMN的文档.打开Activiti Diagram 辑器时会出现一个图示的画布和调色板,同一文件在用XML编辑器打开后,会显示BPMN2.0程定义XML元素.所以,同一个文件能同时使用图形图以及BPMN 2.0XML. 注意Activiti的5.9 BPMN扩展还没有做为部署文件来支持程定义. 所以,Activiti Designer 的“建立部署文件”功能生成一个BAR文件,BAR里面有一个含.bpmn内容的.bpmn20.xml文件.您也能够作一个快速的文件重命名.另外,你也可直接用Activiti Designer 打开.bpmn20.xml文件.            

     

  • BPMN 2.0XML文件能够导入Activiti Designer,并建立一个图表, 只需复制BPMN 2.0 XML进你的项目,再用.Activiti Designer打开它. Activiti Designer用BPMN DI的文件建立图表信息.如你有一个BPMN2.0的XML文件而无BPMN DI信息,那也无图可建立.            

     

  • 对于部署 BAR 文件以及可选的 JAR 文件,能够经过右击          package explorer视图中的Activiti项目,而后选择弹出菜单底问的 Create deployment artifacts 选项          由Activiti Designer来建立.更多关于Designer的部署功能,见          部署 一节.            

     

  • 生成单元测试 (在package explorer 视图下右击BPMN2.0的XML文件,而后选择             generate unit test)生成的单元测试是配置为运行有H2数据            库上的Activiti配置.你如今就能够运行单元测试来测试你的流程定义了.            

     

  • Activiti 项目能够作为Maven 项目来生成.可是须要配置依赖关系            须要运行 mvn eclipse:eclipse 这样Maven的依赖会像预期的那样被配置.             注意对于流程设计来说是不须要Maven的依赖.只是在运行单元测试时须要.            

     

 

Activiti Designer 的BPMN 特性

 

  •           支持开始没有事件,启动错误事件、定时器启动事件,最后没有一个错误事件,事件,结束序列流, 并行网关,网关,网关,独家包容事件网关,嵌入式子流程、事件子流程、调用活动,游泳池,车道, 脚本任务、用户任务、服务任务,邮件任务,手动任务,业务规则任务,得到任务, 定时器边界事件、错误边界事件、信号边界事件、定时器捕获事件,信号捕捉事件、信号投掷项目, 和四个Alfresco特定的元素(用户、脚本、邮件任务和开始事件).            

     

  • 能够很快地改变一个任务的类型,只需鼠标悬停在该元素上,而后选择新的任务类型.            

     

  • 能够快速地添加一个新元素,只需鼠标悬停某个元素上,而后选择一个新的元素类型.            

     

  • 支持对Java 服务任务的Java 类、表达式以及代理表达式的配置.此外,也能够配置字段扩展.            

     

  • 支持pools 和 lanes, 由于Activiti能根据不一样的程定义读取不一样的存储池.它使仅用一个存储池看起来意义重大.若是您使用多个池,要注意储存池的图序列流动,它可能会在部署AD引擎的过程当中发生问题.固然,只要你想,你能够添加尽量多的POOL,LANES.            

     

  • 您能够经过填写名称属性添加标签序列流.             你能够本身定位标签的位置保存为BPMN 2 XML DI部分信息            

     

  •             支持子流程事件.            

     

  • 支持扩嵌入式子流程. 你还能够添加一个嵌入式子流程进另外一个嵌入式子流程.            

     

  • 支持任务和嵌入子过程上的定时器边界事件.尽管,定时器边界事件最大的意义在于能够在Activiti Designer 中 的用户任务或嵌入子过程上使用.            

     

  • 支持附加的Activiti 的扩展,如邮件任务、用户任务候选者配置以及脚本任务的配置.            

     

  • 支持Activiti 执行监听和任务监听.能够给执行监听添加字段扩展.         

     

  • 支持顺序流上的条件.

     

 

Activiti Designer 部署特性

    将流程定义和任务表单部署到Activiti引擎并不难.要准备一个BAR 文件,其内含有流程定义的BPMN 2.0的XML 文件, 还能够有任务表单以及能够在Activiti Explorer中浏览的流程图片.在Activiti Designer中建立BAR文件是很是简单 的.完成流程实现后,在package explorer视图下右击你的Activiti项目,而后选择弹出菜单底部的    Create deployment artifacts 选项.   

 

接下来,deployment目录会被建立,其内包含有BAR文件,你的Activiti项目中的Java类的JAR文件也能够在此.   

 

如今就能够使用Activiti Explorer中的部署标签将这个文件部署到Activiti引擎上了,相信你已经准备好了.    

当你的项目中含有Java 类时,部署要稍微麻烦一点.这种状况下,Activiti Designer 中的    Create deployment artifacts 同时会生成包含编译了的类的JAR 文件. 必须将这个JAR文件部署到你的Activiti Tomcat安装路径下的activiti-XXX/WEB -INF/lib路径下.以使得 这些类在Activiti引擎的类路径下是可用的.    

扩展Activiti Designer

你能够对Activiti Designer提供的默认功能进行扩展.这一节介绍有哪些扩展可用,如何使用这些扩展, 并提供了一些使用的例子.当默认的功能不能知足要求,须要额外功能时,或者业务流程建模时有领域 所特有的需求时,扩展Activiti Designer就变得颇有用了.Acitiviti Designer的扩展分为两种不一样的 类型,扩展画板和扩展输出格式.每种形式都有其特有的方式和不一样的专业技术.

 

Note

扩展Activiti Designer 须要有技术知识以及专业的Java 编程的知识.根据你要建立的扩展类型, 你可能也要熟悉Maven、Eclipse 、OSGI、Eclipse扩展以及SWT.

 

定制画板

流程建模时,能够定制显示给用户的画板.画板是那些可在流程图形的画布上拖拽的形状的集合,    它显示在画布右边.正如你在默认画板所看到的.默认的形状被分组到了事件、分支等区隔(称为"抽屉")    中.Activiti Designer 中有两种内置的选择来定制画板中的抽屉和形状:   

  • 将你本身的形状/节点添加到现有的或新的抽屉内.

  • 禁用Activiti Designer提供的任何或全部的默认BPMN 2.0的形状,除了链接工具和选择工具.

要想定制画板,须要建立一个JAR 文件,并将其添加到Activiti Designer 特定的安装目录下    (后面有更多关于 如何操做 ). 这样的    JAR 文件称为 扩展. 经过编写包含在扩展中的类, Activiti Designer就能知道你   想要的定制.为了使其运行, 类必须实现某些接口.你须要将一个集成了这些接口以及一些继承用的基础   类的类库添加到你项目的类路径下.   

你能够在Activiti Designer管理的源码下找到下面列出的代码实例.查看Activiti源码          projects/designer 目录下的examples/money-tasks   目录.   

 

Note

能够使用你所偏心的工具来设置你的项目,而后使用你选择的构建工具来构建JAR文件.    下面的说明,设置假设使用的是Eclipse Helios ,Maven(3.x)做为构建构建工具, 但任何其   它的设置也都能让你建立一样的结果.   

 

扩展的设置 (Eclipse/Maven)

下载并解压 Eclipse          (使用最新版本) 和最新版本(3.x)的Apache Maven. 若是你使用2.x版本          的Maven, 在构建项目时会运行出错, 因此确保你的版本是最新的. 咱们假设你能熟练使用基   本特性和Eclipse的Java编辑器. 由你决定是使用Eclipse 的Maven 特性仍是在命令行上运行    Maven 命令.

在Eclipse里建立一个新项目.这是一个普通的项目类型.在项目的根路径下建立 pom.xml文件用来包含Maven 项目的设置. 同时建立 src/main/javasrc/main/resources文件, 这是Maven 对于Java 源文件和资源的约定. 打开pom.xml 文件并添加下列代码:

 

<project   xmlns="http://maven.apache.org/POM/4.0.0"   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
<modelVersion>4.0.0</modelVersion>
 
<groupId>org.acme</groupId>   <artifactId>money-tasks</artifactId>   <version>1.0.0</version>   <packaging>jar</packaging>   <name>Acme Corporation Money Tasks</name> ... </project>

 

正如你所看到的, 这只是一个定义了项目groupId, artifactIdversion的基本的pom.xml 文件. 咱们将建立 一个定制,其中包含咱们money业务中一个的自定义节点.

经过在pom.xml 文件内引入依赖,就能够将集成的类库添加到你项目的依赖中:

 

<dependencies>   <dependency>     <groupId>org.activiti.designer</groupId>     <artifactId>org.activiti.designer.integration</artifactId>     <version>5.12.0</version><!-- Use the current Activiti Designer version -->     <scope>compile</scope>   </dependency></dependencies> ... <repositories>   <repository>       <id>Activiti</id>       <url>https://maven.alfresco.com/nexus/content/groups/public/</url>    </repository></repositories>

 

最后,在 pom.xml文件中添加对maven-compiler-plugin    的配置,Java 源代码级别最低是1.5 (看下面的代码片断). 这在使用注解时须要。也能够包含让Maven    生成JAR的MANIFEST.MF 文件的命令. 这不是必需的, 但这样你能够使用清单文   件的一个特定的属性做为扩展的名称 (这个名称能够显示在Designer的某处, 主要是为之后若是Designer    中有几个扩展的时候使用).若是你想这样作,将如下代码片断包含进 pom.xml文件:  

 

<build>   <plugins>         <plugin>       <artifactId>maven-compiler-plugin</artifactId>       <configuration>         <source>1.5</source>         <target>1.5</target>         <showDeprecation>true</showDeprecation>         <showWarnings>true</showWarnings>         <optimize>true</optimize>       </configuration>     </plugin>     <plugin>       <groupId>org.apache.maven.plugins</groupId>       <artifactId>maven-jar-plugin</artifactId>       <version>2.3.1</version>       <configuration>         <archive>           <index>true</index>           <manifest>             <addClasspath>false</addClasspath>             <addDefaultImplementationEntries>true</addDefaultImplementationEntries>           </manifest>           <manifestEntries>             <ActivitiDesigner-Extension-Name>Acme Money</ActivitiDesigner-Extension-Name>           </manifestEntries>         </archive>       </configuration>     </plugin>   </plugins></build>

 

扩展的名称是由ActivitiDesigner-Extension-Name 属性描述的. 如今剩下惟一要作的就是让 根据pom.xml件中的指令来设置项目了. 因此打开命令窗口,转到Eclipse 工做空间下你的项目的根文件夹.而后执行如下Maven命令:

 

mvn eclipse:eclipse

 

等待构建完成.刷新项目(使用项目上下文菜单(右击),选择Refresh). 此时,src/main/javasrc/main/resources 文件夹应该 已是Eclipse 项目下的资源文件夹了.

Note

固然你也能够使用 m2eclipse    插件,只要从项目的上下文菜单(右击)就能启用Maven 依赖管理。而后在项目上下文菜   单中选择Maven > Update project configuration    这也能设置源文件夹.

设置就这样.如今就能够开始建立Activiti Designer的定制了!

将扩展应用到Activiti Designer

你可能想要知道怎样才能将扩展添加到Activiti Designer 以使定制被应用.这就是操做步骤:

  • 一旦建立完扩展的JAR(例如,利用Maven执行项目内的mvn的安装程序进行构建),须要   将其放在计算机中安装Activiti Designer 的地方;   

  • 将扩展存储到硬盘中一个能够的地方,记住这个地方.    注意: 该位置必须是Activiti Designer外Eclipse workspace之外的路径,    workspace中存储扩展将致使用户获得一个弹出错误消息,没法扩展;

  • 启动Activiti Designer,选择菜单栏中的 Window >                  Preferences              

  • 在preferences窗口中, 输入 user关键字. 在Java    部分中你应该能看到访问Eclipse 中的User Libraries一项.   

     

     

  • 选择User Libraries,在右侧显示的树形视图中你能够添加类库.你会看到用来向    Activiti Designer添加扩展的默认分组(根据安装的Eclipse,可能也会看到另外其它的分组).   

     

     

  • 选择Activiti Designer Extensions 分组,点击Add JARs...    按钮. 导航到存储扩展的文件夹,选择你想要添加的扩展文件.完成后,这个扩展就做为    Activiti Designer Extensions 分组的一部分显示在preference 窗口内,以下显示.

     

     

  • 点击OK 按钮并关闭preferences对话框.Activiti Designer Extensions    分组自动添加到了你新建立的Activiti项目.在Navigator 或Package Explorer 视图中你能够看   到此用户类库做为一项出如今项目树中。若是工做空间内已经存在Activiti 的项目了,你也会   看到新扩展显示在该分组内.例子以下所示.   

     

     

此时在打开图形的画板中将有来自新扩展的形状(或禁掉的形状,取决于扩展内的定制).若是 已经打开了图形,将其关闭后从新打开看看画板中有什么变化.

向画板添加形状

          根据你的项目的设置,如今你就可以很容易在画板中添加形状了.每一个要添加的形状都是由JAR文件中   的类来描述的.注意这些类并非Activiti 引擎在运行时使用的类.在扩展内描述的那些属性能够设   置给每一个Activiti Designer 内的形状, you can also define the runtime characteristics that should be used by the engine          when a process instance reaches the node in the process. The runtime characteristics can use any          of the options that Activiti supports for regular ServiceTasks.          See 这一节 for more          details.        

一个形状的类就是添加了一些注解的普通Java 类.这个类必须实现 CustomServiceTask 接口,但你没必要本身实现这个接口.只需继承 AbstractCustomServiceTask 这个基类 (目前,必须是直接继承这个类,中间不要有抽象类).在这个类的Javadoc中你能够找到关于它所提供的 默认设置以及什么时候须要重写它所实现了的方法的说明.重写让你作一些诸如为画板和画布上的形状提供 图标(能够是不一样的)、指定你想让节点(活动、事件、分支)拥有的基本形状的事情.

 

/**  * @author John Doe  * @version 1  * @since 1.0.0  */publicclassAcmeMoneyTaskextendsAbstractCustomServiceTask{...}

 

须要实现 getName() 方法来决定画板中节点使用的名称.也能够将节点放进 一个属于它们本身的抽屉,并为其提供一个图标.重写 AbstractCustomServiceTask. 中恰当的方法.若是你想要提供一个图标,确保图标在JAR中src/main/resources 包 内,大小为16x16 像素,格式为JPEG 或PNG.提供的路径是相对于那个文件夹的.

给图形添加属性是经过向此类添加成员变量,并使用 @Property 注解对其 进行注解来完成的,以下:

 

@Property(type =PropertyType.TEXT, displayName ="Account Number")@Help(displayHelpShort ="Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)privateString accountNumber;

 

有几个PropertyType 值你能够使用,在这一节.           作详细介绍.经过将required 属性设置为true能够使字段成为必填项.若是用户不填写该字段,就会   有消息和红色背景显示.

若是要确保类中这些属性是它们在property 窗口出现的顺序,须要指定             @Property 注解的order属性.

正如你所看到了,有个 @Help 注解用来在填写字段时给用户提供一些引导. 也能够将@Help 注解用于类自己——这个信息会显示在呈现给用户的属性表的上方.

下面列出了对MoneyTask. 的详细阐述。添加了一个comments 字段,你会看 到该节点包含了一个图标.

 

/**  * @author John Doe  * @version 1  * @since 1.0.0  */@Runtime(javaDelegateClass ="org.acme.runtime.AcmeMoneyJavaDelegation")@Help(displayHelpShort ="Creates a new account", displayHelpLong ="Creates a new account using the account number specified")publicclassAcmeMoneyTaskextendsAbstractCustomServiceTask{
 
privatestaticfinalString HELP_ACCOUNT_NUMBER_LONG ="Provide a number that is suitable as an account number.";
 
@Property(type =PropertyType.TEXT, displayName ="Account Number", required =true)   @Help(displayHelpShort ="Provide an account number", displayHelpLong = HELP_ACCOUNT_NUMBER_LONG)   privateString accountNumber;
 
@Property(type =PropertyType.MULTILINE_TEXT, displayName ="Comments")   @Help(displayHelpShort ="Provide comments", displayHelpLong ="You can add comments to the node to provide a brief description.")   privateString comments;
 
/*    * (non-Javadoc)    *    * @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #contributeToPaletteDrawer()    */   @Override   publicString contributeToPaletteDrawer(){     return"Acme Corporation";   }
 
@Override   publicString getName(){     return"Money node";   }
 
/*    * (non-Javadoc)    *    * @see org.activiti.designer.integration.servicetask.AbstractCustomServiceTask #getSmallIconPath()    */   @Override   publicString getSmallIconPath(){     return"icons/coins.png";   }}

 

若是拿这个形状来扩展Activiti Designer,画板和对应的节点看起来会如此:

 

 

money任务的属性窗口显示以下.注意对于accountNumber 字段的必填信息.

 

 

在使用过程变量的属性字段建立图表时,用户可输入静态文本或用表达式.如        (e.g. "This little piggy went to ${piggyLocation}"). 通常来讲,这适用于文本字段,用户能够自由用于任何文本.若是但愿用户使用表达式而后你申请运行期的行为给你的CustomServiceTask (使用 @Runtime),保在此种受拖类型中使用Expression 以便表达式在运行时能作正确解析.运行时行为的更多信息,能够在        this section中找到.        

每一个属性右边的按钮给出了对于字段的帮助.点击按钮显示弹出框,以下显示的.

 

 

配置运行时执行自定义服务任务

      你的字段设置及扩展适用于设计者,用户在建模过程当中能够配置服务任务的属性,在大多数状况下,你会在Activiti的执行进程时想要使用用户配置的属性.要作到这一点,当进程达到CustomServiceTask时,必须指示出Activiti的其中的一类实例.     

有一个特殊的注解来指定运行时CustomServiceTask的特色,@Runtime标注.如例:     

@Runtime(javaDelegateClass ="org.acme.runtime.AcmeMoneyJavaDelegation")

你的 CustomServiceTask 将使一个正常的ServiceTask在BPMN建模过程当中输出。 Activiti的使several ways来定义ServiceTask运行时的特征。所以,@Runtime标注能够采起三个属性之一,它可直接匹配Activiti的选项,以下:            

  • javaDelegateClass映射到activiti:class 在BPMN输出。实现JavaDelegate一类的彻底限定类名.                

  • expression映射到activiti:expression 在BPMN输出. 指定表达式的方法被执行,就如Spring Bean的方法. 使用此选项时,您应该您应该