#二次开发:flowable审批流程实践与创建流程源码分析

2,038 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,上一篇已经描述了基于开源项目doc.iocoder.cn/的flowable的快速开发,创建了一个租户,创建了用户和相应的岗位和角色,然后基于这些角色和bpmn规范快速绘制了流程图,在页面中实现了如下效果。下面讲述下flowable流程创建过程以及流程审批相关的源码分析,掌握这些可以更好的理解flowable的使用和对此进行二次开发。github地址

image.png

oa1.gif

自动化流程初始创建

登录研发角色账号,进行流程的申请创建,下面是调用审批接口的业务代码。主要利用flowable的api runtimeService发起流程实例,然后更新业务拓展表。这里有flowable的api文档,感兴趣的也可以参考下。

private String createProcessInstance0(Long userId, ProcessDefinition definition,
                                          Map<String, Object> variables, String businessKey) {
        // 校验流程定义
        if (definition == null) {
            throw exception(PROCESS_DEFINITION_NOT_EXISTS);
        }
        if (definition.isSuspended()) {
            throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
        }
​
        // 创建流程实例
        ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
        // 设置流程名字
        runtimeService.setProcessInstanceName(instance.getId(), definition.getName());
​
        // 补全流程实例的拓展表
        processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId())
                .setFormVariables(variables));
​
        return instance.getId();
    }

runtimeService.startProcessInstanceById 这个是基于流程定义ID和自定义业务Key以及流程过程中使用的变量(会持久化表中)进行创建新的流程实例,debug看下这个方法如何创建一个新的实例. debug的时候AcquireTimerJobsRunnable

@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
        ObjectProvider<FlowableEventListener> listeners,
        BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
    return configuration -> {
        // 注册监听器,例如说 BpmActivitiEventListener
        configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
        // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
        configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
        configuration.setAsyncExecutorActivate(false); // 关掉异步定时调度
    };
}

创建流程源码分析

造好申请数据,debug进入startProcessInstanceById 这个方法的流程,首先,所有的api都会被CommandExecutorImpl被当作命令去执行,经过设初始化好的拦截器。第一是进入设定的拦截器,包括LogInterceptor(打印日志)、SpringTransactionInterceptor (spring事务设置和继承)、CommandContextInterceptor(api执行上下文)、TransactionContextInterceptor、BpmnOverrideContextInterceptor(真正的命令执行调用)。这里也可以自定义拦截器。

image.png

初步执行完拦截器后会进行真正的命令调用,命令调用由agenda来控制流程命令api调用

// 队列命令执行流程操作 很多命令都是调用上面这一套来初始化上下文 这里首先是StartProcessInstanceCmd@21584
protected void executeOperations(CommandContext commandContext) {
    FlowableEngineAgenda agenda = CommandContextUtil.getAgenda(commandContext);
​
    while(!agenda.isEmpty()) {
        Runnable runnable = agenda.getNextOperation();
        this.executeOperation(commandContext, runnable);
    }
​
}

然后,真正进行创建流程实例,调用ProcessInstanceHelper的createAndStartProcessInstanceWithInitialFlowElement来创建实例,如下所示,里面逻辑较多

