Skip to content

Activiti从零学习路线与工作原理总览

Activiti 是 BPMN 工作流引擎。它把 BPMN XML 解析为可执行流程定义,在运行时创建流程实例、Execution、User Task、Variable、Timer/Async Job,并把执行轨迹写入历史表。

它不是一个完整审批产品,也不是业务数据库。表单、领域状态、登录认证、数据权限、组织架构、通知、附件、外部系统一致性和最终商业结果仍要由业务系统设计。

本页是 Activiti 专栏入口。先建立完整心智模型并跑通一个最小流程,再按“BPMN 建模 → 任务 → 网关 → 一致性 → 部署版本 → 生产排查 → 面试”的顺序深入。

Activiti 不同大版本的 Starter、包名、配置属性、动态迁移和云组件差异较大。项目应先锁定正在使用的准确版本,再对照该版本 API 和源码;本专栏重点讲跨版本稳定的执行原理,并在实现差异处明确说明。

学习目标

完成本页后,你应该能够:

  1. 判断业务应该使用工作流、状态机、任务调度还是普通代码。
  2. 区分流程定义、Deployment、流程实例、Execution、Task、Variable 和 History。
  3. 解释 ProcessEngine、RepositoryService、RuntimeService、TaskService、HistoryService 和 Job Executor 的职责。
  4. 解释 BPMN XML 从部署到创建流程实例、用户任务和历史的完整过程。
  5. 写出一个最小 BPMN,并用 Java 部署、启动、查询和完成任务。
  6. 区分 Runtime 表与 History 表,知道待办和已办为什么不能查同一张表。
  7. 理解业务表与流程表为什么必须分离又必须关联。
  8. 知道版本、权限、事务、幂等、异步 Job 和对账为什么是生产必需能力。
  9. 按本专栏路线继续学习每个深层知识点。

一、Activiti到底解决什么问题

假设医疗数据资产发布要经过:申请人提交、部门负责人审批;涉及敏感字段时增加安全审核;跨境数据增加合规审核;全部通过后发布目录;任何拒绝都要留痕。

如果全部写在业务代码中,会逐渐出现:

text
Controller判断角色
Service写大量if/else
数据库只存一个status
定时任务扫描超时审批
消息消费者推进下一状态
前端自己判断按钮
历史记录散落在日志和业务表

Activiti 将“过程如何推进”显式化:

mermaid
flowchart TD
    A["提交数据资产"] --> B["负责人审批"]
    B --> C{"是否包含高风险数据"}
    C -->|"否"| D["发布目录"]
    C -->|"是"| E["安全与合规审核"]
    E --> D
    B -->|"拒绝"| F["记录拒绝并结束"]

引擎负责:

  • 保存流程定义和版本。
  • 创建一次具体流程实例。
  • 生成谁需要办理的 User Task。
  • 根据变量和网关推进 Execution。
  • 持久化等待状态,不占用 Web 线程持续阻塞。
  • 调度 Timer/Async Job。
  • 保存可查询的流程历史。

业务系统负责:

  • 资产、合同、报销单等领域数据。
  • 申请、审批和发布业务状态。
  • 登录认证、组织和数据权限。
  • 审批意见和长期审计。
  • 外部接口、MQ、Outbox、幂等和补偿。
  • 页面表单、附件、通知和报表。

二、哪些场景适合工作流

适合:

  • 多个人或角色参与。
  • 流程跨越分钟、小时、天甚至月。
  • 有待办、已办、转办、委派、超时和留痕。
  • 分支、并行、会签和事件等待比较明显。
  • 流程定义需要版本化并与业务沟通。

商业场景:

  • 合同审批。
  • 采购和报销审批。
  • 医疗数据资产发布和敏感数据授权。
  • 客服工单与 SLA 升级。
  • 账户开通、多部门审核。
  • 内容发布、法务和合规审查。

三、哪些场景不应该上Activiti

场景更适合原因
订单待支付→已支付→已取消领域状态机状态少、转换短、吞吐高,不一定需要人工待办
每天凌晨同步报表调度平台/XXL-JOB核心是定时和分片,不是人工流程
服务A调用B再调用C应用编排/代码/消息短事务调用链不必引入人工工作流
简单一次性校验普通Service代码BPMN和引擎成本高于收益
大规模实时流处理Kafka Streams/Flink等工作流引擎不是高吞吐流计算平台

