写点什么

项目实践之工作流引擎基本文档!Activiti 工作流框架中流程引擎 API 和服务详解

发布于: 2021 年 05 月 28 日
项目实践之工作流引擎基本文档!Activiti工作流框架中流程引擎API和服务详解

流程引擎的 API 和服务

  • 流程引擎 API(ProcessEngine API)是与 Activiti 打交道的最常用方式

  • 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.xml 和 activiti-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文件放到发布包里,也可以把整个流程和相关资源都放在一起          - 可以通过RepositoryService来部署这种发布包.发布一个发布包,意味着把它上传到引擎中,所有流程都会在保存进数据库之前分析解析好          - 从这点来说,系统知道这个发布包的存在,发布包中包含的流程就已经可以启动了      - RepositoryService可以查询引擎中的发布包和流程定义      - RepositoryService暂停或激活发布包,对应全部和特定流程定义.暂停意味着它们不能再执行任何操作了,激活是对应的反向操作      - RepositoryService获得多种资源,例如包含在发布包里的文件,引擎自动生成的流程图      - RepositoryService获得流程定义的pojo版本,可以用来通过java解析流程,而不必通过xmlRuntimeService      - 负责启动一个流程定义的新实例          - 流程定义定义了流程各个节点的结构和行为          - 流程实例就是这样一个流程定义的实例          - 对每个流程定义来说,同一时间会有很多实例在执行      - RuntimeService可以用来获取和保存流程变量,这些数据是特定于某个流程实例的,并会被很多流程中的节点使用      - Runtimeservice可以查询流程实例和执行,执行对应BPMN 2.0中的'token',基本上执行指向流程实例当前在哪里      - RuntimeService可以在流程实例等待外部触发时使用,可以用来继续流程实例.流程实例可以有很多暂停状态,而服务提供了多种方法来'触发'实例, 接受外部触发后,流程实例就会继续向下执行TaskService      - 任务是由系统中真实人员执行的,它是Activiti这类BPMN引擎的核心功能之一, 所有与任务有关的功能都包含在TaskService中      - 在TaskService中,查询分配给用户或组的任务      - 在TaskService中,创建独立运行任务,这些任务与流程实例无关      - 在TaskService中,手工设置任务的执行者,或者这些用户通过何种方式与任务关联      - 在TaskService中,认领并完成一个任务:          - 认领意味着一个人期望成为任务的执行者,即这个用户会完成这个任务          - 完成意味着“做这个任务要求的事情”,通常来说会有很多种处理形式IdentityService      - 可以管理,创建,更新,删除,查询..群组和用户      -  Activiti执行时并没有对用户进行检查.任务可以分配给任何人,但是引擎不会校验系统中是否存在这个用户.这是Activiti引擎也可以使用外部服务:ldap,活动目录...HistoryService      - HistoryService提供了Activiti引擎的所有历史数据      - 在执行流程时,引擎会根据配置保存很多数据:流程实例启动时间,任务的参与者,完成任务的时间,每个流程实例的执行路径..., 这个服务主要通过查询功能来获得这些数据FormService      - FormService是一个可选服务,即使不使用它,Activiti也可以完美运行,不会损失任何功能      - FormService提供了启动表单和任务表单两个概念          - 启动表单会在流程实例启动之前展示给用户          - 任务表单会在用户完成任务时展示      - Activiti支持在BPMN 2.0流程定义中设置这些表单.这个服务以一种简单的方式将数据暴露出来,是可选的,表单也不一定要嵌入到流程定义中ManagementService      - 在使用Activiti的定制环境中基本上不会用到      - ManagementService可以查询数据库的表和表的元数据      - ManagementService提供了查询和管理异步操作的功能      - Activiti的异步操作用途很多:定时器,异步操作,延迟暂停,激活..
复制代码

异常策略

  • Activiti 中的基础异常为 org.activiti.engine.ActivitiException, 一个非检查异常

  • 这个异常可以在任何时候被 API 抛出,特定方法抛出的特定的异常