public ProcessInstance createAndStartProcessInstanceWithInitialFlowElement(ProcessDefinition processDefinition...) {
    CommandContext commandContext = Context.getCommandContext();
    ...
    // 1.创建开始事件context
    StartProcessInstanceBeforeContext startInstanceBeforeContext = new StartProcessInstanceBeforeContext(businessKey..);
    ...
    ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
    ...
    // 2.插入开始节点的流程实例数据 先保存在jvm缓存中 后续在ACT_RU_EXECUTION表中插入
    ExecutionEntity processInstance = processEngineConfiguration.getExecutionEntityManager().createProcessInstanceExecution(startInstanceBeforeContext.getProcessDefinition()...);
    // 3.记录实例历史节点数据 先保存在jvm缓存中 后续在ACT_HI_PROCINST表中插入
    processEngineConfiguration.getHistoryManager().recordProcessInstanceStart(processInstance);
    if (processEngineConfiguration.isLoggingSessionEnabled()) {
        BpmnLoggingSessionUtil.addLoggingData("processStarted", "Started process instance with id " + processInstance.getId(), processInstance);
    }
     // 4.分发事件给自定义的监听器(代码自定义) 实现了AbstractFlowableEngineEventListener类 这里时PROCESS_CREATED 事件
    FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
    boolean eventDispatcherEnabled = eventDispatcher != null && eventDispatcher.isEnabled();
    if (eventDispatcherEnabled) {
        eventDispatcher.dispatchEvent(FlowableEventBuilder.createEntityEvent(FlowableEngineEventType.PROCESS_CREATED, processInstance), processEngineConfiguration.getEngineCfgKey());
    }
​
     // 5.存下创建流程时自定义的变量 会存入对应的流程变量表中ACT_RU_VARIABLE 创建变量时也有变量通知事件
    processInstance.setVariables(this.processDataObjects(process.getDataObjects()));
    if (startInstanceBeforeContext.getVariables() != null) {
        Iterator var24 = startInstanceBeforeContext.getVariables().keySet().iterator();
​
        while(var24.hasNext()) {
            String varName = (String)var24.next();
            processInstance.setVariable(varName, startInstanceBeforeContext.getVariables().get(varName));
        }
    }
​
    // 6.临时变量,不落表保存 
    if (startInstanceBeforeContext.getTransientVariables() != null) {
        Object eventInstance = startInstanceBeforeContext.getTransientVariables().get("eventInstance");
        if (eventInstance instanceof EventInstance) {
            EventInstanceBpmnUtil.handleEventInstanceOutParameters(processInstance, startInstanceBeforeContext.getInitialFlowElement(), (EventInstance)eventInstance);
        }
​
        Iterator var29 = startInstanceBeforeContext.getTransientVariables().keySet().iterator();
​
        while(var29.hasNext()) {
            String varName = (String)var29.next();
            processInstance.setTransientVariable(varName, startInstanceBeforeContext.getTransientVariables().get(varName));
        }
    }
​
    // 7. 分发ENTITY_INITIALIZED事件给FlowableEventBuilder监听器,这个是创建流程的时候设置的监听器
    if (eventDispatcherEnabled) {
        eventDispatcher.dispatchEvent(FlowableEventBuilder.createEntityWithVariablesEvent(FlowableEngineEventType.ENTITY_INITIALIZED, processInstance, startInstanceBeforeContext.getVariables(), false), processEngineConfiguration.getEngineCfgKey());
    }
​
    // 8.创建一个子流程实例,其中父流程实例ID会被记录 为什么要创建一个子流程实例呢?留下一个疑问,后续继续看源码
    ExecutionEntity execution = processEngineConfiguration.getExecutionEntityManager().createChildExecution(processInstance);
    execution.setCurrentFlowElement(startInstanceBeforeContext.getInitialFlowElement());
    processEngineConfiguration.getActivityInstanceEntityManager().recordActivityStart(execution);
    // 9.因为是开始节点,所以会接着寻找下一节点 
    // 会通过  CommandContextUtil.getAgenda(commandContext).planContinueProcessOperation(execution);来继续执行下一节点
    // 简单来说是加入一个执行队列,不断从里面那任务来执行,前面有提到
    if (startProcessInstance) {
        this.startProcessInstance(processInstance, commandContext, startInstanceBeforeContext.getVariables());
    }
    
    if (callbackId != null) {
        this.callCaseInstanceStateChangeCallbacks(commandContext, processInstance, (String)null, "running");
    }
​
    if (processEngineConfiguration.getStartProcessInstanceInterceptor() != null) {
        StartProcessInstanceAfterContext startInstanceAfterContext = new StartProcessInstanceAfterContext(processInstance, execution, startInstanceBeforeContext.getVariables(), startInstanceBeforeContext.getTransientVariables(), startInstanceBeforeContext.getInitialFlowElement(), startInstanceBeforeContext.getProcess(), startInstanceBeforeContext.getProcessDefinition());
        processEngineConfiguration.getStartProcessInstanceInterceptor().afterStartProcessInstance(startInstanceAfterContext);
    }
​
    return processInstance;
}