不要因为“以后可能改流程”就把所有状态变更放进 Activiti。工作流有模型、表、版本、Job、历史和运维成本。

四、工作流、状态机、任务调度和Saga区别

能力工作流状态机任务调度Saga/分布式事务协调
核心对象流程定义、实例、Task、EventState、Event、TransitionJob、Trigger、Executor本地事务步骤和补偿
人工任务通常需自己实现不是重点
长时间等待原生持久化可实现适合定时触发可跨长流程但关注一致性
BPMN可视化通常不是BPMN可用编排模型但语义不同
历史审计引擎提供流程视角自己设计调度执行历史事务/补偿历史
典型场景审批、工单订单状态定时批处理跨服务业务事务

一个系统可以组合使用:订单状态由状态机维护,退款审批由工作流协调,夜间对账由调度平台执行,跨服务退款使用 Saga/消息补偿。

五、Activiti核心对象关系

mermaid
flowchart TD
    A["BPMN XML资源"] --> B["Deployment:一次部署批次"]
    B --> C["ProcessDefinition V1"]
    B --> D["ProcessDefinition V2或其他process"]
    C --> E["ProcessInstance:业务单A的一次运行"]
    C --> F["ProcessInstance:业务单B的一次运行"]
    E --> G["Execution:当前执行路径"]
    G --> H["Task:等待人工处理"]
    G --> I["Variable:流程上下文"]
    G --> J["Job/Event Subscription:异步和等待"]
    E --> K["History:实例、Activity、Task轨迹"]
对象类比准确含义
BPMN Resource源代码可执行流程XML和图形资源
Deployment一次发布包一次资源部署批次,可含多个定义
ProcessDefinition类/模板版本某个process key的具体版本
ProcessInstance对象实例某张业务单的一次流程运行
Execution执行Token/路径引擎当前走到哪里,并行时可有多个
Task人工工作项User Task产生的待办,不等于业务单
Variable执行上下文网关/办理人等必要小数据,不是业务数据库
History执行轨迹已发生实例、节点和任务记录

六、ProcessEngine内部有哪些层

mermaid
flowchart TD
    A["业务应用"] --> B["Repository/Runtime/Task/History等Service门面"]
    B --> C["Command Executor和Command Context"]
    C --> D["Agenda/Atomic Operation推进Execution"]
    C --> E["Entity Manager和Session"]
    E --> F["数据库Runtime/History/Repository表"]
    G["Job Executor"] --> C
    H["BPMN Parser与Deployment Cache"] --> C

内部类名会随 Activiti 大版本变化,但稳定思路是:公开 Service 不是简单 DAO,而是把操作封装为引擎 Command,在统一上下文和事务中维护多个相关实体。

6.1 Service门面

业务代码通过公开 API 使用引擎,不应直接修改 ACT 表。

6.2 Command Context

统一管理一次引擎命令中的持久化、缓存、Agenda 和事务资源。taskService.complete 会推进后续节点,不只是删除 Task。

6.3 Deployment Cache

缓存已经解析的流程模型,避免每次推进都重新读取和解析 BPMN。缓存丢失可从数据库资源重建。

6.4 Job Executor

扫描可执行 Timer/Async Job,抢占、执行、失败重试。它在独立线程和事务中运行,不继承发起 Web 请求的本地事务。

七、六个最常用Service

Service主要职责常见API
RepositoryService部署、查询、挂起流程定义createDeploymentcreateProcessDefinitionQuery
RuntimeService启动和查询运行实例、变量、事件startProcessInstanceByKeycreateProcessInstanceQuery
TaskService待办、认领、完成、委派、评论createTaskQueryclaimcomplete
HistoryService已办、历史实例、Activity和变量createHistoricTaskInstanceQuery
ManagementServiceJob、数据库表和管理能力具体API按版本
IdentityService引擎用户组和authenticated user企业系统常需对接外部IAM

Service 职责可以重叠关联,但不要用 RepositoryService 启动实例,也不要用 Runtime Task 查询已办。

