通过前面几篇文章的了解,目前我们已经具备足够的知识进行常规userTask的跳转。在“中国式流程”中,驳回、自由跳转等皆是通过这种改方式实现。可以说这也是玩activiti流程比较重要的一环。
activiti本身没有提供驳回和自由跳转的api,可能在老外的思维里,流程是不存在倒着走,或者随意跳转的,必须严格按照连线去走。我们必须通过自己手动去实现这样的功能。但是比较头疼的问题包括:
为了解决上面的问题,必须调整思路,能用现有的一些api,我们要尽可能用上,而不是自己去一步一步折腾。
通过《activiti学习(二十一)——流程虚拟机源码分析(三)——从进入到离开userTask》一文,我们大致了解了如何进入和离开userTask。流程跳转的过程,实际上就是离开当前userTask,然后进入指定的userTask这两步。
我们看下上一篇文章中离开userTask需要做什么:
可以看出我们不能让其执行第5步,否则流程就运行到下一个节点而不是自由跳转了。所以我们不能执行task.complete,而要自己实现前四步,然后再进入指定节点。
幸运的是,进入指定节点,我们不需要做太多事,根据前面的文章看到,我们直接调用execution.performOperation(TRANSITION_CREATE_SCOPE)就差不多了,需要注意的是此前必须调用execution.setActivity()把指定的activitiImpl设置为当前活动,另外调用execution.setTransition(null),就可以了。还有另一个更简便的是调用executionEntity.executeActivity(),设置参数为指定的节点即可。
如果我们要使用实体管理类操作数据库记录的话,因为其插入、修改等等都是到缓存的,所以最好使用命令类。待命令类结束之时,通过回调自动刷新缓存到数据库中。新建命令类CommonJumpCmd.java
public class CommonJumpCmd implements Command {
private String taskId;
private Map variables;
private String desActivityId;
public CommonJumpCmd(String taskId, Map variables, String desActivityId) {
this.taskId = taskId;
this.variables = variables;
this.desActivityId = desActivityId;
}
public Object execute(CommandContext commandContext) {
TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
TaskEntity taskEntity = taskEntityManager.findTaskById(taskId);
ExecutionEntity executionEntity = taskEntity.getExecution();
// 设置流程变量
executionEntity.setVariables(variables);
// 触发执行监听器complete事件
taskEntity.fireEvent(TaskListener.EVENTNAME_COMPLETE);
// 触发全局事件转发器TASK_COMPLETED事件
if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(ActivitiEventBuilder
.createEntityWithVariablesEvent(ActivitiEventType.TASK_COMPLETED, this, variables, false));
}
// 添加act_ru_identitylink表记录。常规流程实例id与执行id相同
if (Authentication.getAuthenticatedUserId() != null && executionEntity.getProcessInstanceId() != null) {
executionEntity.involveUser(Authentication.getAuthenticatedUserId(), IdentityLinkType.PARTICIPANT);
}
// 删除任务
taskEntityManager.deleteTask(taskEntity, TaskEntity.DELETE_REASON_COMPLETED, false);
//触发执行监听器end事件
ScopeImpl scope = (ScopeImpl)executionEntity.getActivity();
List exectionListeners = scope.getExecutionListeners("end");
for(ExecutionListener listener : exectionListeners) {
executionEntity.setEventName("end");
executionEntity.setEventSource(scope);
try {
listener.notify(executionEntity);
} catch (Exception e) {
e.printStackTrace();
}
}
String processDefinitionId = executionEntity.getProcessDefinitionId();
ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration().getDeploymentManager()
.findDeployedProcessDefinitionById(processDefinitionId);
ActivityImpl desActivityimpl = processDefinitionEntity.findActivity(desActivityId);
executionEntity.executeActivity(desActivityimpl);
return null;
}
}
代码不复杂,对应的注释也都写好了。当然为了看起来简洁,这里并没有对一些异常情况进行处理,例如若传入的taskId和desActivityId找不到对应的记录时是否要抛出异常等。
CommonJumpCmd虽然代码不复杂而且逻辑清晰,但是也有几个坑需要重点注意:
接下来我们测试一下,建一个流程图commonJump.bpmn
流程图很简单,就是四个普通的userTask串行。接着我们部署和启动,部署流程文档路径和启动流程id因读者各自不同而不同:
public void deploy() {
RepositoryService repositoryService = pe.getRepositoryService();
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
InputStream inputStream = null;
try {
inputStream = App.class.getClassLoader().getResource("bpmn/jump/commonJump.bpmn").openStream();
deploymentBuilder.addInputStream("commonJump.bpmn", inputStream);
deploymentBuilder.name("commonJumpDeployment");
Deployment deployment = deploymentBuilder.deploy();
System.out.println("部署完成");
} catch (IOException e) {
e.printStackTrace();
}
}
public void startProcessByIdWithVar() {
RuntimeService runtimeService = pe.getRuntimeService();
Map vars = new HashMap();
vars.put("assignee", "张三");
ProcessInstance pi = runtimeService.startProcessInstanceById("commonJump:1:4", vars);
System.out.println("流程定义ID:" + pi.getProcessInstanceId());
System.out.println("流程实例ID:" + pi.getId());
}
此时查看act_ru_execution表:
act_ru_task表:
接下来我们调用自由跳转到userTask3试一试效果:
public void commonJump() {
String taskId = "2505";
Map variables = new HashMap();
variables.put("assignee", "李四");
String desActivityId = "usertask3";
CommonJumpCmd commonJumpCmd = new CommonJumpCmd(taskId, variables, desActivityId);
ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
CommandExecutor commandExecutor = service.getCommandExecutor();
commandExecutor.execute(commonJumpCmd);
}
执行完毕后查看act_ru_execution表,此时已经跳转到userTask3了:
再看act_ru_task表:
上述都显示流程已正常跳转到userTask3上,其他变量表、历史表等可以自行查看,这里不展示了。
之后通过正常task.complete提交流程到userTask4节点上:
public void completeTaskWithVar() {
Map vars = new HashMap();
vars.put("assignee", "王五");
TaskService taskService = pe.getTaskService();
taskService.complete("5002", vars);
System.out.println("完成提交");
}
查看act_ru_execution表和act_ru_task表,执行均无异常:
接下来我们想把流程回退到userTask3:
public void commonJump() {
String taskId = "7502";
Map variables = new HashMap();
variables.put("assignee", "赵六");
String desActivityId = "usertask3";
CommonJumpCmd commonJumpCmd = new CommonJumpCmd(taskId, variables, desActivityId);
ServiceImpl service = (ServiceImpl)pe.getRepositoryService();
CommandExecutor commandExecutor = service.getCommandExecutor();
commandExecutor.execute(commonJumpCmd);
}
查看act_ru_execution表和act_ru_task表,已成功退回userTask3无异常。
本文完成了userTask的常规跳转,然而对于多实例userTask,以及分支节点并不适用。多实例情况和分支情况后面随着流程虚拟机的分析再去实现。常规的流程跳转,已经能满足很多简单流程的使用。另外通过本次实践,我们对如何拆解虚拟机的调用更有经验。
可以说activiti学习写了这么多篇,对不少快速上手百度查答案的人来说,这才算是第一篇稍微有点意义和价值的文章(偷笑)。其实我在写这个系列之前,已经知道如何跳转,但是自己只懂依葫芦画瓢,完全没有实质的理解。通过前面一系列的铺垫和学习,懂得流程虚拟机的走向,发生意外情况(例如上面说的两个坑),懂得如何调试,如何解决问题。虽说activiti流程跳转操纵的关键来来去去就那几个,但是如果不能从原理上去理解,那么如何使工作流引擎与业务逻辑耦合,会是一个大难题。