4 Activiti API

4.1 流程引擎的 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.xml 和 activiti-context.xml 文件。 对于 activiti.cfg.xml 文件,流程引擎会使用Activiti 的经典方式构建:
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine()
对于 activiti-context.xml 文件,流程引擎会使用 Spring 方法构建:先创建一个 Spring 的环境,然后通过环境获得流程引擎。

所有服务都是无状态的。这意味着可以在多节点集群环境下运行 ctiviti,每个节点都指向同一个数据库, 不用担心哪个机器实际执行前端的调用。 无论在哪里执行服务都没有问题。

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 的更多信息。

4.2 异常策略

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(…) 就会抛出。

4.3 使用 Activiti 服务

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

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

4.3.1 部署流程

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

在src/test/resources/org/activiti/test 目录下创建一个新的 xml 文件 VacationRequest.bpmn20.xml(如果不使用单元测试模板,你也可以在任何地方创建), 内容如下。注意这一章不会解释例子中使用的 xml 结构。 如果有需要可以先阅读[bpmn 2.0章](../Chapter 7. BPMN 2.0 Introduction 介绍 BPMN 2.0/What is BPMN 什么是 BPMN.md)来了解这些。

<?xml version="1.0" encoding="UTF-8" ?>
<definitions id="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">

  <process id="vacationRequest" name="Vacation request">

    <startEvent id="request" activiti:initiator="employeeName">
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" type="long" value="1" required="true"/>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
        <activiti:formProperty id="vacationMotivation" name="Motivation" type="string" />
      </extensionElements>
    </startEvent>
    <sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest" />

    <userTask id="handleRequest" name="Handle vacation request" >
      <documentation>
        ${employeeName} would like to take ${numberOfDays} day(s) of vacation (Motivation: ${vacationMotivation}).
      </documentation> 
      <extensionElements>
         <activiti:formProperty id="vacationApproved" name="Do you approve this vacation" type="enum" required="true">
          <activiti:value id="true" name="Approve" />
          <activiti:value id="false" name="Reject" />
        </activiti:formProperty>
        <activiti:formProperty id="managerMotivation" name="Motivation" type="string" />
      </extensionElements>
      <potentialOwner>
        <resourceAssignmentExpression>
          <formalExpression>management</formalExpression>
        </resourceAssignmentExpression>
      </potentialOwner>         
    </userTask>
    <sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision" />

    <exclusiveGateway id="requestApprovedDecision" name="Request approved?" />
    <sequenceFlow id="flow3" sourceRef="requestApprovedDecision" targetRef="sendApprovalMail">
      <conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'true'}</conditionExpression>
    </sequenceFlow>

    <task id="sendApprovalMail" name="Send confirmation e-mail" />
    <sequenceFlow id="flow4" sourceRef="sendApprovalMail" targetRef="theEnd1" />
    <endEvent id="theEnd1" />

    <sequenceFlow id="flow5" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
      <conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'false'}</conditionExpression>
    </sequenceFlow>

    <userTask id="adjustVacationRequestTask" name="Adjust vacation request">
      <documentation>
        Your manager has disapproved your vacation request for ${numberOfDays} days.
        Reason: ${managerMotivation}
      </documentation>
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
        <activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" />
        <activiti:formProperty id="resendRequest" name="Resend vacation request to manager?" type="enum" required="true">
          <activiti:value id="true" name="Yes" />
          <activiti:value id="false" name="No" />
        </activiti:formProperty>
      </extensionElements>
      <humanPerformer>
        <resourceAssignmentExpression>
          <formalExpression>${employeeName}</formalExpression>
        </resourceAssignmentExpression>
      </humanPerformer>  
    </userTask>
    <sequenceFlow id="flow6" sourceRef="adjustVacationRequestTask" targetRef="resendRequestDecision" />

    <exclusiveGateway id="resendRequestDecision" name="Resend request?" />
    <sequenceFlow id="flow7" sourceRef="resendRequestDecision" targetRef="handleRequest">
      <conditionExpression xsi:type="tFormalExpression">${resendRequest == 'true'}</conditionExpression>
    </sequenceFlow>

     <sequenceFlow id="flow8" sourceRef="resendRequestDecision" targetRef="theEnd2">
      <conditionExpression xsi:type="tFormalExpression">${resendRequest == 'false'}</conditionExpression>
    </sequenceFlow>
    <endEvent id="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()); 