落表的数据为什么要保存在缓存中,不直接执行sql操作呢?而后续在 CommandContextInterceptor的finally提交?好处在于落表数据提交管理,事务的控制以及代码的耦合性提高

// CommandContextInterceptor 需要插入的缓存数据
protected Map<Class<? extends Entity>, Map<String, Entity>> insertedObjects = new HashMap(); // sqlSession自定义sql缓存map
public void insert(Entity entity, IdGenerator idGenerator) {
    if (entity.getId() == null) {
        String id = idGenerator.getNextId();
        if (this.dbSqlSessionFactory.isUsePrefixId()) {
            id = entity.getIdPrefix() + id;
        }
​
        entity.setId(id);
    }
​
    Class<? extends Entity> clazz = entity.getClass();
    if (!this.insertedObjects.containsKey(clazz)) {
        this.insertedObjects.put(clazz, new LinkedHashMap());
    }
​
    ((Map)this.insertedObjects.get(clazz)).put(entity.getId(), entity);
    this.entityCache.put(entity, false);
    entity.setInserted(true);
}
//  CommandContextInterceptor 拦截器finally提交
   try {
            Context.setCommandContext(commandContext);
            Object var7 = this.next.execute(config, command, commandExecutor);
            return var7;
        } catch (Exception var33) {
            commandContext.exception(var33);
        } finally {
            try {
                if (!contextReused) {
                    commandContext.close();
                }
​
                commandContext.setReused(originalContextReusedState);
            } finally {
                Context.removeCommandContext();
            }
        }

创建完节点的实例后,下一步操作是初始化ContinueProcessOperation,该操作被添加到ageneda队列中,ageneda的从队列中继续循环拿取任务执行。事件开始节点的FlowNodeActivityBehavior行为执行函数执行的Leava操作,离开该节点,继续流程,leave紧接着又调用了aganda的planOperation,添加了TakeOutgoingSequenceFlowsOperation,继续执行该操作 ,进行handleActivityEnd,对开始节点进行一个结束处理,记录到sql缓存中,然后分发节点完成事件。最后执行leaveFlowNode,寻找下一个节点,可以重点看下这个leaveFlowNode方法,这里的情景时离开开始节点,到下一节点,也就是研发审批任务节点

 protected void leaveFlowNode(FlowNode flowNode) {
        LOGGER.debug("Leaving flow node {} with id '{}' by following it's {} outgoing sequenceflow", new Object[]{flowNode.getClass(), flowNode.getId(), flowNode.getOutgoingFlows().size()});
        String defaultSequenceFlowId = null;
        if (flowNode instanceof Activity) {
            defaultSequenceFlowId = ((Activity)flowNode).getDefaultFlow();
        } else if (flowNode instanceof Gateway) {
            defaultSequenceFlowId = ((Gateway)flowNode).getDefaultFlow();
        }
​
        List<SequenceFlow> outgoingSequenceFlows = new ArrayList();
        // 获取节点下一个处理节点
        Iterator var4 = flowNode.getOutgoingFlows().iterator();
​
        while(true) {
            SequenceFlow sequenceFlow;
            label101:
            do {
                while(var4.hasNext()) {
                    sequenceFlow = (SequenceFlow)var4.next();
                    // 是否跳过处理 这里的业务场景是只有一个箭头,即getOutgoingFlows.siza() = 1
                    String skipExpressionString = sequenceFlow.getSkipExpression();
                    if (!SkipExpressionUtil.isSkipExpressionEnabled(skipExpressionString, sequenceFlow.getId(), this.execution, this.commandContext)) {
                        continue label101;
                    }
​
                    if (flowNode.getOutgoingFlows().size() == 1 || SkipExpressionUtil.shouldSkipFlowElement(skipExpressionString, sequenceFlow.getId(), this.execution, this.commandContext)) {
                        outgoingSequenceFlows.add(sequenceFlow);
                    }
                }
​
                if (outgoingSequenceFlows.size() == 0 && this.evaluateConditions && defaultSequenceFlowId != null) {
                    var4 = flowNode.getOutgoingFlows().iterator();
                    // outgoingSequenceFlows 添加下一个节点 一个节点指向多个节点 (多人审批)
                    while(var4.hasNext()) {
                        sequenceFlow = (SequenceFlow)var4.next();
                        if (defaultSequenceFlowId.equals(sequenceFlow.getId())) {
                            outgoingSequenceFlows.add(sequenceFlow);
                            break;
                        }
                    }
                }
​
                if (outgoingSequenceFlows.size() == 0) {
                    .......
                } else {
                    ......
                    // 添加这个节点的执行操作
                    outgoingExecutions.add(this.execution);
                    ExecutionEntity outgoingExecution;
                    // 一个节点指向多个节点 (多人审批)
                    if (outgoingSequenceFlows.size() > 1) {
                       .....
                       outgoingExecutions.add(this.execution);
                    }
​
                    Iterator var15 = outgoingExecutions.iterator();
                    
                    while(var15.hasNext()) {
                        outgoingExecution = (ExecutionEntity)var15.next();
                        // 添加下一步ContinueProcessOperation操作
                        this.agenda.planContinueProcessOperation(outgoingExecution);
                        if (processEngineConfiguration.isLoggingSessionEnabled()) {
                            BpmnLoggingSessionUtil.addSequenceFlowLoggingData("sequenceFlowTake", outgoingExecution);
                        }
                    }
                }
​
                return;
            } while(this.evaluateConditions && (!this.evaluateConditions || !ConditionUtil.hasTrueCondition(sequenceFlow, this.execution) || defaultSequenceFlowId != null && defaultSequenceFlowId.equals(sequenceFlow.getId())));
​
            outgoingSequenceFlows.add(sequenceFlow);
        }
    }

