蓝凌EKP18产品:流程虚拟机(PVM)

如果把流程引擎比作一台计算机,那么:

  • 流程定义 = 程序代码(描述了要做什么)

  • 流程实例 = 运行中的进程(程序的运行态)

  • PVM = CPU + 操作系统(读取"指令"、调度执行、管理状态)

本文将以 EKP 平台的 PVM 层真实源码为基础,带你从概念到实现,彻底理解流程虚拟机的设计思想。


一、PVM 是什么?

1.1 概念定义

PVM(Process Virtual Machine)是流程引擎的核心执行层。它的职责是:

读取流程定义,按图索骥地驱动流程实例从一个节点走到下一个节点,同时管理整个执行过程中的状态变迁。

PVM 层的设计理念是与具体流程引擎解耦------这意味着同一套 PVM 可以驱动不同"方言"的流程定义(BPMN、自定义流程等),只要它们遵循 PVM 定义的接口契约。

1.2 为什么需要虚拟机?

问题 PVM 的解决方案
流程定义格式多样(BPMN、自定义JSON等) PVM 抽象出统一的节点/路由模型,不同格式只需适配
执行逻辑分散在各处,难以维护 所有执行逻辑收敛到原子操作 + 执行路径模型
并发分支、聚合、跳转逻辑复杂 PVM 提供树形执行路径 + 原子操作队列,统一调度
状态管理混乱 PVM 维护明确的状态机(6种状态,严格转换规则)

二、PVM 的核心架构

2.1 整体分层

PVM 在 EKP 中的位置属于 引擎层 的核心,整体架构如下:

复制代码
┌──────────────────────────────────────────────┐
│                 应用层 (Service)               │
│   发起流程、审批、驳回、加签、转办等业务操作      │
├──────────────────────────────────────────────┤
│              引擎层 (Engine + PVM)             │
│  ┌─────────────────────────────────────────┐  │
│  │          PVM(流程虚拟机)                │  │
│  │  ┌─────────┐ ┌──────────┐ ┌─────────┐  │  │
│  │  │ 原子操作 │ │ 执行路径  │ │ 事件系统 │  │  │
│  │  │ 调度器   │ │ 状态管理  │ │ 监听分发 │  │  │
│  │  └─────────┘ └──────────┘ └─────────┘  │  │
│  └─────────────────────────────────────────┘  │
│  ┌─────────────────────────────────────────┐  │
│  │        引擎层实现 (Engine Support)        │  │
│  │   节点行为、路由策略、持久化实现等          │  │
│  └─────────────────────────────────────────┘  │
├──────────────────────────────────────────────┤
│              持久层 (DAO / ORM)                │
│    流程实例表、工作项表、执行路径表等            │
├──────────────────────────────────────────────┤
│              基础层 (Common)                   │
│    Spring容器、事务管理、工具类等               │
└──────────────────────────────────────────────┘

2.2 三大核心组件

PVM 由三个紧密协作的核心组件构成:

组件 对应类/接口 职责
执行路径 Execution / ExecutionWraper 流程的"程序计数器",记录当前走到哪了
原子操作调度器 AtomicOperation + ExecutionWraper 中的队列 将原子操作排队执行,支持异步队列和同步阻塞两种模式
事件系统 EventExecutionWraper / EngineEventMulticaster 在关键节点发布事件,驱动监听器链式执行

这三个组件的关系可以用一句话概括:

执行路径 是载体,原子操作调度器 是引擎,事件系统 是神经系统。


三、执行路径(Execution)------ 流程的"程序计数器"

3.1 概念类比

在操作系统中,每个线程都有一个程序计数器(Program Counter),指向当前正在执行的指令地址。

在 PVM 中,执行路径(Execution) 就是流程的"程序计数器"------它记录了:

  • 当前在哪个活动(节点)

  • 当前关联哪个任务(工作项)

  • 当前处于什么状态

  • 它的父执行路径是谁(树形结构)

  • 它所属的流程实例

3.2 六种状态 ------ PVM 的状态机

Execution 接口定义了流程执行路径的6种核心状态:

复制代码

状态转换图:

复制代码
                    ┌──────────┐
                    │  ACTIVE  │ ← 执行路径被创建 / 收到 signal 信号
                    └────┬─────┘
                         │
            ┌────────────┼────────────┐
            ↓            ↓            ↓
      ┌──────────┐ ┌──────────┐ ┌──────────┐
      │ WAITING  │ │ ENDING   │ │CONCURRENT│
      │(等人审批) │ │(准备流转) │ │(并行分支) │
      └────┬─────┘ └────┬─────┘ └────┬─────┘
           │            │            │
           ↓            ↓            │
      ┌──────────┐ ┌──────────┐      │
      │SUSPENDED │ │  ENDED   │ ←────┘(所有子路径结束后)
      │ (被挂起) │ │ (已结束) │
      └──────────┘ └──────────┘

