项目采用 Spring Boot 2.7 + Flowable 6.8 + Camel 3.20,演示如何让 BPMN 流程通过 Camel Task 调用外部 HTTP 接口。
一、项目结构
flowable-camel-demo/
├── pom.xml
└── src/main/
├── java/com/example/demo/
│ ├── FlowableCamelApplication.java
│ ├── config/
│ │ └── FlowableCamelConfiguration.java ← 桥接配置
│ ├── route/
│ │ └── HttpCamelRoute.java ← Camel路由
│ └── controller/
│ └── ProcessController.java ← 测试接口
└── resources/
├── application.yml
└── processes/
└── camel-http-demo.bpmn20.xml
二、pom.xml(关键依赖)
XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<groupId>com.example</groupId>
<artifactId>flowable-camel-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<flowable.version>6.8.0</flowable.version>
<camel.version>3.20.9</camel.version>
</properties>
<dependencies>
<!-- Flowable 流程引擎 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- Flowable Camel 集成模块(核心) -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-camel</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- Camel Spring Boot 启动器 -->
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-spring-boot-starter</artifactId>
<version>${camel.version}</version>
</dependency>
<!-- Camel HTTP 组件(用于演示调用外部接口) -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http</artifactId>
<version>${camel.version}</version>
</dependency>
<!-- Web + H2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
三、application.yml
XML
server:
port: 8080
spring:
application:
name: flowable-camel-demo
flowable:
database-schema-update: true
deployment:
resources: classpath*:processes/*.bpmn20.xml
async-executor-activate: true
# Camel 配置
camel:
springboot:
# 启动时自动加载 routes 目录下的路由
routes-include-pattern: classpath:route/*.xml
component:
servlet:
mapping:
context-path: /camel/*
logging:
level:
org.flowable: info
com.example.demo: debug
org.apache.camel: info
四、核心:桥接 Flowable 与 Camel
Flowable 和 Camel 是两个独立的运行时,必须通过 FlowableComponent 建立通信桥梁。
java
package com.example.demo.config;
import org.apache.camel.CamelContext;
import org.apache.camel.spring.boot.CamelContextConfiguration;
import org.flowable.camel.FlowableComponent;
import org.flowable.engine.RuntimeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 将 Flowable 的 RuntimeService 注入到 Camel 的 FlowableComponent 中
* 这是两者能互相通信的关键
*/
@Component
public class FlowableCamelConfiguration implements CamelContextConfiguration {
@Autowired
private RuntimeService runtimeService;
@Override
public void beforeApplicationStart(CamelContext camelContext) {
FlowableComponent flowableComponent = new FlowableComponent();
flowableComponent.setRuntimeService(runtimeService);
// 注册到 CamelContext,协议名为 "flowable"
camelContext.addComponent("flowable", flowableComponent);
}
@Override
public void afterApplicationStart(CamelContext camelContext) {
// 启动后的回调,通常留空
}
}
五、Camel 路由定义
这是最核心的业务代码。路由监听来自 Flowable 的消息,调用外部 HTTP 接口,再把结果返回给流程。
java
package com.example.demo.route;
import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
@Component
public class HttpCamelRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
// ============================================
// 监听来自 Flowable 的消息
// ============================================
// URI 规则: flowable:processDefinitionKey
// copyVariablesToProperties=true: 把流程变量复制到 Exchange properties
// copyVariablesFromProperties=true: 把 Exchange properties 复制回流程变量
// ============================================
from("flowable:camelHttpDemo?copyVariablesToProperties=true©VariablesFromProperties=true")
.routeId("flowable-http-route")
.log(">>> [Camel] 收到 Flowable 消息")
.log(">>> [Camel] 流程变量 orderId: ${exchangeProperty.orderId}")
.log(">>> [Camel] 流程变量 userName: ${exchangeProperty.userName}")
// 清理 Camel 自动添加的 HTTP 头,避免污染请求
.removeHeaders("CamelHttp*")
// 设置请求方法
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
// 动态调用外部 HTTP 接口(这里用 httpbin.org 做演示)
// exchangeProperty.xxx 可以读取 Flowable 传入的流程变量
.toD("https://httpbin.org/get?orderId=${exchangeProperty.orderId}&user=${exchangeProperty.userName}")
// 将 HTTP 响应体保存到 Exchange property,Flowable 可以取回
.setProperty("httpResponseBody", body())
.setProperty("httpResponseStatus", simple("${header.CamelHttpResponseCode}"))
// 最终 Exchange Body 会被 Flowable 自动存入流程变量 "camelBody"
.setBody(simple("HTTP调用完成,状态码: ${header.CamelHttpResponseCode}"));
}
}
六、BPMN 流程定义
放到 src/main/resources/processes/camel-http-demo.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:flowable="http://flowable.org/bpmn"
targetNamespace="http://example.com/camel-http-demo">
<process id="camelHttpDemo" name="Camel HTTP 演示流程" isExecutable="true">
<!-- 开始 -->
<startEvent id="start" name="开始" />
<sequenceFlow id="f1" sourceRef="start" targetRef="camelTask" />
<!-- ========================================== -->
<!-- Camel 服务任务:核心配置在这里 -->
<!-- ========================================== -->
<serviceTask id="camelTask" name="Camel调用外部API"
flowable:type="camel">
<extensionElements>
<!-- 指定 CamelContext 的 bean 名称(默认就是 camelContext) -->
<flowable:field name="camelContext">
<flowable:string>camelContext</flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
<sequenceFlow id="f2" sourceRef="camelTask" targetRef="checkResult" />
<!-- 排他网关:根据 Camel 返回结果分流 -->
<exclusiveGateway id="checkResult" name="判断结果" />
<!-- 成功分支:camelBody 变量是 Camel 路由最终返回的 Body -->
<sequenceFlow id="f3" sourceRef="checkResult" targetRef="successTask">
<conditionExpression xsi:type="tFormalExpression">
${camelBody != null}
</conditionExpression>
</sequenceFlow>
<!-- 失败分支 -->
<sequenceFlow id="f4" sourceRef="checkResult" targetRef="failEnd">
<conditionExpression xsi:type="tFormalExpression">
${camelBody == null}
</conditionExpression>
</sequenceFlow>
<!-- 成功处理(模拟业务处理) -->
<serviceTask id="successTask" name="处理成功结果"
flowable:delegateExpression="${successHandler}" />
<sequenceFlow id="f5" sourceRef="successTask" targetRef="endSuccess" />
<endEvent id="endSuccess" name="成功结束" />
<!-- 失败结束 -->
<endEvent id="failEnd" name="失败结束" />
<!-- 边界错误事件:捕获 Camel 路由中的异常 -->
<boundaryEvent id="camelError" attachedToRef="camelTask" cancelActivity="true">
<errorEventDefinition errorRef="CAMEL_ERROR" />
</boundaryEvent>
<sequenceFlow id="f6" sourceRef="camelError" targetRef="errorEnd" />
<endEvent id="errorEnd" name="异常结束" />
</process>
</definitions>
七、辅助类与启动类
SuccessHandler.java(成功后的业务处理)
java
package com.example.demo.delegate;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component("successHandler")
public class SuccessHandler implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(SuccessHandler.class);
@Override
public void execute(DelegateExecution execution) {
String camelBody = (String) execution.getVariable("camelBody");
String httpResp = (String) execution.getVariable("httpResponseBody");
logger.info("[业务处理] Camel返回: {}", camelBody);
logger.info("[业务处理] HTTP原始响应: {}", httpResp);
}
}
启动类
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FlowableCamelApplication {
public static void main(String[] args) {
SpringApplication.run(FlowableCamelApplication.class, args);
}
}
八、测试接口
java
package com.example.demo.controller;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ProcessController {
@Autowired
private RuntimeService runtimeService;
/**
* 启动流程,触发 Camel HTTP 调用
*
* 测试请求:
* curl -X POST http://localhost:8080/api/start \
* -H "Content-Type: application/json" \
* -d '{"orderId":"ORDER_9527","userName":"zhangsan"}'
*/
@PostMapping("/start")
public Map<String, Object> startProcess(@RequestBody Map<String, Object> params) {
Map<String, Object> variables = new HashMap<>();
// 这些变量会被自动传递到 Camel 路由的 Exchange properties 中
variables.put("orderId", params.getOrDefault("orderId", "TEST_001"));
variables.put("userName", params.getOrDefault("userName", "admin"));
ProcessInstance instance = runtimeService
.startProcessInstanceByKey("camelHttpDemo", variables);
// 读取 Camel 返回的流程变量
Map<String, Object> processVars = runtimeService.getVariables(instance.getId());
Map<String, Object> result = new HashMap<>();
result.put("processInstanceId", instance.getId());
result.put("camelBody", processVars.get("camelBody")); // Camel路由最终Body
result.put("httpResponseBody", processVars.get("httpResponseBody")); // HTTP原始响应
result.put("httpResponseStatus", processVars.get("httpResponseStatus")); // HTTP状态码
return result;
}
}
九、运行与验证
1. 启动项目
直接运行 FlowableCamelApplication.main(),控制台会看到:
>>> [Camel] 收到 Flowable 消息
>>> [Camel] 流程变量 orderId: ORDER_9527
2. 发起测试
curl -X POST http://localhost:8080/api/start \
-H "Content-Type: application/json" \
-d '{"orderId":"ORDER_9527","userName":"zhangsan"}'
3. 返回结果
JSON
{
"processInstanceId": "2501",
"camelBody": "HTTP调用完成,状态码: 200",
"httpResponseBody": "{\"args\":{\"orderId\":\"ORDER_9527\",\"user\":\"zhangsan\"},...}",
"httpResponseStatus": "200"
}
十、变量映射对照表(核心理解)
这是学习 Flowable-Camel 集成的关键,必须掌握:
| 方向 | Flowable 侧 | Camel 侧 | 配置控制 |
|---|---|---|---|
| Flowable → Camel | 流程变量(如 orderId) |
Exchange Properties | copyVariablesToProperties=true |
| Flowable → Camel | 流程变量 Map | Exchange Body | copyVariablesToBody=true(默认false) |
| Camel → Flowable | 流程变量 camelBody |
Exchange Body | 自动映射,无需配置 |
| Camel → Flowable | 流程变量(任意名) | Exchange Properties | copyVariablesFromProperties=true |
Endpoint URI 参数说明:
from("flowable:camelHttpDemo?copyVariablesToProperties=true©VariablesFromProperties=true")
-
copyVariablesToProperties=true:启动时把 Flowable 所有流程变量塞进 Camel Exchange 的 properties,路由中通过${exchangeProperty.xxx}读取。 -
copyVariablesFromProperties=true:路由结束后,把 Exchange properties 里新增的/修改的值写回 Flowable 流程变量。
十一、进阶:换成 Kafka / MQ / FTP
Camel 的优势在于换组件只需改路由,BPMN 完全不用动。
例如把 HTTP 换成发 Kafka 消息:
java
@Component
public class KafkaCamelRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("flowable:camelHttpDemo?copyVariablesToProperties=true")
.log("发送订单到Kafka: ${exchangeProperty.orderId}")
// 只需改这一行,BPMN 中的 camelTask 完全不用改
.to("kafka:order-topic?brokers=localhost:9092")
.setBody(constant("KAFKA_SENT"));
}
}
十二、总结
| 步骤 | 做什么 |
|---|---|
| 1. 加依赖 | flowable-camel + camel-spring-boot-starter + 具体协议组件(http/kafka) |
| 2. 建桥梁 | 用 FlowableComponent 把 RuntimeService 注册到 CamelContext |
| 3. 写路由 | from("flowable:processDefinitionKey") 监听流程消息,.to("xxx") 调用外部系统 |
| 4. 画流程 | BPMN 中 serviceTask 设置 flowable:type="camel" |
| 5. 传变量 | 通过 copyVariablesToProperties / copyVariablesFromProperties 双向交换数据 |
一句话记忆 :Flowable 的 Camel Task 就是一个**"消息发射器"**,它把流程变量打包发给 Camel 路由;Camel 处理完后把结果写回流程变量。BPMN 负责编排,Camel 负责对接外部世界。