activiti学习(二十五)——多实例任务节点的跳转(退回、自由跳转功能)

本文介绍在Activiti工作流引擎中实现多实例任务跳转的方法,包括处理父execution、子execution及流程变量的难点,提供了一个参考命令类,演示了在多实例任务间跳转的具体实现。

概述

相比普通任务节点跳转,多实例任务的跳转要考虑更多的因素,主要是因为多实例任务包含了父execution和子execution,子execution又有其对应的task,另外还有控制多实例任务的内置变量。这几个特殊点是处理多实例任务跳转的难点。关于普通节点跳转的处理,请参考前文《activiti学习(二十二)——常规任务节点跳转(退回、自由跳转功能)

 

解决思路

与常规节点跳转相比,多实例任务有几个棘手的地方:

1、多实例任务存在父execution和子execution的情况,执行跳转时,必须删除子execution;

2、并行多实例任务存在多个task,进行跳转时,必须把多个task都删除;

3、因为并行多实例任务创建子execution的判断与流程变量有关,因此跳转时必须清除原来的流程变量;

我们的跳转命令必须解决以上三个问题。

 

具体实现

这里简单写一个命令类供各位参考。

import java.util.List;
import java.util.Map;

import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.impl.ActivitiEventBuilder;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ExecutionEntityManager;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.runtime.AtomicOperation;

public class MultiInstanceJumpCmd implements Command {

	private String taskId;
	private Map<String, Object> variables;
	private String desActivityId;
	private String scActivityId;

	public MultiInstanceJumpCmd(String taskId, Map<String, Object> variables, String scActivityId, String desActivityId) {
		this.taskId = taskId;
		this.variables = variables;
		this.desActivityId = desActivityId;
		this.scActivityId = scActivityId;
	}

	public Object execute(CommandContext commandContext) {
		TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
		TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
		ExecutionEntity parentExecutionEntity = taskEntity.getProcessInstance();
		String processDefinitionId = parentExecutionEntity.getProcessDefinitionId();
		ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration().getDeploymentManager()
				.findDeployedProcessDefinitionById(processDefinitionId);
		ActivityImpl curActivityImpl = processDefinitionEntity.findActivity(scActivityId);
		
		// 设置流程变量
		parentExecutionEntity.setVariables(variables);
		parentExecutionEntity.setExecutions(null);
		parentExecutionEntity.setActivity(curActivityImpl);
		parentExecutionEntity.setEventSource(curActivityImpl);
		parentExecutionEntity.setActive(true);
		// 触发全局事件转发器TASK_COMPLETED事件
		if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
			Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(ActivitiEventBuilder
					.createEntityWithVariablesEvent(ActivitiEventType.TASK_COMPLETED, this, variables, false));
		}

		// 删除任务
		List<TaskEntity> taskList = taskEntityManager.findTasksByProcessInstanceId(parentExecutionEntity.getProcessInstanceId());
		for(TaskEntity taskEntity1 : taskList) {
			taskEntity1.fireEvent(TaskListener.EVENTNAME_COMPLETE);
			taskEntityManager.deleteTask(taskEntity1, TaskEntity.DELETE_REASON_COMPLETED, false);	
		}

		ExecutionEntityManager executionEntityManager = Context.getCommandContext().getExecutionEntityManager();
		List<ExecutionEntity> childExecutionList = executionEntityManager.findChildExecutionsByParentExecutionId(parentExecutionEntity.getId());
		for(ExecutionEntity executionEntityChild : childExecutionList) {
			List<ExecutionEntity> childExecutionList1 = executionEntityManager.findChildExecutionsByParentExecutionId(executionEntityChild.getId());
			for(ExecutionEntity executionEntityChild1 : childExecutionList1) {
				executionEntityChild1.remove();
				Context.getCommandContext().getHistoryManager().recordActivityEnd(executionEntityChild1);
			}
			executionEntityChild.remove();
			Context.getCommandContext().getHistoryManager().recordActivityEnd(executionEntityChild);
		}
		
		commandContext.getIdentityLinkEntityManager().deleteIdentityLinksByProcInstance(parentExecutionEntity.getId());
		ActivityImpl desActivityimpl = processDefinitionEntity.findActivity(desActivityId);	
		
		parentExecutionEntity.removeVariable("nrOfInstances");
		parentExecutionEntity.removeVariable("nrOfActiveInstances");
		parentExecutionEntity.removeVariable("nrOfCompletedInstances");
		parentExecutionEntity.removeVariable("loopCounter");
		
		parentExecutionEntity.setActivity(desActivityimpl);
		parentExecutionEntity.performOperation(AtomicOperation.TRANSITION_CREATE_SCOPE);

		return null;
	}
}