状态转换的典型场景

  1. ACTIVE → WAITING:自动节点执行完毕,进入人工节点,等待用户审批

  2. ACTIVE → ENDING :调用了 take() 方法,指定了后续路由

  3. ACTIVE → CONCURRENT:遇到并行网关,分裂成多个子执行路径

  4. WAITING → ACTIVE:用户完成审批,执行路径被重新激活

  5. ENDING → ENDED:任务结束事件触发完成,执行路径标记为结束

3.3 树形执行路径模型

这是 PVM 设计中非常精妙的一点:执行路径是树形结构

复制代码
流程主执行路径 (ProcessExecution)
├── 子执行路径-分支A (并发分支1)
│   └── 子执行路径-分支A-1 (嵌套并发)
├── 子执行路径-分支B (并发分支2)
└── 子执行路径-分支C (并发分支3)

这个树形结构天然支持了嵌套并发------并行分支内部还可以继续分裂出子分支,形成了父子层级关系。当子分支全部完成后,父分支才能继续向前流转。


四、原子操作调度器 ------ PVM 的"指令执行单元"

4.1 原子操作接口回顾

复制代码
// AtomicOperation.java
public interface AtomicOperation {
    /** 启动任务操作 */
    String TRANSITION_START_TASK = "TransitionStartTask";
    /** 执行任务操作 */
    String EXECUTE_TASK = "ExecuteTask";
    /** 结束任务操作 */
    String TRANSITION_END_TASK = "TransitionEndTask";
    /** 移到父任务操作 */
    String PROPAGATE_TO_PARENT = "MoveToParentTask";
    /** 广播事件 */
    String EXECUTE_EVENT_LISTENER = "ExecuteEventListener";

    /** 执行操作 */
    void propagate(ExecutionWraper execution);

    /** 是否允许在队列中重复出现 */
    boolean allowRepeat();
}

4.2 双队列调度机制

PVM 的原子操作调度采用双队列模型

双队列的设计意图

队列类型 触发时机 特点
异步队列(COMMON) 正常的流程流转 先进先出,排队执行,允许流程逐步推进
同步队列(SYNC) 同步事件监听器触发 保存当前异步队列 → 立即执行同步操作 → 恢复异步队列

这种设计可以类比操作系统的中断处理:正常流程(异步队列)在执行中,突然来了一个必须立即处理的同步事件(如同步监听器),PVM 就"中断"当前队列,执行完同步操作后再"恢复现场"。

4.3 SyncAtomicOperation ------ 阻塞式原子操作

复制代码
// SyncAtomicOperation.java
public interface SyncAtomicOperation extends AtomicOperation {
    /**
     * 不相同的执行路径范围,则直接触发当前原子操作;
     * 相同的执行路径范围,则阻塞当前执行路径的队列,
     * 触发当前原子操作,执行结束后重置为阻塞前的队列。
     *
     * @return 是否当前执行路径
     */
    boolean isLocalScope();
}

SyncAtomicOperation 拓展了原子操作的能力------它要求立即同步执行,而不是排到队列尾部。典型实现:

  • MoveToChildTask ------ 移动到子任务

  • AbandonTask ------ 废弃任务

  • ExecuteSyncEventListener ------ 执行同步事件监听器


五、事件系统 ------ PVM 的"神经系统"

5.1 事件驱动模型

PVM 采用事件驱动的方式串联整个执行过程。在关键节点上,PVM 会发布事件,然后由事件监听器链式响应。

5.2 事件驱动的原子操作链

PVM 中流程的推进本质上是**"事件 + 原子操作"的链式调用**。以"启动一个任务"为例:

复制代码
TransitionStartTask
    │
    ├─ 1. 设置执行路径状态为 ACTIVE
    ├─ 2. 关联目标任务
    └─ 3. fire(EnterTaskEvent, task, new ActivateTask())
            │
            ├─ 4. 发布 EnterTaskEvent → 触发所有监听器
            └─ 5. 监听器执行完毕 → 调用 ActivateTask
                    │
                    └─ 6. fire(ActivateTaskEvent, task, EXECUTE_TASK)
                            │
                            ├─ 7. 发布 ActivateTaskEvent
                            └─ 8. 调用 ExecuteTask
                                    │
                                    └─ 9. behaviour.execute(execution)
                                            │
                                            ├─ 自动节点:执行业务逻辑 → 结束
                                            └─ 人工节点:创建待办 → WAITING

5.3 任务结束与路由选择

当一个任务完成后,PVM 需要决定下一步往哪走

路由决策矩阵

条件 路由方向 含义
destination == nullparent == null End 流程正常结束
destination == nullparent != null Parent 回到父执行路径(子分支结束)
destination 在当前活动范围内 Next 继续往前走到下一个节点
destination 不在当前活动范围内 Parent 跳回父路径再中转
特权跳转标记命中 Jump 管理员特权跳转

六、核心流程完整追踪

让我们用一个请假审批流程来追踪 PVM 的完整执行链路:

复制代码
[开始] → [提交请假单] → [部门经理审批] → [结束]

6.1 启动流程

复制代码
1. 应用层调用 startProcess()
2. 引擎层创建 ExecutionContextImpl
   ├── 创建流程主执行路径 (ProcessExecution)
   ├── 加载流程定义 (OpenProcessDefinition)
   └── 设置流程参数 (Parameters)