八、一次流程从部署到完成的全过程

mermaid
flowchart TD
    A["RepositoryService部署BPMN"] --> B["资源和ProcessDefinition写入数据库"]
    B --> C["业务提交申请"]
    C --> D["RuntimeService按key/definitionId启动实例"]
    D --> E["创建根Execution并进入Start Event"]
    E --> F["沿Sequence Flow到User Task"]
    F --> G["创建Task和候选Identity Link"]
    G --> H["用户查询/认领待办"]
    H --> I["业务服务校验权限并complete"]
    I --> J["Execution继续并计算Gateway"]
    J --> K{"是否还有等待节点"}
    K -->|"有"| L["创建Task、Job或事件订阅"]
    L --> H
    K -->|"无且到达End"| M["结束运行实例"]
    M --> N["Runtime数据清理,History保留轨迹"]

每一步都可能失败,排查要锁定 businessKey → processInstanceId → processDefinitionId → current Activity/Task/Job,不能只看业务状态。

九、Runtime表和History表

常见前缀:

前缀含义示例
ACT_RE_*Repository,部署和定义ACT_RE_DEPLOYMENTACT_RE_PROCDEF
ACT_RU_*Runtime,当前运行状态ACT_RU_EXECUTIONACT_RU_TASK
ACT_HI_*History,已经发生的轨迹ACT_HI_PROCINSTACT_HI_TASKINST
ACT_GE_*General,通用资源/属性ACT_GE_BYTEARRAY
ACT_ID_*Identity,用户和组是否使用取决于身份集成

版本不同还会有 Job、Event、Form 等其他表。不能仅凭表名前缀直接更新。

为什么Task完成后ACT_RU_TASK查不到

Runtime 表只保存当前活动待办。Task 完成后应从 Runtime 移除,并根据 History Level 写历史。待办查 Runtime,已办查 History 加业务审批记录。

流程结束后Runtime实例为什么消失

流程结束意味着不再需要维持活动 Execution;历史实例保留起止和轨迹。业务表仍需保存最终领域状态。

十、首个可执行BPMN

保存为 src/main/resources/processes/leave_approval.bpmn20.xml

xml
<?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:activiti="http://activiti.org/bpmn"
             targetNamespace="https://example.com/processes">

    <process id="leave_approval"
             name="请假审批"
             isExecutable="true">

        <startEvent id="start" name="提交申请"/>

        <userTask id="managerReview"
                  name="主管审批"
                  activiti:candidateGroups="LEAVE_MANAGER"/>

        <exclusiveGateway id="decisionGateway"
                          name="审批结果"
                          default="toRejected"/>

        <endEvent id="approvedEnd" name="审批通过"/>
        <endEvent id="rejectedEnd" name="审批拒绝"/>

        <sequenceFlow id="f1" sourceRef="start" targetRef="managerReview"/>
        <sequenceFlow id="f2" sourceRef="managerReview" targetRef="decisionGateway"/>

        <sequenceFlow id="toApproved"
                      sourceRef="decisionGateway"
                      targetRef="approvedEnd">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[
                ${approved == true}
            ]]></conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="toRejected"
                      sourceRef="decisionGateway"
                      targetRef="rejectedEnd"/>
    </process>
</definitions>

这里故意将拒绝作为 Default Flow:变量缺失或不为 Boolean true 时不会默认通过。实际业务应区分“明确拒绝”和“变量异常人工处理”,不能把所有异常静默当拒绝。

十一、部署Demo

java
@Service
public class LeaveProcessDeploymentService {

    private final RepositoryService repositoryService;

    public LeaveProcessDeploymentService(
            RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }

    @Transactional
    public ProcessDefinition deploy() {
        Deployment deployment = repositoryService.createDeployment()
                .name("请假审批流程")
                .addClasspathResource(
                        "processes/leave_approval.bpmn20.xml")
                .deploy();

        ProcessDefinition definition = repositoryService
                .createProcessDefinitionQuery()
                .deploymentId(deployment.getId())
                .processDefinitionKey("leave_approval")
                .singleResult();

        if (definition == null) {
            throw new IllegalStateException("部署后未找到流程定义");
        }

        return definition;
    }
}