这个类需要为其提供taskId,源节点id、目标节点id。35行获取父execution,36行获取流程定义id。54-58行通过流程定义id获取所有task,并删除之。60-70行删除子execution,之所以会有两个for循环,是因为并行多实例任务的子execution还会再创建子execution作为具体的执行实例。75-78行删除流程变量。81行不能使用普通节点跳转的executionEntity.executeActivity()。这是因为如果目标节点是多实例任务的话,不经过AtomicOperationTransitionCreateScope类,创建的execution会有问题。稍微跟踪一下AtomicOperation.TRANSITION_CREATE_SCOPE对应的AtomicOperationTransitionCreateScope类:

public class AtomicOperationTransitionCreateScope implements AtomicOperation {
  
//......

  public void execute(InterpretableExecution execution) {
    InterpretableExecution propagatingExecution = null;
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
    if (activity.isScope()) {
      propagatingExecution = (InterpretableExecution) execution.createExecution();
      propagatingExecution.setActivity(activity);
      propagatingExecution.setTransition(execution.getTransition());
      execution.setTransition(null);
      execution.setActivity(null);
      execution.setActive(false);
      log.debug("create scope: parent {} continues as execution {}", execution, propagatingExecution);
      propagatingExecution.initialize();

    } else {
      propagatingExecution = execution;
    }

    propagatingExecution.performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_START);
  }
}

对于普通节点,activity.isScope()判断为false,则执行第19行。若目标节点是普通任务节点,那不会有问题。但是目标节点是多实例任务时,会执行9-16行,除了创建一个子execution以外,还让子execution去执行接下来的原子操作。具体情况说起来比较绕口,就以数据库记录来解释方便一点,当通过跳转进入并行多实例任务时,act_ru_execution表如下:

2501是父execution,其id与流程实例id是一致的。12501是上面第9行代码创建出来的execution。12506、12507、12508则是进入并行多实例任务节点后,行为类根据节点配置的属性,创建对应数量的execution,这些execution的父亲是12501。而12506、12507、12508这三个execution,下面都有对应的task,当task完成时,对应的execution会结束。所以如果像普通节点跳转那样调用executionEntity.executeActivity()的话,就不会产生12501这个execution。

 

简单演示

新建流程图multiInstanceJump.bpmn,与普通节点跳转的类似,区别是任务节点全变成了多实例任务。

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="multiInstanceJump" name="MultiInstance Jump" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="usertask1">
      <multiInstanceLoopCharacteristics isSequential="true">
        <loopCardinality>3</loopCardinality>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="usertask2">
      <multiInstanceLoopCharacteristics isSequential="false">
        <loopCardinality>3</loopCardinality>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="usertask3">
      <multiInstanceLoopCharacteristics isSequential="true">
        <loopCardinality>3</loopCardinality>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <userTask id="usertask4" name="usertask4">
      <multiInstanceLoopCharacteristics isSequential="false">
        <loopCardinality>3</loopCardinality>
      </multiInstanceLoopCharacteristics>
    </userTask>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="usertask4"></sequenceFlow>
    <endEvent id="endevent2" name="End"></endEvent>
    <sequenceFlow id="flow6" sourceRef="usertask4" targetRef="endevent2"></sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_multiInstanceJump">
    <bpmndi:BPMNPlane bpmnElement="multiInstanceJump" id="BPMNPlane_multiInstanceJump">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="150.0" y="220.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="230.0" y="210.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="210.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="380.0" y="300.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask4" id="BPMNShape_usertask4">
        <omgdc:Bounds height="55.0" width="105.0" x="230.0" y="300.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent2" id="BPMNShape_endevent2">
        <omgdc:Bounds height="35.0" width="35.0" x="150.0" y="310.0"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="185.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="230.0" y="237.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="335.0" y="237.0"></omgdi:waypoint>
        <omgdi:waypoint x="380.0" y="237.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="432.0" y="265.0"></omgdi:waypoint>
        <omgdi:waypoint x="432.0" y="300.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="380.0" y="327.0"></omgdi:waypoint>
        <omgdi:waypoint x="335.0" y="327.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
        <omgdi:waypoint x="230.0" y="327.0"></omgdi:waypoint>
        <omgdi:waypoint x="185.0" y="327.0"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

接下来的部署和启动操作这里略过,不懂的读者请参考前面的文章,已经写过很多次了。其他配置与《activiti学习(二十二)——常规任务节点跳转(退回、自由跳转功能)》基本一致。

启动后查看act_ru_execution表:

userTask1为串行多实例节点。因此这时execution只有2个。

act_ru_task表:

该task对应2503那个execution。

现在我们准备从userTask1跳转到userTask4:

public void multiInstanceJump() {
	String taskId = "2509";
	Map<String, Object> variables = new HashMap<String, Object>();
	String scActivityId = "usertask1";
	String desActivityId = "usertask4";
	MultiInstanceJumpCmd multiInstanceJumpCmd = new MultiInstanceJumpCmd(taskId, variables, scActivityId, desActivityId);
	ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
	CommandExecutor commandExecutor = service.getCommandExecutor();
	commandExecutor.execute(multiInstanceJumpCmd);
}

 执行后查看结果,act_ru_execution表:

 因为userTask4为并行数3的并行多实例节点,因此会有5个execution。