可以阅读[部署章节](../Chapter 6. Deployment 部署/Business archives 业务文档.md)来了解更多关于部署的信息。

4.3.2 开始流程实例

把流程定义发布到 Activiti 引擎后,我们可以基于它发起新流程实例。 对每个流程定义,都可以有很多流程实例。 流程定义是“蓝图”,流程实例是它的一个运行的执行。

所有与流程运行状态相关的东西都可以通过 RuntimeService 获得。 有很多方法可以启动一个新流程实例。在下面的代码中,我们使用定义在流程定义 xml 中的 key 来启动流程实例。 我们也可以在流程实例启动时添加一些流程变量,因为第一个用户任务的表达式需要这些变量。 流程变量经常会被用到,因为它们赋予来自同一个流程定义的不同流程实例的特别含义。 简单来说,流程变量是区分流程实例的关键。

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(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 instance
Log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());

4.3.3 完成任务

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

// Fetch all tasks for the management group
TaskService 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 = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("managerMotivation", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);

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

4.3.4 挂起,激活一个流程

我们可以挂起一个流程定义。当挂起流程定时时, 就不能创建新流程了(会抛出一个异常)。 可以通过 RepositoryService 挂起一个流程

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

要想重新激活一个流程定义,可以调用repositoryService.activateProcessDefinitionXXX 方法。也可以挂起一个流程实例。挂起时,流程不能继续执行(比如,完成任务会抛出异常), 异步操作(比如定时器)也不会执行。挂起流程实例可以调用
runtimeService.suspendProcessInstance 方法。 激活流程实例可以调用 runtimeService.activateProcessInstanceXXX 方法。

4.3.5 扩展阅读

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

4.4 查询 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();

4.5 变量

每个流程实例需要并且使用数据来执行存在的步骤。在 Activiti,这些数据称为 variables(变量),并存储在数据库中。变量可用于表达式(例如在单独的网关中选择正确的流出序列流),在 java 服务任务调用外部服务(例如提供输入或存储服务调用的结果),等等。

一个流程实例可以有变量(称为 process variables 流程变量),但也可以执行(流程是活动的特定指针)并且用户任务可以有变量。一个流程实例可以拥有任意数量的变量。每个变量存储在 ACT_RU_VARIABLE 数据库表中的一行。

任何 startProcessInstanceXXX 方法都有一个可选的参数来提供变量,当流程实例创建和开始时。例如, RuntimeService:

ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);

在流程执行时可以添加变量。例如(RuntimeService):

void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

注意变量可以设置在本地,对于一个给定的执行(记住一个流程实例由一个执行树组成)。变量只在执行时可见,并且不会高于执行树。当数据不应该传播到流程实例级别,或变量有在流程实例中特定路径的新值(例如当使用并行路径时),这可能是有用的。

变量也可以再次获取,如下所示。注意,类似的方法在 TaskService 存在。这意味着任务跟执行一样,可以使用局部变量,为了任务的持续时间 而 alive (存活)。

Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);

变量 经常使用在 Java delegates, [表达式](Expressions 表达式.md), execution- 或者 tasklisteners ,脚本,等等。在这些结构,当前执行或任务对象是可用的,它可以用于变量设置和/或检索。最简单的方法是:

execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);

execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);

注意,本地变体也可用于上述所有。

历史(和向后兼容的原因),在上面的任何调用,实际上在幕后所有变量将从数据库中获取。这意味着,如果你有10个变量,并通过 getVariable(“myVariable”) 只有一次,在幕后其他9个将获取和缓存。这不是坏事,因为后续调用不会再接触到数据库。例如,当您的流程定义有三个连续的服务任务(因此有一个数据库事务),使用一个调用来获取所有变量在第一个服务任务的时候,这样可能比在每个服务任务分别获取所需的变量要好。注意,这个应用在获取和设置变量时。

