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

上一篇已经描述了基于开源项目https://doc.iocoder.cn/的flowable的快速开发,创建了一个租户,创建了用户和相应的岗位和角色,然后基于这些角色和bpmn规范快速绘制了流程图,在页面中实现了如下效果。下面讲述下flowable流程创建过程以及流程审批相关的源码分析,掌握这些可以更好的理解flowable的使用和对此进行二次开发。github地址

自动化流程初始创建

登录研发角色账号,进行流程的申请创建,下面是调用审批接口的业务代码。主要利用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可以关闭,方便debug

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

创建流程源码分析

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

初步执行完拦截器后会进行真正的命令调用,命令调用由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.创建开始事件contextStartProcessInstanceBeforeContext 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() = 1String 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决定了审批人的生成 ,没有利用自带的apithis.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);
    

二次开发:flowable审批流程实践与创建流程源码分析相关推荐

  1. [源码学习][知了开发]WebMagic-总体流程源码分析

    写在前面 前一段时间开发[知了]用到了很多技术(可以看我前面的博文http://blog.csdn.net/wsrspirit/article/details/51751568),这段时间抽空把这些整 ...

  2. MyBatis(二)MyBatis基本流程源码分析

    MyBatis体系结构 MyBatis的工作流程 在MyBatis启动的时候我们要去解析配置文件,包括全局配置文件和映射器配置文件,我们会把它们解析成一个Configuration对象,里面会包含各种 ...

  3. android系统加载主题的流程,详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,Phon ...

  4. Java项目(一)--MyBatis实现OA系统项目实战(7)--开发多级审批流程

    开发多级审批流程 请假流程 设计约束 每一个请假单对应一个审批流程. 请假单创建后,按业务规则生成部门经理.总经理审批任务. 审批任务的经办人只能审批自己辖区内的请假申请. 所有审批任务"通 ...

  5. c# cad 二次开发 类库 netload 图层操作、创建图层、删除图层、设置当前图层等

    c# cad 二次开发 类库 netload 图层操作.创建图层.删除图层.设置当前图层等 using Autodesk.AutoCAD.ApplicationServices; using Auto ...

  6. 企业微信SCRM系统部署_企业微信SCRM二次开发_企业微信SCRM系统独立版源码价格

    企业微信SCRM系统部署_企业微信SCRM二次开发_企业微信SCRM系统独立版源码价格 点趣互动是企业微信系统的第三方应用提供厂商,用于管理员工企业微信的内一款系统软件.点趣互动企业微信scrm软件主 ...

  7. Android音频框架之二 用户录音启动流程源码走读

    前言 此篇是对<Android音频框架之一 详解audioPolicy流程及HAL驱动加载>的延续,此系列博文是记录在Android7.1系统即以后版本实现 内录音功能. 当用户使用 Au ...

  8. swoole 启动流程_EasySwoole 服务启动过程以及主体设计流程源码解析

    EasySwoole 服务启动过程以及主体设计流程源码解析 本文主要讲解EasySwoole 服务的启动过程,会通过源码片段讲解主体的设计流程 命令启动 当我们通过php easyswoole sta ...

  9. java快速开发平台 二次开发 外包项目利器 springmvc SS-M后台框架源码 (转载)

    获取[下载地址]   [免费支持更新] 三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体 [新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系 ...

最新文章

  1. JPA基础(一):全面阐释和精彩总结JPA
  2. np.append()
  3. vs2010连接mongodb服务器,X64位
  4. 【Python】疫情卷土重来?Python可视化带你追踪疫情的最新动态
  5. windows 下 logstash 安装启动
  6. iOS App 目录结构
  7. SharePoint中的权限体系
  8. 金立e3t刷android4.4,金立E3T刷机包 Amigo OS 小清新风格 个性化定制功能 稳定流畅...
  9. js使用showModalDialog,弹出一个自适应大小窗口
  10. 劳荣枝潜逃 23 年落网,多亏了它!
  11. python类的构造函数是_python类(class)的构造函数、初始化函数、析构函数
  12. Oracle密码过期ORA-28001
  13. 安装版本swf文件转换其他视频格式工具(例:swf to mp4) ,转换后的视频无水印...
  14. Linux下 C++遍历目录文件
  15. 输出文件名,用i迭代的时候的方法
  16. 密码学基础(五):常见名词解释和密码学标准
  17. 领克发布智能电混技术 全新设计语言概念车亮相
  18. 微信小程序开发入门教程
  19. Keil5各个版本的下载地址
  20. matlab画一只猫,【MATLAB系列04】当一只猫遇见了Matlab

热门文章

  1. Android模拟器 7.1 (64) 以上版本安装xposed框架
  2. 计算机基本网络连接设置 故障排查与处理,电脑无法连接网络?常见故障排除及修复!学起来!...
  3. 拍立淘-图像搜索与识别
  4. ChatGPT for Google :将 ChatGPT 整合到搜索引擎,ChatGPT 和谷歌不必二选一
  5. 七夜救赎是java游戏吗_七夜救赎手游-七夜救赎手机版v1.0预约
  6. 【信号处理】系统与卷积积分
  7. hosts文件修改后 如何生效
  8. 苹果收购英特尔调制解调器业务,角力5G胜算几何?
  9. 湖北黄石电动机保护器公司哪家好?_电动机保护器-上海硕吉电器_新浪博客
  10. 阿波罗进阶版-16-ROS 1