3. 创建 ExecutionWraper 包裹执行路径
4. execution.start()
   └── fire(ProcessStartEvent, null, TransitionStartProcess)
       └── TransitionStartProcess.propagate()
           └── 找到开始节点的默认出口路由
           └── performAtomicOperation(TRANSITION_START_TASK)

6.2 进入"提交请假单"节点

复制代码
5. TransitionStartTask.propagate()
   ├── setFdState(ACTIVE)
   ├── setActivity("提交请假单"节点)
   └── fire(EnterTaskEvent) → ActivateTask
       └── fire(ActivateTaskEvent) → ExecuteTask
           └── behaviour.execute()
               ├── [自动节点] 执行业务逻辑后自动结束
               │   └── performAtomicOperation(TRANSITION_END_TASK)
               └── [人工节点] 创建待办工作项
                   └── setFdState(WAITING)  ← 停下来等人处理

6.3 用户提交请假单

复制代码
6. 应用层调用 completeWorkitem()
7. 引擎层找到对应的执行路径
8. execution.signal()  ← 重新激活
   └── Signal.propagate()
       └── behaviour.signal()  ← 节点收到信号
           └── 执行审批通过逻辑
           └── execution.takeDefaultTransition()  ← 指定出口路由
               └── setFdState(ENDING)
               └── performAtomicOperation(TRANSITION_END_TASK)

6.4 流转到"部门经理审批"

复制代码
9. TransitionEndTask.propagate()
   ├── getNextRoute() → Route.Next ("部门经理审批")
   └── TransitionTake.propagate()
       └── propagateToNext()
           ├── setActivity("部门经理审批"节点)
           └── performAtomicOperation(TRANSITION_START_TASK)
               └── [回到步骤5,循环往复]

6.5 流程结束

复制代码
10. 最后一个节点完成
11. getNextRoute() → Route.End
12. TransitionTake.propagate()
    └── fire(ProcessEndEvent, ..., new DestroyScope())
        └── 清理所有执行路径
        └── 流程实例标记为已完成

七、PVM 的设计精髓

7.1 分层解耦 ------ EngineWire 接口

PVM 层不关心流程定义怎么存、执行路径怎么持久化。它只通过 EngineWire 接口向引擎层"要东西":

这种设计的价值在于:你可以换掉整个引擎层实现(比如从关系数据库换到NoSQL),PVM 层一行代码都不用改。

7.2 原子操作 + 事件 = 可扩展的执行链

每个原子操作在执行前后都可以发布事件,事件监听器可以插入自定义逻辑。这形成了一条可插拔的执行链

复制代码
原子操作A
  ├── 前置事件 → [监听器1] → [监听器2] → ...
  ├── 核心逻辑
  └── 后置事件 → [监听器3] → 下一个原子操作B

7.3 树形执行路径 = 天然的并发模型

并行分支就是创建子执行路径,聚合就是等待子执行路径完成。整个模型清晰且强大:

7.4 Operation 注册表 ------ 简单的工厂模式

通过字符串名称即可获取原子操作实例,原子操作之间通过名称引用(如 TRANSITION_START_TASK),形成了松耦合的操作网络。


八、本课小结

概念 一句话理解
PVM 流程引擎的CPU + 操作系统,读取定义、调度执行、管理状态
执行路径 流程的"程序计数器",树形结构,记录当前位置和状态
六种状态 ACTIVE → WAITING/CONCURRENT/ENDING → ENDED,严格的状态机
双队列调度 异步队列(正常流转)+ 同步队列(事件中断),模拟中断处理
事件系统 在关键节点发布事件,驱动原子操作链式执行
EngineWire PVM与引擎层的解耦契约,更换持久化实现不影响PVM
树形执行路径 天然的并发模型,分支=子路径,聚合=子路径结束
相关推荐
十月的皮皮10 小时前
stm20260720-从新手 C 到量产 STM32 工程:程序设计推导指南
c语言·开发语言·stm32·stm32cubemx·hal库
驰骋工作流10 小时前
流程引擎BPM设计之:流程消息
java·工作流引擎·bpm·jflow·ccflow
dogstarhuang10 小时前
从 0 到 1 搭建可收费的 API 开放平台(实战)
java·架构·api
promiseThen10 小时前
5 分钟上手 Markdown:标题到表格、代码块与简历实战
前端
web66liang10 小时前
webpack4+vue2项目使用 sass-embedded 导致的 DockerfIle 构建失败的问题
前端
Strayer10 小时前
拓扑管网 3D 可视化大屏demo:科技感(地图 + 拓扑 + 3D 空间)
前端·three.js·数据可视化
葬送的代码人生10 小时前
从 Vue 到 React:Tailwind CSS 布局 + BFF 代理实战
前端·react.js·架构
摸鱼老王不带810 小时前
不用后端,纯前端读出"网站眼里你的真实出口 IP"——聊聊 Cloudflare 的 cdn-cgi/trace
前端
程序员黑豆10 小时前
鸿蒙开发入门:Row 和 Column 布局组件详解
前端·harmonyos