
1. 项目概述从零到一玩转Activiti Modeler如果你正在接触工作流引擎尤其是Activiti那么“如何快速上手并可视化地设计、部署和管理流程”绝对是你绕不开的第一个坎。Activiti Modeler作为其官方提供的在线流程设计器极大地降低了流程定义的门槛让开发者能像画流程图一样直观地完成BPMN 2.0规范的流程建模。但很多朋友在初次使用时往往卡在“设计完流程之后怎么办”这一步——如何将画好的图部署成可运行的流程定义又如何去启动、查询乃至删除这些流程实例这个过程如果仅靠零散的文档很容易让人摸不着头脑。今天我就以一个实际可运行的项目为蓝本带你完整走一遍使用Activiti Modeler进行流程创建、编辑、部署并最终通过代码对流程实例进行全生命周期管理的实战路径。这不是一个简单的功能罗列而是融合了我多次在项目中落地Activiti时踩过的坑、总结的最佳实践以及如何让Modeler与后端服务无缝衔接的深度解析。无论你是刚接触工作流的新手还是想优化现有流程管理方式的开发者这篇内容都能给你提供一套即拿即用的解决方案。2. Activiti Modeler核心定位与项目环境搭建2.1 为什么选择Activiti Modeler在开始动手之前我们得先搞清楚Activiti Modeler在我们的技术栈里扮演什么角色。Activiti本身是一个强大的工作流和业务流程管理BPM引擎但它核心是一个Java库流程定义本身是以XML格式符合BPMN 2.0标准存在的。直接手写或编辑这些XML对于复杂流程来说不仅效率低下而且极易出错。Activiti Modeler正是为了解决这个问题而生。它是一个基于Web的图形化设计器允许你通过拖拽组件如用户任务、网关、服务任务等来绘制流程图并自动生成对应的BPMN 2.0 XML。它的核心价值在于可视化建模降低学习成本业务分析师也能参与初步设计。标准化输出确保生成的流程定义文件严格符合BPMN 2.0规范能被Activiti引擎正确解析。集成性它可以作为一个独立应用运行也可以被嵌入到你自己的Spring Boot等Web应用中。在我们的项目上下文中Modeler主要承担“流程设计器”的职责。我们用它来创建和编辑流程模型Model然后将其“部署”Deploy到Activiti引擎中使之成为可执行的流程定义Process Definition。后续的流程实例Process Instance启动、任务处理、实例删除等操作则通过我们编写的后端服务代码来完成。2.2 项目基础环境与依赖配置为了让整个流程“可运行”我们需要搭建一个包含Activiti Modeler和后端引擎的完整环境。这里我推荐使用Spring Boot来快速集成这是目前最主流的方案。首先创建一个标准的Spring Boot项目。在你的pom.xml文件中需要引入关键依赖dependencies !-- Spring Boot Web 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Activiti Spring Boot Starter 集成 -- dependency groupIdorg.activiti/groupId artifactIdactiviti-spring-boot-starter/artifactId version7.1.0.M6/version !-- 请使用与Spring Boot版本兼容的稳定版 -- /dependency !-- 数据库驱动这里以H2内存数据库为例方便演示 -- dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency !-- 如果需要也可以引入MySQL驱动 -- !-- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency -- /dependencies关键点解析activiti-spring-boot-starter这个starter会自动配置Activiti引擎、Spring Process Engine以及相关的服务如RepositoryService、RuntimeService等。它会自动创建所需的数据库表。数据库选择Activiti需要数据库来存储流程定义、实例、任务等数据。示例中使用H2内存数据库重启后数据会丢失适合演示和测试。生产环境务必换成MySQL、PostgreSQL等持久化数据库。只需更换驱动和配置application.properties中的数据库连接信息即可。接下来在application.properties中做基本配置# 应用端口 server.port8080 # H2数据库配置内存模式 spring.datasource.urljdbc:h2:mem:activiti-db;DB_CLOSE_DELAY-1 spring.datasource.driverClassNameorg.h2.Driver spring.datasource.usernamesa spring.datasource.password # 启动时自动创建表第一次运行需要 spring.datasource.initialization-modealways # Activiti 配置 # 关闭自动部署我们将通过Modeler和API手动部署 spring.activiti.database-schema-updatetrue spring.activiti.check-process-definitionsfalse注意事项spring.activiti.database-schema-updatetrue设置为true引擎启动时会自动检查并创建/更新数据库表结构。生产环境建议设置为false或使用create-drop等更可控的策略。spring.activiti.check-process-definitionsfalse关闭Spring Boot启动时自动扫描processes目录下的BPMN文件进行部署。因为我们打算通过Modeler上传或API部署所以这里关闭。至此一个集成了Activiti引擎的Spring Boot后端环境就准备好了。接下来我们需要把Activiti Modeler集成进来。3. 集成Activiti Modeler进行流程可视化设计3.1 获取与集成Activiti ModelerActiviti Modeler是Activiti项目的一部分。对于Spring Boot项目一个相对简便的方式是直接使用Activiti官方提供的UI模块或者从前端工程入手。这里我介绍一种更直接、更贴近实战的集成方法将Modeler的静态资源引入到我们的项目中。获取静态资源你可以从Activiti的GitHub仓库例如 activiti/activiti-ui 项目的发布版本中找到编译好的Modeler前端资源通常是一个包含HTML、JS、CSS的文件夹。或者更简单的方式是在网络上寻找已经打包好的、可用于独立部署的Activiti Modeler WAR包或静态资源包。放置资源将获取到的Modeler相关静态文件如index.html,editor.html,scripts,styles等目录复制到Spring Boot项目的src/main/resources/static目录下。例如你可以创建一个src/main/resources/static/modeler文件夹把所有文件放进去。配置视图解析可选如果你希望直接通过根路径访问可以简单配置。但更常见的做法是我们编写一个简单的Controller来重定向到Modeler的入口页面。import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; Controller public class ModelerController { GetMapping(/modeler) public String modeler() { // 假设你的Modeler入口页面是 static/modeler/editor.html return redirect:/modeler/editor.html; } }启动Spring Boot应用访问http://localhost:8080/modeler你应该就能看到Activiti Modeler的设计界面了。实操心得网络上找到的Modeler资源版本可能与你使用的Activiti引擎版本不完全匹配可能导致一些高级特性无法使用或出现兼容性问题。最佳实践是使用与你的activiti-spring-boot-starter版本相匹配的Modeler资源。通常大版本号一致如7.x的基础功能是兼容的。Modeler默认可能需要连接一个“模型API”后端来保存模型数据。对于简单集成我们可以先专注于其“设计并导出BPMN XML”的功能。更复杂的集成如模型保存、导入需要部署额外的Activiti REST服务或自行实现对应接口。3.2 使用Modeler创建与编辑第一个流程打开Modeler界面你会看到一个画布和左侧的工具栏。我们来创建一个简单的请假流程作为示例。创建新模型点击“创建模型”或类似按钮输入模型名称如“员工请假流程”和描述。拖拽组件从左侧面板拖一个Start Event开始事件到画布。拖一个User Task用户任务到画布将其命名为“提交请假申请”。双击任务节点可以在右侧属性面板配置“Assignee”受理人这里我们先填一个静态值比如“employee”。在实际项目中这里通常会配置为表达式如${applyUserId}。再拖一个User Task命名为“经理审批”Assignee设为“manager”。拖一个Exclusive Gateway排他网关到画布。拖一个End Event结束事件到画布。连接序列流使用连接线工具将各个节点按顺序连接起来开始事件 - 提交申请 - 排他网关 - 经理审批 - 结束事件。同时从排他网关直接拉一条线到结束事件用于审批驳回等场景。配置网关条件点击从排他网关到“经理审批”的序列流在属性面板中找到“Condition”条件选择“Expression”并输入一个简单的表达式例如${approval true}。这意味着当流程变量approval为true时会走这条审批通过的路径。另一条流向结束的线可以配置为默认流Default flow。保存与导出设计完成后点击保存按钮如果集成了后端保存功能。最关键的一步找到“导出”或“下载”功能选择导出为“BPMN 2.0 XML”格式。这会下载一个.bpmn20.xml或.bpmn文件。用文本编辑器打开这个文件你会看到它就是一个符合BPMN 2.0标准的XML描述文件这就是Activiti引擎能识别的流程定义。避坑指南任务指派在Modeler中直接写死的Assignee如“manager”在演示时可行但在真实业务中极不灵活。正确的做法是使用UEL表达式例如${departmentManager}然后在启动流程或完成任务时通过代码动态设置这个变量的值。网关使用排他网关Exclusive Gateway用于决策多选一并行网关Parallel Gateway用于同时触发多个分支。务必理清业务逻辑选择正确的网关类型。连线上的条件表达式是流程流转的核心逻辑。导出文件务必通过Modeler的“导出”功能获取BPMN XML文件。直接复制画布或截图是没用的引擎只认XML。4. 流程部署将设计图转化为引擎可执行的定义拿到BPMN XML文件后它只是一个静态的模型文件。下一步就是将其“部署”到Activiti引擎中使其成为一个可被启动的“流程定义”。4.1 通过RepositoryService进行部署Activiti提供了RepositoryService来管理流程定义和部署。我们创建一个Spring Bean如一个Controller或Service来调用它。import org.activiti.api.process.model.ProcessDefinition; import org.activiti.api.process.runtime.ProcessRuntime; import org.activiti.engine.RepositoryService; import org.activiti.engine.repository.Deployment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.charset.StandardCharsets; RestController RequestMapping(/api/process) public class ProcessDeployController { Autowired private RepositoryService repositoryService; Autowired private ProcessRuntime processRuntime; // 用于查询已部署的定义 /** * 方式一通过上传BPMN XML文件进行部署 */ PostMapping(/deploy-by-file) public String deployByUpload(RequestParam(file) MultipartFile file) throws IOException { if (file.isEmpty()) { return 部署失败文件为空; } String fileName file.getOriginalFilename(); // 进行简单的文件类型校验 if (fileName ! null !(fileName.endsWith(.bpmn) || fileName.endsWith(.bpmn20.xml))) { return 部署失败仅支持.bpmn或.bpmn20.xml文件; } Deployment deployment repositoryService.createDeployment() .addBytes(fileName, file.getBytes()) // 使用文件字节流 .name(部署自上传文件 fileName) .deploy(); // 执行部署 return 部署成功部署ID: deployment.getId() , 部署名称: deployment.getName(); } /** * 方式二从项目资源路径下读取固定文件进行部署适合预定义流程 */ PostMapping(/deploy-predefined) public String deployPredefined() throws IOException { Resource resource new ClassPathResource(processes/leave-application.bpmn20.xml); if (!resource.exists()) { return 部署失败资源文件不存在; } String bpmnXmlContent new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); Deployment deployment repositoryService.createDeployment() .addString(leave-application.bpmn20.xml, bpmnXmlContent) // 使用字符串内容 .name(员工请假流程预部署) .deploy(); return 预定义流程部署成功部署ID: deployment.getId(); } }关键代码解析repositoryService.createDeployment()创建一个部署构建器。.addBytes()/.addString()添加部署资源。你可以添加文件字节流也可以直接添加XML字符串。支持同时添加多个资源如流程主图、用户任务表单等。.name()为本次部署设置一个名称便于管理。.deploy()执行部署操作。这是一个同步方法执行成功后流程定义就已经存入数据库并可以被查询和启动了。部署成功后你可以通过repositoryService.createProcessDefinitionQuery().list()来查询所有已部署的流程定义验证部署是否成功。4.2 部署结果管理与版本控制每次执行deploy()只要流程的key在BPMN XML文件的process标签的id属性相同Activiti就会将其视为同一流程的不同版本。引擎会自动为这个流程定义生成一个新的版本号从1开始递增并停用旧版本。// 查询所有流程定义 ListProcessDefinition definitions processRuntime.processDefinitions(Pageable.of(0, 10)).getContent(); for (ProcessDefinition pd : definitions) { System.out.println(ID: pd.getId() , Key: pd.getKey() , Name: pd.getName() , Version: pd.getVersion()); }这个特性对于流程的迭代更新非常有用。当你修复了一个流程错误或优化了审批节点后只需重新部署新的BPMN文件新发起的流程实例就会自动使用最新版本而正在运行的老版本实例则不受影响继续按原定义执行。注意事项流程定义KeyBPMN XML中process idmyProcess ...的id属性就是流程定义的Key。它是流程的唯一业务标识版本更替时Key不变。流程定义ID部署后Activiti会生成一个全局唯一的ID格式通常为{key}:{version}:{随机数}。在启动流程实例时既可以使用Key启动最新版本也可以使用这个具体的ID启动指定版本。5. 流程实例的启动、查询与删除实战流程定义部署好后就相当于在引擎里“注册”了一个流程模板。真正的业务流程是从启动一个“流程实例”开始的。5.1 启动流程实例我们使用RuntimeService来启动和管理流程实例。import org.activiti.engine.RuntimeService; import org.activiti.engine.runtime.ProcessInstance; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/instance) public class ProcessInstanceController { Autowired private RuntimeService runtimeService; /** * 启动一个流程实例 * param processDefinitionKey 流程定义Key * param businessKey 业务唯一标识例如请假单号 * return 启动的流程实例信息 */ PostMapping(/start) public MapString, Object startInstance(RequestParam String processDefinitionKey, RequestParam(required false) String businessKey) { // 准备流程变量 MapString, Object variables new HashMap(); variables.put(applyUserId, zhangsan); // 申请人ID variables.put(departmentManager, lisi); // 部门经理ID用于任务分配 variables.put(days, 3); // 请假天数 variables.put(approval, null); // 审批结果初始为null由审批任务设置 // 启动流程实例 ProcessInstance instance; if (businessKey ! null !businessKey.trim().isEmpty()) { // 关联业务Key启动 instance runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables); } else { // 不关联业务Key启动 instance runtimeService.startProcessInstanceByKey(processDefinitionKey, variables); } MapString, Object result new HashMap(); result.put(success, true); result.put(processInstanceId, instance.getId()); result.put(processDefinitionId, instance.getProcessDefinitionId()); result.put(businessKey, instance.getBusinessKey()); result.put(activityId, instance.getActivityId()); // 当前活动节点ID return result; } }关键点解析runtimeService.startProcessInstanceByKey()这是最常用的启动方式使用流程定义Key来启动最新版本的流程定义。流程变量Variables在启动时传入的variablesMap至关重要。这些变量在整个流程实例生命周期内都有效可以用于任务分配在BPMN中User Task的Assignee配置为${departmentManager}引擎在创建任务时会用这里传入的“lisi”去赋值。网关条件判断如前文所述排他网关的条件${approval true}其中的approval变量就需要在审批任务完成后被设置。业务数据传递如days请假天数可以在后续任务或监听器中读取。业务键Business Key这是一个非常实用的字段用于将Activiti流程实例与你的业务实体如请假单、订单关联起来。例如你可以把请假单的数据库ID作为Business Key传入这样以后就可以通过这个Key快速找到对应的流程实例。5.2 查询与监控流程实例流程实例启动后我们需要能够查询它们的状态、当前节点等信息。/** * 根据业务Key查询流程实例 */ GetMapping(/query-by-businesskey) public ListMapString, Object queryByBusinessKey(RequestParam String businessKey) { ListProcessInstance instances runtimeService.createProcessInstanceQuery() .processInstanceBusinessKey(businessKey) .list(); return instances.stream().map(instance - { MapString, Object info new HashMap(); info.put(id, instance.getId()); info.put(businessKey, instance.getBusinessKey()); info.put(definitionId, instance.getProcessDefinitionId()); info.put(activityId, instance.getActivityId()); // 当前停留的节点ID info.put(suspended, instance.isSuspended()); // 是否被挂起 return info; }).collect(Collectors.toList()); } /** * 查询某个用户待办的任务 */ GetMapping(/my-tasks) public ListMapString, Object getMyTasks(RequestParam String userId) { ListTask tasks taskService.createTaskQuery() .taskAssignee(userId) // 查找指派给该用户的任务 .orderByTaskCreateTime().desc() // 按创建时间倒序 .list(); return tasks.stream().map(task - { MapString, Object taskInfo new HashMap(); taskInfo.put(taskId, task.getId()); taskInfo.put(taskName, task.getName()); taskInfo.put(processInstanceId, task.getProcessInstanceId()); taskInfo.put(createTime, task.getCreateTime()); // 可以进一步通过runtimeService获取流程变量显示业务信息 // MapString, Object vars runtimeService.getVariables(task.getProcessInstanceId()); // taskInfo.put(applyUser, vars.get(applyUserId)); // taskInfo.put(days, vars.get(days)); return taskInfo; }).collect(Collectors.toList()); }5.3 完成任务与驱动流程流转流程实例启动后会停留在第一个用户任务节点“提交请假申请”。我们需要模拟用户完成任务来驱动流程向下流转。import org.activiti.engine.TaskService; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; RestController RequestMapping(/api/task) public class TaskController { Autowired private TaskService taskService; Autowired private RuntimeService runtimeService; /** * 完成一个任务 * param taskId 任务ID * param approved 审批是否通过 */ PostMapping(/complete) public String completeTask(RequestParam String taskId, RequestParam boolean approved) { // 在完成任务前可以设置流程变量这些变量会影响后续网关的走向 MapString, Object taskVariables new HashMap(); taskVariables.put(approval, approved); // 设置审批结果变量 // 完成任务 taskService.complete(taskId, taskVariables); // 完成任务后可以查询流程实例是否已到达下一个节点或结束 // 这里简单返回成功信息 return 任务完成成功。流程继续流转。; } /** * 认领任务如果任务没有被指定受理人或者需要拾取 */ PostMapping(/claim) public String claimTask(RequestParam String taskId, RequestParam String userId) { taskService.claim(taskId, userId); return 任务认领成功; } }核心逻辑任务查询用户“lisi”部门经理登录系统后调用/api/instance/my-tasks?userIdlisi会看到指派给他的“经理审批”任务。任务执行前端展示任务列表用户点击处理调用/api/task/complete接口传入任务ID和审批结果approvedtrue/false。流程推进taskService.complete()方法会结束当前任务。引擎会根据任务出口序列流计算下一个节点。由于我们设置了排他网关和条件${approval true}引擎会判断变量approval的值决定流程是走向“结束事件”如果为false或默认流还是走向下一个节点如果为true。在我们的简单流程里审批通过后流程就结束了。变量传递在complete方法中传入的taskVariables会设置到流程实例的上下文中对后续所有节点可见。5.4 删除流程实例谨慎操作与业务考量删除流程实例是一个需要慎重的操作因为它会清除该实例的所有运行时数据任务、变量、历史记录等。通常用于管理异常流程或测试数据清理。/** * 删除流程实例及其历史记录 * param processInstanceId 流程实例ID * param deleteReason 删除原因会记录在历史中 */ DeleteMapping(/delete) public String deleteInstance(RequestParam String processInstanceId, RequestParam String deleteReason) { try { // 使用RuntimeService删除运行时实例 runtimeService.deleteProcessInstance(processInstanceId, deleteReason); // 注意deleteProcessInstance默认会保留历史记录。 // 如果需要彻底删除包括历史记录需要使用HistoryService // historyService.deleteHistoricProcessInstance(processInstanceId); return 流程实例删除成功。; } catch (ActivitiObjectNotFoundException e) { return 删除失败未找到流程实例ID processInstanceId; } }重要警告与最佳实践业务状态同步如果你的业务系统有自己的状态如“请假单状态”为“审批中”在删除Activiti流程实例前务必先更新业务系统的状态为“已取消”或“异常终止”保持数据一致性。删除原因务必填写有意义的deleteReason例如“申请人撤销”、“系统异常终止”这对于后续审计至关重要。历史记录runtimeService.deleteProcessInstance()方法默认会将该实例移入历史表ACT_HI_*这意味着你仍然可以从历史中查询到它。如果调用historyService.deleteHistoricProcessInstance()则是物理删除数据将不可恢复。生产环境慎用物理删除。级联删除删除流程实例会级联删除其下的所有运行中的任务、变量等。6. 常见问题排查与实战技巧实录在实际集成和开发过程中你肯定会遇到各种问题。下面是我总结的一些典型场景和解决方案。6.1 Modeler设计与引擎运行不一致问题现象在Modeler里流程画得好好的一部署启动就报错或者流转逻辑不对。可能原因1BPMN XML语法错误。虽然Modeler生成但偶尔也可能因版本兼容性问题产生非标内容。排查将Modeler导出的XML文件用文本编辑器打开检查关键节点如process,userTask,sequenceFlow的id、name、sourceRef、targetRef属性是否完整、有无非法字符。可以尝试使用在线的BPMN 2.0验证工具进行校验。可能原因2表达式错误。在Assignee或条件中使用了表达式如${manager}但启动流程时没有传入对应的变量。排查检查所有使用了${...}的地方。确保在启动流程实例或完成任务时通过variablesMap传入了所有必需的变量。可以使用runtimeService.getVariables(processInstanceId)在运行时查看变量值。可能原因3网关配置遗漏。排他网关的某条流出连线没有设置条件也没有设置为默认流。排查对于排他网关所有流出连线必须满足“有且仅有一条默认流其余连线必须有条件”的规则。检查Modeler中每条连线的属性。6.2 任务查询不到或分配错误问题现象明明启动了流程但调用taskService.createTaskQuery().taskAssignee(“某人”).list()却返回空列表。可能原因1Assignee表达式未解析。在Modeler中User Task的Assignee字段如果写的是表达式${manager}那么引擎会在创建任务时从当前流程变量中查找manager这个key的值并将其作为Assignee。如果变量不存在或值为null任务Assignee可能为null。解决在启动流程或到达该任务的上一个节点时务必确保设置了正确的流程变量。可以在任务创建监听器中打印日志来调试。可能原因2候选人Candidate Users/Groups设置。任务可能被分配给了候选人组或候选人而不是具体的受理人。此时需要用.taskCandidateUser()或.taskCandidateGroupIn()来查询。可能原因3流程尚未流转到用户任务节点。可能因为自动服务任务Service Task执行时间过长或出错流程卡在了前面。排查使用runtimeService.getActiveActivityIds(processInstanceId)查看流程实例当前活动的节点ID与Modeler中的节点ID对比。6.3 流程实例无法删除或出现外键约束错误问题现象调用删除接口时抛出异常提示有关联数据无法删除。可能原因存在未完成的子任务或关联实体。Activiti的数据表之间存在外键约束。标准操作优先使用runtimeService.deleteProcessInstance(processInstanceId, reason)。这个方法会正确处理级联删除。强制删除极端情况如果实例状态异常标准删除失败可以考虑先通过taskService.deleteTasks(…)删除关联任务再通过runtimeService.suspendProcessInstanceById(…)挂起实例最后再尝试删除。但更推荐的方式是直接操作数据库删除仅限开发测试环境按照ACT_RU_TASK-ACT_RU_IDENTITYLINK-ACT_RU_VARIABLE-ACT_RU_EXECUTION-ACT_RU_EVENT_SUBSCR-ACT_RU_JOB-ACT_RU_TIMER_JOB-ACT_RU_SUSPENDED_JOB-ACT_RU_DEADLETTER_JOB-ACT_RU_HISTORY_JOB-ACT_RU_EXECUTION的顺序进行清理最后删除ACT_RU_PROCINST记录。生产环境严禁此操作。6.4 性能优化与数据清理建议随着流程实例数量的增长运行时表ACT_RU_*和历史表ACT_HI_*会变得非常庞大。历史数据归档Activiti的历史级别history-level可以在配置中设置。如果不需要完整的审计跟踪可以设置为audit或activity减少历史数据量。定期如每月将历史数据迁移到备份库并从当前库中清理。异步执行器对于服务任务Service Task等如果设置为activiti:asynctrue则会由异步执行器Async Executor处理避免阻塞流程线程。确保你的异步执行器配置正确且运行正常。流程定义缓存Activiti会缓存已部署的流程定义。在频繁重新部署的测试环境有时会遇到缓存未刷新的问题。可以调用repositoryService.setProcessDefinitionCacheMaxSize(0)临时禁用缓存或通过repositoryService.deleteDeployment(deploymentId, true)删除部署时级联清除缓存。整个流程走下来从Modeler画图到后端代码驱动你会发现Activiti的核心思想是状态机和事件驱动。Modeler帮你定义状态和转移规则BPMN而你的代码则通过调用引擎API来触发状态转移启动、完成任务。理解这一点就能更好地设计流程和处理各种边界情况。最后一定要善用Activiti提供的各种ServiceRepositoryService, RuntimeService, TaskService, HistoryService进行查询和调试它们是你与流程引擎交互的最主要工具。