从上面逻辑可以知道开始节点离开后找到了下一个流程节点,也就是那个箭头,然后添加了一个ContinueProcessOperation,该操作又找到了流程节点的目标节点,也就是研发审批任务节点,然后又添加了一个ContinueProcessOperation(无限套娃^_^)审批任务节点,走不同逻辑分支,重要的还是获取到ActivityBehavior行为函数,这里是自定义的BpmUserTaskActivityBehavior类,继承了UserTaskActivityBehavior,这个自定义类主要是根据分配的规则计算任务节点的分配人,终于又回到业务上了,来看下这个TaskActivityBehavior专门为任务型节点指定的行为函数

public void execute(DelegateExecution execution, MigrationContext migrationContext) {
       ......
        if (processEngineConfiguration.isEnableProcessDefinitionInfoCache()) {
          ......
        } else {
            activeTaskName = this.userTask.getName();
         //初始化任务节点
            ......
        }
​
        CreateUserTaskBeforeContext beforeContext = new CreateUserTaskBeforeContext(this.userTask, execution, activeTaskName, activeTaskDescription, activeTaskDueDate, activeTaskPriority, activeTaskCategory, activeTaskFormKey, activeTaskSkipExpression, activeTaskAssignee, activeTaskOwner, activeTaskCandidateUsers, activeTaskCandidateGroups);
        if (processEngineConfiguration.getCreateUserTaskInterceptor() != null) {
            processEngineConfiguration.getCreateUserTaskInterceptor().beforeCreateUserTask(beforeContext);
        }
         //初始化任务节点 属性 包括任务名字 描述 任务过期 任务优先级 任务分类 任务表单
        this.handleName(beforeContext, expressionManager, task, execution);
        this.handleDescription(beforeContext, expressionManager, task, execution);
        this.handleDueDate(beforeContext, expressionManager, task, execution, processEngineConfiguration, activeTaskDueDate);
        this.handlePriority(beforeContext, expressionManager, task, execution, activeTaskPriority);
        this.handleCategory(beforeContext, expressionManager, task, execution);
        this.handleFormKey(beforeContext, expressionManager, task, execution);
        boolean skipUserTask = SkipExpressionUtil.isSkipExpressionEnabled(beforeContext.getSkipExpression(), this.userTask.getId(), execution, commandContext) && SkipExpressionUtil.shouldSkipFlowElement(beforeContext.getSkipExpression(), this.userTask.getId(), execution, commandContext);
    // 插入任务
        TaskHelper.insertTask(task, (ExecutionEntity)execution, !skipUserTask, !skipUserTask && processEngineConfiguration.isEnableEntityLinks());
   // 不跳过任务
        if (!skipUserTask) {
            if (processEngineConfiguration.isLoggingSessionEnabled()) {
                BpmnLoggingSessionUtil.addLoggingData("userTaskCreate", "User task '" + task.getName() + "' created", task, execution);
            }
// 重要-设置任务审批人 这里自定义类BpmUserTaskActivityBehavior决定了审批人的生成 ,没有利用自带的api
            this.handleAssignments(taskService, beforeContext.getAssignee(), beforeContext.getOwner(), beforeContext.getCandidateUsers(), beforeContext.getCandidateGroups(), task, expressionManager, execution, processEngineConfiguration);
          ......
        } else {
            TaskHelper.deleteTask(task, (String)null, false, false, false);
            this.leave(execution);
        }
​
    }