部署成功只证明资源持久化和模型解析成功,不证明候选组、变量、Listener 和每条运行分支都正确。

十二、启动流程Demo

java
@Service
public class LeaveApprovalService {

    private final RuntimeService runtimeService;
    private final LeaveApplicationRepository applicationRepository;

    public LeaveApprovalService(
            RuntimeService runtimeService,
            LeaveApplicationRepository applicationRepository) {
        this.runtimeService = runtimeService;
        this.applicationRepository = applicationRepository;
    }

    @Transactional
    public String submit(Long applicationId, String operatorId) {
        LeaveApplication application = applicationRepository
                .findByIdForUpdate(applicationId)
                .orElseThrow(() -> new IllegalArgumentException("申请不存在"));

        if (!application.canSubmit(operatorId)) {
            throw new IllegalStateException("状态或权限不允许提交");
        }

        Map<String, Object> variables = new HashMap<>();
        variables.put("applicantId", operatorId);
        variables.put("leaveDays", application.getLeaveDays());

        ProcessInstance instance = runtimeService.startProcessInstanceByKey(
                "leave_approval",
                applicationId.toString(),
                variables);

        application.markApproving(
                instance.getProcessInstanceId(),
                instance.getProcessDefinitionId());

        return instance.getProcessInstanceId();
    }
}

前提是业务表与 Activiti 表真正使用同一事务资源;跨库时不能依赖普通 @Transactional,详见一致性专栏。

十三、查询与完成Task Demo

java
public List<Task> listCandidateTasks(String userId) {
    return taskService.createTaskQuery()
            .taskCandidateUser(userId)
            .active()
            .orderByTaskCreateTime()
            .desc()
            .listPage(0, 20);
}
java
@Transactional
public void decide(
        String taskId,
        String operatorId,
        String requestId,
        boolean approved) {

    Task task = taskService.createTaskQuery()
            .taskId(taskId)
            .active()
            .singleResult();

    if (task == null) {
        // 应结合requestId和审批记录区分幂等重试与错误taskId
        throw new IllegalStateException("任务不存在或已处理");
    }

    permissionService.assertCanComplete(operatorId, task);
    approvalRecordService.assertRequestNotProcessed(requestId);

    Map<String, Object> variables = new HashMap<>();
    variables.put("approved", approved);
    variables.put("lastApproverId", operatorId);

    taskService.complete(taskId, variables);

    approvalRecordService.record(
            requestId,
            task,
            operatorId,
            approved);
}

真正生产实现要使用数据库唯一约束防并发重复,不能只用 assertRequestNotProcessed 的先查询逻辑。完整实现见任务专栏。

十四、业务表和流程表怎样关联

mermaid
flowchart TD
    A["业务主键applicationId"] --> B["作为businessKey启动实例"]
    B --> C["Activiti生成processInstanceId"]
    C --> D["业务表保存processInstanceId和definitionId"]
    D --> E["业务查流程:使用processInstanceId"]
    C --> F["流程查业务:使用businessKey"]

业务表保存:领域字段、业务状态、流程实例关系、审批记录和版本快照。流程变量只保存网关、办理人和执行上下文需要的小字段。

深入见:业务表、流程表与分布式一致性

十五、权限为什么不能交给前端或Activiti单独完成

完成审批至少校验:

mermaid
flowchart TD
    A["用户登录身份"] --> B["API权限"]
    B --> C["Task assignee/candidate/owner关系"]
    C --> D["业务单数据权限"]
    D --> E["当前业务状态和Task节点"]
    E --> F["动作权限:通过/拒绝/转办等"]
    F --> G["并发和幂等校验"]

Activiti 候选组可以表达任务候选关系,但不一定知道企业数据权限。前端按钮隐藏也可以被绕过。最终授权必须在服务端完成。

十六、Job Executor为什么重要

Timer、异步 Service Task 等不会一直占用请求线程。引擎持久化 Job,Job Executor 获取后执行:

