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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 本文介绍了工作流框架中的几个流程引擎API的使用场景以及几个工作流的服务类。详细说明了工作流Activiti框架中几个服务类的作用。然后说明了工作流中的异常处理策略。接着介绍了工作流Activiti框架中的查询API以及支持的表达式的应用,最后通过单元测试的示例说明这些API和服务类的使用方式。通过这篇文章,基本上就能够使用代码在项目中熟练的使用工作流框架Activiti进行问题的处理。

流程引擎的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解析流程,而不必通过xml
RuntimeService
            - 负责启动一个流程定义的新实例
                    - 流程定义定义了流程各个节点的结构和行为
                    - 流程实例就是这样一个流程定义的实例
                    - 对每个流程定义来说,同一时间会有很多实例在执行
            - 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,TaskListener
ActivitiObjectNotFoundException:         当请求或操作的对应不存在时抛出
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().这会关闭所有初始化的流程引擎
相关文章
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
52 4
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
138 3
|
3月前
|
缓存 测试技术 API
API的封装步骤流程
API封装流程是一个系统化的过程,旨在将内部功能转化为可复用的接口供外部调用。流程包括明确需求、设计接口、选择技术和工具、编写代码、测试、文档编写及部署维护。具体步骤为确定业务功能、数据来源;设计URL、请求方式、参数及响应格式;选择开发语言、框架和数据库技术;实现数据连接、业务逻辑、错误处理;进行功能、性能测试;编写详细文档;部署并持续维护。通过这些步骤,确保API稳定可靠,提高性能。
|
22天前
|
JSON JavaScript 中间件
Koa框架下的RESTful API设计与实现
在现代 Web 开发中,构建高效、可维护的 API 是至关重要的。Koa 是一个流行的 Node.js Web 应用框架,它具有简洁、灵活和强大的特性,非常适合用于设计和实现 RESTful API。
|
27天前
|
API PHP 数据库
PHP中哪个框架最适合做API?
在数字化时代,API作为软件应用间通信的桥梁至关重要。本文探讨了PHP中适合API开发的主流框架,包括Laravel、Symfony、Lumen、Slim、Yii和Phalcon,分析了它们的特点和优势,帮助开发者选择合适的框架,提高开发效率、保证接口稳定性和安全性。
49 3
|
1月前
|
JavaScript 中间件 API
Node.js进阶:Koa框架下的RESTful API设计与实现
【10月更文挑战第28天】本文介绍了如何在Koa框架下设计与实现RESTful API。首先概述了Koa框架的特点,接着讲解了RESTful API的设计原则,包括无状态和统一接口。最后,通过一个简单的博客系统示例,详细展示了如何使用Koa和koa-router实现常见的CRUD操作,包括获取、创建、更新和删除文章。
46 4
|
2月前
|
存储 数据可视化 API
API接口数据获取流程的细化
本文概述了API的基础知识、获取API访问权限的方法、编写代码调用API的步骤、数据处理与分析技巧以及数据安全与合规的重要性,并提供了社交媒体数据分析、天气预报应用和电商数据分析等API数据获取的应用实例,旨在帮助读者全面了解和实践API接口数据获取的流程。
|
27天前
|
安全 API 网络架构
Python中哪个框架最适合做API?
本文介绍了Python生态系统中几个流行的API框架,包括Flask、FastAPI、Django Rest Framework(DRF)、Falcon和Tornado。每个框架都有其独特的优势和适用场景。Flask轻量灵活,适合小型项目;FastAPI高性能且自动生成文档,适合需要高吞吐量的API;DRF功能强大,适合复杂应用;Falcon高性能低延迟,适合快速API开发;Tornado异步非阻塞,适合高并发场景。文章通过示例代码和优缺点分析,帮助开发者根据项目需求选择合适的框架。
66 0
|
2月前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
98 9
|
1月前
|
存储 监控 安全
API接口数据获取全流程用户指南
本文介绍了从明确需求到数据存储与管理的API接口数据获取全流程。首先,明确业务需求和选择合适的数据源;接着,准备API接口,包括审查文档、申请密钥和安全存储;然后,构建与发送请求,处理响应与数据;最后,进行数据存储与管理,并持续监控与优化,确保数据的安全与合规。通过这些步骤,用户可以高效地获取和管理数据,为数据分析和业务优化提供支持。