当然,当使用大量的变量或者只是当您想要严格控制数据库查询和交互的时候,这是不合适的。Activiti 5.17 以来,新方法介绍了给一个更严格的控制,通过添加一个可选参数的新方法,告诉引擎是否需要在幕后将所有变量获取并缓存:

Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
Object getVariable(String variableName, boolean fetchAllVariables);
void setVariable(String variableName, Object value, boolean fetchAllVariables);

当参数 fetchAllVariables 为 true 时,上述行为将完全一样:当获取或设置一个变量,所有其他变量将获取和缓存。

然而,当值是 false 时,将使用特定的查询,其他变量将不会获取和缓存。只有当前问题的变量的值将缓存为后续使用。

4.6 表达式

Activiti 使用 UEL 处理表达式。UEL 即 Unified Expression Language (统一表达式语言),它是 EE6 规范的一部分(参考 EE6规范)。为了在所有运行环境都支持最新 UEL 的所有功能,我们使用了一个 JUEL 的修改版本。

表达式可以用在很多场景下,比如 Java 服务任务执行监听器任务监听器条件流。 虽然有两重表达式,值表达式和方法表达式,Activiti进行了抽象,所以两者可以同样使用在需
要表达式的场景中。

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

    myVar m y V a r {myBean.myProperty}

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

    printer.print() p r i n t e r . p r i n t ( ) {myBean.addNewOrder(‘orderName’)}
    ${myBean.doSomething(myVar, execution)}

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

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

  • execution:DelegateExecution 提供外出执行的额外信息。
  • task:DelegateTask 提供当前任务的额外信息。注意,只对任务监听器的表达式有效。
  • authenticatedUserId:当前登录的用户id。如果没有用户登录,这个变量就不可用。

想要更多具体的使用方式和例子,参考 [spring 中的表达式](../Chapter 5. Spring integration 集成 Spring/Expressions 表达式.md),Java 服务任务执行监听器任务监听器条件流

4.7 单元测试

业务流程是软件项目的一部分,它也应该和普通的业务流程一样进行测试: 使用单元测试。因为 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 测试看起来像这样。

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 方法获得流程引擎和各种服务。 和 ActivitiTestCase 一样(参考上面章节),使用这个 Rule 也会启用org.activiti.engine.test.Deployment 注解(参考上面章节使用和配置的介绍),它会在 classpath 下查找默认的配置文件。 如果配置文件相同的话,对应的流程引擎会被静态缓存, 就可以用于多个单元测试。

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

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());
  }
}

4.8 调试单元测试

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

假设我们已经在单元测试里设置了一个断点。 Ecilpse 里,在代码左侧双击:
这里写图片描述
现在用调试模式运行单元测试(右击单元测试, 选择“运行为”和“单元测试”),测试会停在我们的断点上, 然后我们就可以监视测试的变量,它们显示在右侧面板里。
这里写图片描述
要监视Activiti的数据,打开“显示”窗口 (如果找不到,打开“窗口”->“显示视图”->“其他”,选择显示。) 并点击(代码已完成)

org.h2.tools.Server.createWebServer(“-web”).start()
这里写图片描述
选择你点击的行,右击。然后选择“显示”(或者直接快捷方式就不用右击了)
这里写图片描述
现在打开一个浏览器,打开 http://localhost:8082 , 输入内存数据库的 JDBC URL(默认为 jdbc:h2:mem:activiti ), 点击连接按钮。
这里写图片描述
你现在可以看到 Activiti 的数据,通过它们可以了解单元测试时如何以及为什么这样运行的。
这里写图片描述

4.9 在 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");

当然,也可以使用其他方式创建流程引擎,可以参考[配置章节](../Chapter 3. Configuration 配置/Creating a ProcessEngine 创建 ProcessEngine.md)中的描述。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值