mermaid
flowchart TD
    A["Execution到达异步/Timer边界"] --> B["持久化Job并提交"]
    B --> C["Job Executor定期获取到期Job"]
    C --> D["锁定/抢占Job"]
    D --> E["在新线程和新事务中执行"]
    E --> F{"成功还是失败"}
    F -->|"成功"| G["推进Execution并删除Job"]
    F -->|"失败"| H["记录异常、减少重试或进入死信语义"]

不同 Activiti 版本的 Job 表和死信能力不同。生产必须监控最老 Job、失败次数、锁超时和执行吞吐。

十七、版本差异必须先确认

Activiti 5

大量传统项目仍使用。API 思路与表结构是很多教程基础,但旧版依赖、Spring 集成和安全能力要评估。

Activiti 6

引擎内部和 BPMN 模型能力有较大演进,仍是常见单体嵌入式工作流选择。不要假设 Activiti 5 的内部类和迁移脚本完全适用。

Activiti 7

更强调云原生和应用服务/API 集成方向,依赖和使用方式与传统 5/6 教程可能不同。具体社区状态、模块和兼容性应查看项目锁定版本的官方仓库与发布说明。

Flowable

由 Activiti 社区历史分支演进而来,API 和表名有相似之处,但功能和实现已独立发展。Flowable 的 API、配置和文档不能无验证复制到 Activiti。

Camunda 7/8

Camunda 7 与早期 Activiti 有历史渊源,但已独立发展;Camunda 8/Zeebe 是不同架构。流程概念可比较,运行时、API、表和迁移不能互换。

选择时看:项目存量、JDK/Spring Boot兼容、活跃维护、BPMN能力、迁移工具、运维模型、社区和团队经验,不要只看名称相似。

十八、完整学习路线

第1阶段:总览和选型

当前页面必须掌握:对象关系、引擎分层、Service、Runtime/History、首个流程。

再阅读:工作流总览与选型

第2阶段:BPMN可执行建模

阅读:BPMN流程设计与可执行建模

重点:Event、Task、Gateway、Sequence Flow、SubProcess、Call Activity、Multi-instance、Listener、同步/异步和模型测试。

第3阶段:任务生命周期

阅读:任务生命周期、待办已办与并发审批

重点:assignee/candidate、claim、转办、委派、complete事务、变量、权限、幂等、历史和列表性能。

第4阶段:网关与Execution

阅读:网关、Execution与分支汇聚原理

重点:排他、并行、包容、事件网关,Split/Join、变量类型和卡网关排查。

第5阶段:业务一致性

阅读:业务表、流程表与分布式一致性

重点:同库事务、跨库命令、XA/JTA、Outbox/Inbox、事务消息、Saga、幂等、补偿和对账。

第6阶段:部署与版本

阅读:部署、版本、缓存与生产发布

重点:Deployment、ProcessDefinition、版本生成、按 key/ID 启动、旧实例、缓存、挂起、删除、灰度和回退。

第7阶段:商业训练与验收

最后再看:Activiti面试题。面试页用于短回答,不替代原理页。

十九、生产项目必须补的能力

能力最低要求
业务关联businessKey、processInstanceId、definitionId和binding
权限assignee/candidate + 数据权限 + 操作权限
幂等requestId、task动作、Delegate、事件和消费唯一约束
事务明确同库本地事务或跨库最终一致方案
版本新旧定义、旧Delegate和变量兼容矩阵
异步Job重试、超时、死信/人工恢复和幂等
可观测性Task停留、Job失败、流程年龄、对账差异
数据生命周期Runtime容量、History归档、审计保留
修复公共API、补偿、对账和受审计人工处置
安全模型发布权限、表达式白名单、敏感变量治理

二十、最小排查路线

mermaid
flowchart TD
    A["确认环境、tenant、businessKey和时间"] --> B["查processInstanceId和definitionId"]
    B --> C{"Runtime实例是否存在"}
    C -->|"存在"| D["查Task、Execution、Variable、Job和订阅"]
    C -->|"不存在"| E["查HistoricProcessInstance和结束原因"]
    D --> F["下载实例对应版本BPMN"]
    E --> F
    F --> G["对照业务状态、审批记录和Outbox"]
    G --> H["根据事务、权限、变量、网关或异步层定位"]

不要首先重启或改数据库。重启可能让 Job 继续执行并改变现场,直接改表则可能进一步破坏执行树。