act_ru_task表:

 有3个任务,跳转正常。

 

小结

考虑到实际流程中,必然存在普通任务节点,也存在多实例任务节点,因此跳转的命令必须能兼容这两种情况。目前简单测试了一下,该命令能正常进行普通任务节点和多实例任务节点的跳转。此次文章纯属抛砖引玉,读者如果有更好的方法,或者发现命令中存在一些错误,也请给予指正。

### Activiti 工作流引擎使用教程与配置指南 Activiti 是一个基于 Java 的开源工作流引擎,支持 BPMN 2.0 标准,并提供了流程建模、执行、监控和管理的完整解决方案。以下是 Activiti 工作流引擎的使用教程与配置指南,涵盖核心概念、初始化配置、流程定义、部署与执行等关键步骤。 #### 1. 环境准备与依赖配置 在 Java 项目中使用 Activiti 前,需引入相关依赖。若使用 Maven 构建项目,可在 `pom.xml` 中添加如下依赖: ```xml <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>7.0.0.Beta2</version> </dependency> ``` 此外,还需配置数据库连接,Activiti 支持多种数据库,如 MySQL、PostgreSQL 和 H2 等。以 MySQL 为例,在 `activiti.cfg.xml` 中配置如下: ```xml <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti" /> <property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver" /> <property name="jdbcUsername" value="root" /> <property name="jdbcPassword" value="password" /> <property name="databaseSchemaUpdate" value="true" /> </bean> ``` #### 2. 初始化流程引擎 Activiti 提供了多种方式创建流程引擎,最常见的是通过 `ProcessEngineConfiguration` 构建: ```java ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); ``` 该方法会自动加载 `activiti.cfg.xml` 配置并初始化流程引擎[^4]。 #### 3. 流程定义与 BPMN 2.0 Activiti 使用 BPMN 2.0(Business Process Model and Notation)作为流程建模语言。流程定义文件(通常为 `.bpmn` 文件)描述了流程节点任务、网关、事件等结构。 例如,一个简单的审批流程 BPMN 文件如下: ```xml <process id="approvalProcess" name="Approval Process"> <startEvent id="startEvent" /> <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitTask" /> <userTask id="submitTask" name="Submit Request" /> <sequenceFlow id="flow2" sourceRef="submitTask" targetRef="approveTask" /> <userTask id="approveTask" name="Approve" /> <sequenceFlow id="flow3" sourceRef="approveTask" targetRef="endEvent" /> <endEvent id="endEvent" /> </process> ``` #### 4. 部署流程定义 在流程引擎初始化后,可将 BPMN 文件部署到引擎中: ```java RepositoryService repositoryService = processEngine.getRepositoryService(); Deployment deployment = repositoryService.createDeployment() .addClasspathResource("processes/approvalProcess.bpmn") .deploy(); ``` 部署成功后,可通过 `ProcessDefinition` 获取流程定义信息: ```java ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .deploymentId(deployment.getId()) .singleResult(); ``` #### 5. 启动流程实例 部署完成后,即可启动流程实例并执行流程: ```java RuntimeService runtimeService = processEngine.getRuntimeService(); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("approvalProcess"); ``` #### 6. 任务查询与完成 用户任务由 `TaskService` 管理。例如,查询当前用户可处理的任务并完成: ```java TaskService taskService = processEngine.getTaskService(); List<Task> tasks = taskService.createTaskQuery().taskAssignee("user1").list(); for (Task task : tasks) { taskService.complete(task.getId()); } ``` #### 7. 流程监控与事件监听 Activiti 提供了丰富的监控接口,可通过 `HistoryService` 查询流程执行历史记录。此外,支持通过监听器(如 `ExecutionListener` 和 `TaskListener`)实现流程节点的事件处理。 例如,添加一个任务创建监听器: ```java public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { System.out.println("Task created: " + delegateTask.getName()); } } ``` 在 BPMN 文件中注册监听器: ```xml <userTask id="submitTask" name="Submit Request"> <extensionElements> <activiti:taskListener event="create" class="com.example.MyTaskListener" /> </extensionElements> </userTask> ``` #### 8. 表单集成与服务调用 Activiti 支持集成表单,如使用 `FormService` 管理任务表单数据。此外,可通过 `JavaDelegate` 接口实现服务任务调用外部系统: ```java public class EmailServiceTask implements JavaDelegate { @Override public void execute(DelegateTask delegateTask) { System.out.println("Sending email..."); } } ``` 在 BPMN 文件中引用服务任务: ```xml <serviceTask id="sendEmail" name="Send Email" activiti:class="com.example.EmailServiceTask" /> ``` #### 9. 扩展性与版本控制 Activiti 支持流程版本管理,每次部署相同流程定义时会自动递增版本号。此外,可通过插件机制扩展流程引擎功能,如自定义任务分配策略、事件总线集成等。 --- ###
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值