Camunda 7 External Task Client Spring Boot Starter 实战指南:基于 REST API 实现外部任务 Worker
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
本文以 Camunda 7 平台的camunda-bpm-spring-boot-starter-external-task-client为核心,系统讲解如何通过 Spring Boot Starter 将外部系统接入 Camunda 工作流引擎:Worker 通过引擎 REST API 完成外部服务任务的 fetch(拉取)、lock(锁定)与 complete(完成)。读完本文,你将掌握依赖引入、application.yml属性配置、注解式 Topic 订阅、纯 Spring 集成方式,以及客户端自动装配与属性合并的底层实现原理。
概述:External Task 模式与 Starter 的定位
在 Camunda 的 BPMN 流程建模中,Service Task既可以由引擎内部委托代码(JavaDelegate)执行,也可以被建模为外部任务(External Task),交给运行在引擎之外的 Worker 进程处理。这种"外部任务"模式将业务系统与流程引擎解耦:流程引擎只负责发布任务、锁定任务、接收结果,而真正的业务逻辑(如调用第三方接口、执行耗时计算)由独立部署的 Worker 完成。
本 Starter 正是为此而生。仓库中的 spring-boot-starter/starter-client/README.md 明确指出:该 Starter 允许你实现一个 Camunda External Task Worker,它使用 Camunda REST API 来 fetch、lock 和 complete 外部服务任务,并基于 Java External Task Client(即 clients/java 目录下的camunda-external-task-client-java客户端)构建。因此它具备以下特点:
- 无需在 Worker 侧部署引擎,只需能访问引擎的 REST 端点;
- 天然适合微服务架构、多语言/异构系统参与工作流;
- Worker 可水平扩展,多个 Worker 实例可同时订阅同一 Topic,由引擎按锁机制分发任务。
引入依赖
在 Spring Boot 项目中添加如下 Maven 依赖即可开始使用:
<dependency> <groupId>org.camunda.bpm.springboot</groupId> <artifactId>camunda-bpm-spring-boot-starter-external-task-client</artifactId> <version>...</version> </dependency>该坐标与仓库中的实际模块一致:模块目录为 spring-boot-starter/starter-client/spring-boot,其pom.xml中的 artifactId 正是camunda-bpm-spring-boot-starter-external-task-client。从该模块的pom.xml可以确认,它会传递依赖camunda-external-task-client-spring(Spring 集成层)、spring-boot-autoconfigure与spring-boot-starter,因此你无需额外手工引入 REST 客户端或 JSON 序列化相关依赖。
在 application.yml 中配置客户端与订阅
Starter 的核心配置入口是camunda.bpm.client前缀,绑定到 ClientProperties.java(标注了@ConfigurationProperties(prefix = "camunda.bpm.client")),由 ClientAutoConfiguration.java 中的@EnableConfigurationProperties({ClientProperties.class})自动启用。
README 给出了一个最小可用示例,其中base-url指向 Camunda 引擎 REST API,subscriptions以 Topic 名为 key 配置订阅过滤条件:
camunda.bpm.client: base-url: http://localhost:8080/engine-rest subscriptions: creditScoreChecker: process-definition-key: loan_process include-extension-properties: true variable-names: defaultScore客户端级(Client 级)配置项
ClientProperties继承自 ClientConfiguration.java,以下属性可配置在camunda.bpm.client下(YAML 中采用 kebab-case,如base-url、worker-id、max-tasks):
| 配置项 | 类型/默认值 | 说明 |
|---|---|---|
base-url | String,必填 | Camunda Runtime REST API 的基础地址,例如http://localhost:8080/engine-rest。 |
worker-id | String,默认自动生成 | 引擎感知的 Worker 标识。若未指定,会自动生成"主机名 + 随机 128 位 UUID"的组合,参见 EnableExternalTaskClient.java 中workerId()的 Javadoc。多实例部署时建议显式配置唯一值。 |
max-tasks | int,默认 10 | 单次请求最多拉取的任务数。 |
use-priority | boolean,默认true | 是否按任务优先级取任务。 |
use-create-time | boolean,默认false | 是否按任务创建时间取任务。 |
order-by-create-time | asc/desc | 配合use-create-time指定排序方向,常量见 EnableExternalTaskClient.java 中的STRING_ORDER_BY_ASC_VALUE/STRING_ORDER_BY_DESC_VALUE。 |
async-response-timeout | long(毫秒) | 长轮询(异步响应)超时。设置后 fetch-and-lock 请求会挂起等待任务到来,避免空轮询对引擎造成压力;不设置则同步立即返回。 |
lock-duration | long(毫秒),默认 20000 | 客户端全局锁定时长,必须大于 0。会被订阅级lock-duration覆盖。 |
date-format | String,默认yyyy-MM-dd'T'HH:mm:ss.SSSZ | 日期类型变量的序列化/反序列化格式。 |
default-serialization-format | String,默认application/json | 未显式指定格式时对象的默认序列化格式。 |
disable-auto-fetching | boolean,默认false | 为true时客户端启动后不立即拉取任务,需手动调用ExternalTaskClient#start()。 |
disable-backoff-strategy | boolean,默认false | 为true时禁用客户端退避策略(Backoff)。注意:禁用退避可能对引擎造成较大负载,建议同时配置async-response-timeout。 |
basic-auth | 对象 | 见下文"Basic Auth 认证"一节。 |
订阅级(Subscription 级)配置项
subscriptions是一个以Topic 名为 key 的 Map,value 绑定到 SubscriptionConfiguration.java。每个订阅支持以下属性:
| 配置项 | 类型/默认值 | 说明 |
|---|---|---|
topic-name | String | 订阅的 Topic 名,通常对应 BPMN 模型中 Service Task 的camunda:topic扩展属性;也可以不写,因为 Map 的 key 就是 Topic 名。 |
auto-open | boolean,默认true | true表示应用启动后立即开始拉取任务;false表示需要手动调用SpringTopicSubscription#open()打开订阅。 |
lock-duration | long(毫秒),默认 20000 | 该订阅的锁定时长,覆盖客户端级配置。 |
variable-names | List<String>,默认全部变量 | 只拉取指定名称的流程变量;不配置则拉取全部可见变量。 |
local-variables | boolean,默认false | 是否只拉取外部任务局部作用域的变量(true),否则拉取任务可见范围内的所有变量。 |
business-key | String | 按业务键过滤要拉取的任务。 |
process-definition-id | String | 按流程定义 ID 过滤。 |
process-definition-id-in | List<String> | 按多个流程定义 ID 过滤。 |
process-definition-key | String | 按流程定义 Key 过滤,README 示例中的loan_process即此用法。 |
process-definition-key-in | List<String> | 按多个流程定义 Key 过滤。 |
process-definition-version-tag | String | 按流程定义版本标签过滤。 |
process-variables | Map<String, Object> | 按流程变量名值对过滤(fetch 条件),例如要求某变量等于指定值。 |
without-tenant-id | boolean,默认false | 只拉取无租户(tenant)的任务。 |
tenant-id-in | List<String> | 按租户 ID 列表过滤。 |
include-extension-properties | boolean,默认false | 是否在任务中附带 Service Task 的自定义扩展属性(camunda:extensionProperties),README 示例中设置为true。 |
上述全部属性均可在PropertiesAwareSpringTopicSubscription的合并逻辑中找到对应处理(见下文),说明它们是受支持并会被逐一注入订阅配置的。
编写 Topic 订阅 Handler
配置好属性后,实现一个 Bean 并标注@ExternalTaskSubscription("topicName")即可订阅对应 Topic。README 的示例使用"类级注解 + 实现ExternalTaskHandler"的方式:
@Configuration @ExternalTaskSubscription("creditScoreChecker") public class CreditScoreCheckerHandler implements ExternalTaskHandler { @Override public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) { // add your business logic here } }其中ExternalTask与ExternalTaskService来自底层 Java 客户端(clients/java/client 的org.camunda.bpm.client.task包)。在execute方法内你可以:
- 通过
externalTask.getAllVariables()/getVariable(name)读取流程变量; - 通过
externalTaskService.complete(externalTask, variables)完成任务并回写变量; - 通过
externalTaskService.handleFailure(...)报告处理失败,引擎会按 BPMN 中的错误处理配置决定重试; - 通过
externalTaskService.handleBpmnError(...)抛出 BPMN 错误,驱动边界事件(Boundary Event)等流程路径。
注解的两种放置位置
ExternalTaskSubscription注解的@Target同时包含TYPE与METHOD(见 ExternalTaskSubscription.java),因此有两种写法:
- 类级注解:标注在实现
ExternalTaskHandler的@Configuration/@Component类上,如上例; - 方法级注解:标注在返回
ExternalTaskHandler的@Bean方法上。
仓库测试中的 FullSubscriptionConfiguration.java 展示了方法级注解的完整用法,几乎穷举了注解的所有属性,可作为编写复杂订阅的参考模板:
@Configuration public class FullSubscriptionConfiguration { @ExternalTaskSubscription( autoOpen = true, topicName = "topic-one", variableNames = {"annotated-variable-one", "annotated-variable-two"}, lockDuration = 1111, localVariables = true, businessKey = "annotated-business-key", processDefinitionId = "annotated-process-definition-id", processDefinitionIdIn = {"annotated-id-one", "annotated-id-two"}, processDefinitionKey = "annotated-key", processDefinitionKeyIn = {"annotated-key-one", "annotated-key-two"}, processDefinitionVersionTag = "annotated-version-tag", processVariables = { @ProcessVariable(name = "annotated-var-name-foo", value = "annotated-var-val-foo"), @ProcessVariable(name = "annotated-var-name-bar", value = "annotated-var-val-bar") }, withoutTenantId = true, tenantIdIn = {"annotated-tenant-id-one", "annotated-tenant-id-two"}, includeExtensionProperties = true ) @Bean public ExternalTaskHandler handler() { return (externalTask, externalTaskService) -> { // interact with the external task }; } }注意注解约定:字符串类型属性默认值为保留字$null$,long 类型默认值为Long.MIN_VALUE,int 类型默认值为Integer.MIN_VALUE,注解解析时会将这些哨兵值视为"未设置"(见 ExternalTaskSubscription.java 与 EnableExternalTaskClient.java 的 Javadoc 说明)。
属性合并机制:注解与 yml 如何协同
使用 Starter 时,订阅配置可以同时来自注解和application.yml。二者的合并逻辑由 PropertiesAwareSpringTopicSubscription.java 的mergeSubscriptionWithProperties()实现:
- 先从注解(或
@Bean定义)中解析出订阅配置merge; - 以 Topic 名调用
clientProperties.findSubscriptionPropsByTopicName(topicName)取出 yml 中对应订阅的属性; - 对
autoOpen、lockDuration、variableNames、businessKey、processDefinitionId、processDefinitionIdIn、processDefinitionKey、processDefinitionKeyIn、processDefinitionVersionTag、processVariables、withoutTenantId、tenantIdIn、includeExtensionProperties等每一项,只要 yml 中显式设置了非空值,就覆盖注解中的值。
这意味着一个务实的使用策略是:把"硬编码"的静态配置(如 Topic 名、过滤条件)放在注解中,把与环境相关的配置(如端点、凭据、是否开启扩展属性)放在application.yml,便于不同环境(dev/test/prod)通过外部化配置覆盖。仓库测试 MergeSubscriptionConfigurationTest.java 与 PropertiesOverrideSubscriptionConfigurationTest.java 专门验证了这一覆盖语义。
订阅的启动时机
底层 Spring 集成的订阅实现通过监听应用事件来决定何时打开订阅。Starter 将允许启动订阅的事件限定为ApplicationStartedEvent(见PropertiesAwareSpringTopicSubscription.isEventThatCanStartSubscription()),即应用完全启动完成后才开始拉取任务,避免在 Bean 尚未就绪时就开始消费任务。若auto-open为false,则可通过注入SpringTopicSubscription并调用其open()手动开启。
Basic Auth 认证与请求拦截器
Starter 内置了对引擎 REST API 的 Basic Auth 支持。在application.yml中配置:
camunda.bpm.client: base-url: http://localhost:8080/engine-rest basic-auth: username: demo password: demo绑定类为 BasicAuthProperties.java,包含username与password两个字段。装配逻辑位于 PropertiesAwareClientFactory.java 的addBasicAuthInterceptor():当basic-auth非空时,会创建BasicAuthProvider(username, password)并添加到客户端的请求拦截器列表(getRequestInterceptors().add(...))。BasicAuthProvider来自底层 Java 客户端的org.camunda.bpm.client.interceptor.auth包。
由于它走的是请求拦截器机制,你同样可以自定义实现ClientRequestInterceptor的ClientRequestInterceptorBean 来为每个 REST 请求附加自定义头(如 OAuth Token、自定义 Header),仓库测试 RequestInterceptorConfigurationTest.java 与 BasicAuthAndInterceptorConfigurationTest.java 即覆盖了此类场景。
纯 Spring 集成(不使用 Spring Boot)
如果你的项目使用的是 Spring(非 Spring Boot),可以退而求其次只引入 Spring 集成层依赖:
<dependency> <groupId>org.camunda.bpm</groupId> <artifactId>camunda-external-task-client-spring</artifactId> <version>...</version> </dependency>对应模块位于 spring-boot-starter/starter-client/spring。随后用@EnableExternalTaskClient注解启用客户端,并显式配置 REST API 端点等选项:
@Configuration @EnableExternalTaskClient(baseUrl = "http://localhost:8080/engine-rest") public class SimpleConfiguration { }@EnableExternalTaskClient定义于 EnableExternalTaskClient.java,它通过@Import(PostProcessorConfiguration.class)引入客户端与订阅的后处理器(ClientPostProcessor、SubscriptionPostProcessor),这两个处理器由 ClientAutoConfiguration.java 在 Spring Boot 场景下自动注册为 Bean(@ConditionalOnMissingBean保证可被用户自定义 Bean 覆盖)。
@EnableExternalTaskClient支持的全部注解属性(即上一节 Client 级配置项的注解形态)包括:baseUrl(必填,别名value)、workerId、maxTasks(默认 10)、usePriority(默认true)、useCreateTime(默认false)、orderByCreateTime、asyncResponseTimeout、lockDuration、disableAutoFetching、disableBackoffStrategy、dateFormat、defaultSerializationFormat。订阅注解@ExternalTaskSubscription与 Starter 场景完全一致,可照常使用。纯 Spring 场景下没有application.yml自动绑定,因此所有配置均通过注解或编程方式提供。
底层工作流程:从自动配置到任务消费
将 README 描述与仓库源码结合,一个 Worker 的完整生命周期如下:
- 自动装配:
ClientAutoConfiguration在应用启动时生效,注册SubscriptionPostProcessor(使用PropertiesAwareSpringTopicSubscription实现)与ClientPostProcessor(使用PropertiesAwareClientFactory实现); - 客户端构建:
PropertiesAwareClientFactory.afterPropertiesSet()将ClientProperties中的baseUrl、workerId、maxTasks、usePriority、lockDuration、asyncResponseTimeout、dateFormat、defaultSerializationFormat等属性应用到底层ExternalTaskClient,并注册 Basic Auth 拦截器(见 PropertiesAwareClientFactory.java); - 订阅合并:每个
@ExternalTaskSubscriptionBean 在初始化时通过mergeSubscriptionWithProperties()将 yml 属性与注解属性合并,生成最终的订阅配置; - 启动消费:监听
ApplicationStartedEvent,应用启动完成后自动打开订阅; - 持续循环:客户端通过 REST API 周期性调用 fetch-and-lock 拉取并锁定任务 → 调用
ExternalTaskHandler.execute()执行业务逻辑 → 通过ExternalTaskService完成/报错/抛 BPMN 错误;客户端内置退避策略(Backoff)与长轮询(asyncResponseTimeout)机制以减轻引擎轮询压力。
底层 REST 交互(fetch-and-lock、complete、handleFailure、handleBpmnError、extendLock、unlock 等请求 DTO 与执行器)实现于 clients/java/client 的org.camunda.bpm.client.impl与org.camunda.bpm.client.task.impl包,例如 FetchAndLockRequestDto.java、CompleteRequestDto.java。集成测试 ClientIT.java 与 TopicSubscriptionIT.java 验证了真实引擎环境下的拉取、锁定与完成链路。
测试与验证
仓库为 Spring 与 Spring Boot 两种集成都提供了完善的测试覆盖,可供你在接入时参考验证自己的配置:
- Spring 集成层测试:spring-boot-starter/starter-client/spring/src/test 下的
ConfigurationTest、SimpleConfigurationTest、DefaultConfigurationTest、SubscriptionTest、BackoffStrategyConfigurationTest、MultipleClientAnnotationsExceptionTest等,覆盖注解解析、订阅生命周期(autoOpen为 false 时的NotOpenedException)、退避策略 Bean 等; - Spring Boot Starter 测试:spring-boot-starter/starter-client/spring-boot/src/test 下的
ClientConfigurationTest、SubscriptionConfigurationTest、MergeSubscriptionConfigurationTest、PropertiesOverrideSubscriptionConfigurationTest、BasicAuthConfigurationTest、集成测试 ClientAutoConfigurationIT.java 等,覆盖属性绑定与自动装配行为。
小结
camunda-bpm-spring-boot-starter-external-task-client将 External Task 模式的接入成本降到了最低:加一个依赖、写一段application.yml、实现一个ExternalTaskHandler并标注@ExternalTaskSubscription,一个可水平扩展的 Worker 就完成了。其"注解声明 + 外部化属性覆盖 + 应用就绪后自动开启订阅"的设计,既保证了开发效率,也保留了多环境部署的灵活性。若你正在 Spring Boot 项目中集成 Camunda 外部任务,可将本 Starter 作为首选接入方式;纯 Spring 项目则可退而使用camunda-external-task-client-spring加@EnableExternalTaskClient的等价方案。
值得一提的是,本 Starter 起源于社区扩展(最初由 Oliver Steinhauer 创建),后并入 Camunda 官方仓库(见 README.md 的 Credits 说明),这也解释了它"面向真实业务场景、开箱即用"的设计取向。需要说明的是,Camunda 7 CE 已进入 EoL(生命周期结束)状态,新项目建议评估 Camunda 8 平台,但理解该 Starter 所体现的外部任务模式与 Spring 集成范式,对迁移与维护存量系统仍有直接的参考价值。
【免费下载链接】camunda-bpm-platformCamunda 7 CE is End of Life (EoL). Please check out Camunda 8 instead (https://github.com/camunda/camunda) or read about Camunda 7 Enterprise End of Life (https://camunda.com/blog/2025/02/camunda-7-enterprise-end-of-life-extension/) – Camunda 7 CE was a flexible framework for workflow and decision automation using BPMN and DMN.项目地址: https://gitcode.com/GitHub_Trending/ca/camunda-bpm-platform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考