二十一、常见误区

误区准确结论后果
Activiti就是画流程图BPMN会被解析为Execution、Task和Job图能看但不会运行/排障
Activiti是完整审批系统表单、权限、业务状态、通知仍需开发越权和数据混乱
ProcessDefinition等于Deployment一次部署可含多个定义版本和查询理解错误
Task等于流程实例一个实例可先后/并行产生多个Task列表和状态模型错误
Running等于有Task可能等待Job、消息、Timer或Join无Task就误判结束
流程变量可存完整业务对象变量不是业务数据库表膨胀、序列化和泄密
complete就是删任务它推进Execution并执行后续节点不理解事务回滚
新定义覆盖旧实例旧实例绑定原definitionId版本兼容事故
@Transactional自动覆盖跨库本地事务只管理自己的资源双写不一致
直接改ACT表最快会绕过引擎关联和缓存流程损坏

二十二、面试标准回答

Activiti是什么

Activiti是BPMN工作流引擎。它把BPMN XML部署成版本化ProcessDefinition,业务启动时创建ProcessInstance和Execution,运行到User Task时生成待办,完成任务后推进Execution、计算网关,并将当前状态保存在Runtime表、轨迹保存在History表。业务系统仍负责业务表、权限、表单、通知和最终一致性。

Activiti一次流程怎样运行

RepositoryService先部署BPMN并生成定义;RuntimeService按key或definitionId启动实例,根Execution从Start Event沿Sequence Flow推进;到User Task后创建Task和Identity Link并持久化等待;用户经服务端权限校验后由TaskService完成任务,Execution继续经过网关、Service Task、异步Job或事件;到End后清理Runtime并保留History。

工作流和状态机怎么选

简单、短时、高吞吐的领域状态转换优先状态机;跨多人多角色、需要待办已办、并行会签、超时等待、历史审计和可视化版本的长流程适合工作流。任务调度解决定时批处理,Saga解决跨服务本地事务与补偿,它们可以与工作流组合而不是互相替代。

Runtime和History表有什么区别

Runtime表保存当前仍活动的Execution、Task、Variable和Job;Task完成或流程结束后相应运行记录会清理。History表按配置保存已发生实例、Activity、Task和变量轨迹。因此待办查Runtime,已办和轨迹查History并结合业务审批记录,不能一直查ACT_RU_TASK。

Activiti能替代业务系统吗

不能。Activiti只协调过程,不是领域事实、组织权限、表单、附件、消息和外部系统的事实源。业务表保存业务状态和数据,流程通过businessKey/processInstanceId关联;跨库和外部调用还要使用本地事务、Outbox、幂等、补偿和对账。

二十三、学习实验与验收

  1. 部署最小 BPMN,查询 deploymentId、definitionId、key 和 version。
  2. 启动两个 businessKey 不同的实例,证明一个定义可创建多个实例。
  3. 查询 Runtime Task、Execution 和 Variable,画出关系。
  4. claim 并 complete 一个 Task,观察 Runtime Task 消失和 Historic Task 生成。
  5. 把 approved 从 Boolean 改成 String,观察网关变量契约问题。
  6. 部署同 key V2,证明旧实例仍绑定 V1,新实例通常使用新激活版本。
  7. 创建异步 Service Task,观察 Job 与原请求事务的区别。
  8. 同库故障注入,证明业务表和 ACT 表共同回滚。
  9. 构造 Outbox 重复发送,使用 Inbox 唯一约束去重。
  10. 根据 businessKey、processInstanceId、definitionId 完成一次流程卡住排查。

验收时必须能回答:

  • Deployment、ProcessDefinition、ProcessInstance、Execution和Task各是什么?
  • 为什么User Task等待不需要占用一个Java线程?
  • complete为什么不只是删除Task?
  • Runtime和History为什么分开?
  • Job Executor为什么是新线程/新事务?
  • businessKey和processInstanceId怎样双向关联?
  • 候选组为什么不能替代业务数据权限?
  • 新版本为什么不自动迁移旧实例?
  • 同库事务和跨库最终一致怎样选择?
  • 卡住时为什么必须先确认实例绑定的definitionId?

关联知识点