​

创建流程总结

1)设计模式

  • 拦截链,利用定义好的拦截器初始化事务,统一进行事务提交处理,管理不同事务的优先级,管理上下文的初始化和清理等
  • api执行队列,统一进行api调用,动态添加api操纵,解耦不同操作,串起整个流程
  • protected void executeOperations(CommandContext commandContext) {
        FlowableEngineAgenda agenda = CommandContextUtil.getAgenda(commandContext);
    ​
        while(!agenda.isEmpty()) {
            Runnable runnable = agenda.getNextOperation();
            this.executeOperation(commandContext, runnable);
        }
    ​
    }
    ​
    public void executeOperation(CommandContext commandContext, Runnable runnable) {
        if (runnable instanceof AbstractOperation) {
            AbstractOperation operation = (AbstractOperation)runnable;
            if (operation.getExecution() == null || !operation.getExecution().isEnded()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Executing operation {}", operation.getClass());
                }
                this.agendaOperationRunner.executeOperation(commandContext, operation);
            }
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Executing operation {}", runnable.getClass());
            }
    ​
            runnable.run();
        }
    }
    
  • 事件监听模式,节点的生命周期、流程实例的生命周期、变量的生命周期、自定义节点监听事件等都会发布相应事件。提供二次开发接口
  • ActivityBehavior,也就是节点具体行为执行的模式,不同的节点有不同的ActivityBehavior,讲节点的执行动作和执行流程解耦,同时提供二次开发

2)业务流程

  • 流程中不同的节点具有不同的性质,包括任务节点、序列节点、事件节点(开始、结束)、网关节点,针对不同的节点有不同的流程控制
  • 对于一个普通的流程拉说,创建实例是基于bpmn的xmL定义的流程定义,(部署的作用可以更新流程定义)创建流程实例(会创建子流程实例),创建实例父流程中会保存业务key、创建流程时带的变量到表中,然后执行流程过程,期间走到到任务节点会创建节点任务,创建节点任务,分发给审批人,流程的创建至此差不多。

3)注意的问题

  • 变量的创建表达式,注意变量类型要与表达式相对应,以及变量名称要与表达式一致,不能带空格以及一些特殊符号

    // ${var:containsAny(project, 3,4)} 传入的list变量是否包含 3 4
    List<Integer> vars = new ArrayList<>();
    vars.add(createReqVO.getType());
    processInstanceVariables.put("project", vars);
    

\