/** * 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 时,就会抛出异常.taskId 不能为 null,如果传入 null,就会抛出 ActivitiIllegalArgumentException


  • 应该避免过多的异常继承,子类只用于特定的场合

  • 流程引擎和 API 调用的其他场合不使用子类异常,抛出一个普通的 ActivitiExceptions


ActivitiWrongDbException:         当Activiti引擎发现数据库版本号和引擎版本号不一致时抛出ActivitiOptimisticLockingException:   对同一数据进行并发方法并出现乐观锁时抛出ActivitiClassLoadingException:       当无法找到需要加载的类或在加载类时出现了错误-JavaDelegate,TaskListenerActivitiObjectNotFoundException:     当请求或操作的对应不存在时抛出ActivitiIllegalArgumentException:    这个异常表示调用Activiti API时传入了一个非法的参数,可能是引擎配置中的非法值,或提供了一个非法值,或流程定义中使用的非法值ActivitiTaskAlreadyClaimedException:   当任务已经被认领了,再调用taskService.claim(...)就会抛出
复制代码

查询 API

  • 在 Activiti 流程引擎中查询数据有两种方式:

  • 查询 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 规范的一部分.为了在所有运行环境都支持最新 UEL 的所有功能,使用 JUEL 的修改版本

  • 表达式可以用在很多场景下:

  • Java 服务任务

  • 执行监听器

  • 任务监听器

  • 条件流

  • 虽然有两重表达式:值表达式和方法表达式, Activiti 进行了抽象,所以两者可以同样使用在需要表达式的场景中

  • Value expression: 解析为值,默认


${myVar}${myBean.myProperty}
复制代码


所有流程变量都可以使用,所有 spring bean(spring 环境中)也可以使用在表达式中


  • Method expression: 调用一个方法,使用或不使用参数


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


当调用一个无参数的方法时,记得在方法名后添加空的括号,以区分值表达式传递的参数可以是字符串也可以是表达式,它们会被自动解析


  • 这些表达式支持解析原始类型:

  • bean

  • list

  • 数组

  • map

  • 包括比较

  • 在流程实例中,表达式中可以使用一些默认对象:

  • execution: DelegateExecution,提供外出执行的额外信息

  • task: DelegateTask,提供当前任务的额外信息 ,只对任务监听器的表达式有效

  • authenticatedUserId: 当前登录的用户 id.如果没有用户登录,这个变量就不可用

单元测试

  • 业务流程是软件项目的一部分,它也应该和普通的业务流程一样进行测试:使用单元测试

  • 因为 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 注解也可以直接设置资源的位置


public class MyBusinessProcessTest extends ActivitiTestCase {
@Deployment public void 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 方法获得流程引擎和各种服务

  • 使用这个 Rule 也会启用 org.activiti.engine.test.Deployment 注解

  • 它会在 classpath 下查找默认的配置文件,如果配置文件相同的话,对应的流程引擎会被静态缓存,就可以用于多个单元测试


public class MyBusinessProcessTest {
@Rule public ActivitiRule activitiRule = new ActivitiRule();
@Test @Deployment public void 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 的数据库:

  • 在单元测试里设置了一个断点:

  • 用调试模式运行单元测试,右击单元测试,选择[运行为]和[单元测试],测试会停在我们的断点上, 然后我们就可以监视测试的变量,它们显示在调试面板里

  • 要监视 Activiti 的数据,打开[显示]窗口(如果找不到,打开[窗口]-[显示视图]-[其他],选择[显示]并点击[代码已完成],org.h2.tools.Server.createWebServer("-web").start()

  • 选择你点击的行,右击.然后选择[显示]

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

  • 可以看到 Activiti 的数据,通过它们可以了解单元测试时,如何以及为什么这样运行的

Web 中的流程引擎

  • ProcessEngine 是线程安全的,可以在多线程下共享

  • 在 web 应用中, 意味着可以在容器启动时创建流程引擎, 在容器关闭时关闭流程引擎

  • 编写一个 ServletContextListener 在普通的 Servlet 环境下初始化和销毁流程引擎:


public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) { ProcessEngines.init(); }
public void contextDestroyed(ServletContextEvent servletContextEvent) { ProcessEngines.destroy(); }
}
复制代码


contextInitialized 方法会执行 ProcessEngines.init() 这会查找 classpath 下的 activiti.cfg.xml 文件,根据配置文件创建一个 ProcessEngine(比如,多个 jar 中都包含配置文件)如果 classpath 中包含多个配置文件,确认它们有不同的名字


  • 需要使用流程引擎时,可以通过


ProcessEngines.getDefaultProcessEngine()
复制代码



ProcessEngines.getProcessEngine("myName");
复制代码


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

发布于: 2021 年 05 月 28 日阅读数: 29
用户头像

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

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

评论

发布
暂无评论
项目实践之工作流引擎基本文档!Activiti工作流框架中流程引擎API和服务详解