1. 概述

本文将介绍 Apache Camel 并探索其核心概念之一——消息路由。首先我们会讲解基础概念和术语,然后展示两种定义路由的方式:Java DSL 和 Spring DSL。最后通过一个示例演示如何定义路由:从文件夹读取文件并移动到目标文件夹,同时为每个文件名添加日期前缀。

2. 关于 Apache Camel

Apache Camel 是一个开源集成框架,旨在简化系统集成。它允许用户使用统一的 API 集成各种系统,支持多种协议和数据类型,同时具备良好的扩展性,可引入自定义协议。

3. Maven 依赖

pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.apache.camel.springboot</groupId>
    <artifactId>camel-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.11</version>
</dependency>

测试所需依赖:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test-spring-junit5</artifactId>
    <version>4.3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.2.0</version>
    <scope>test</scope>
</dependency>

4. 领域特定语言(DSL)

路由和路由引擎是 Camel 的核心部分。路由包含系统间集成的流程和逻辑。为了更简洁地定义路由,Camel 提供了多种领域特定语言(DSL):

  • Java DSL:使用 Java/Groovy 代码定义路由
  • Spring DSL:通过 XML 配置定义路由

两种 DSL 的选择主要取决于个人偏好,大部分功能都相互支持。Java DSL 提供更多 Spring DSL 不支持的功能,而 Spring DSL 的优势在于无需重新编译代码即可修改 XML 配置。

5. 核心概念与架构

5.1 核心概念

术语 说明
Message 包含传输数据的实体,由唯一标识符、消息体、头部和附件组成
Exchange 消息的容器,在消费者接收消息时创建。支持单向消息和请求-响应交互模式
Endpoint 系统接收/发送消息的通道(如 Web 服务 URI、队列、文件、邮箱等)
Component 终端工厂,为不同技术提供统一接口(Camel 已支持大量组件)
Processor 自定义集成逻辑的 Java 接口,包含 process() 方法处理消息

5.2 架构设计

Camel 架构分为两层:

  1. 顶层CamelContext 代表 Camel 运行时系统,连接路由、组件和终端
  2. 底层
    • 处理器负责终端间的路由和转换
    • 终端负责集成不同系统

6. 定义路由

路由可通过 Java DSL 或 Spring DSL 定义。下面通过文件移动示例(添加日期前缀)演示两种方式。

6.1 使用 Java DSL

private static final long DURATION_MILIS = 10000;
private static final String SOURCE_FOLDER = "src/test/source-folder";
private static final String DESTINATION_FOLDER = "src/test/destination-folder";

@Test
public void givenJavaDSLRoute_whenCamelStart_thenMoveFolderContent() throws Exception {
    CamelContext camelContext = new DefaultCamelContext();
    camelContext.addRoutes(new RouteBuilder() {
      @Override
      public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true")
          .process(new FileProcessor())
          .to("file://" + DESTINATION_FOLDER);
      }
    });
    camelContext.start();
    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    File destinationFile1 = new File(DESTINATION_FOLDER + "/" + dateFormat.format(date) + "File1.txt");
    File destinationFile2 = new File(DESTINATION_FOLDER + "/" + dateFormat.format(date) + "File2.txt");

    Awaitility.await().atMost(DURATION_MILIS, TimeUnit.MILLISECONDS).untilAsserted(() -> {
      assertThat(destinationFile1.exists()).isTrue();
      assertThat(destinationFile2.exists()).isTrue();
    });
    camelContext.stop();
}

关键点解析:

  • configure() 方法流程:读取源文件夹 → FileProcessor 处理 → 发送到目标文件夹
  • delete=true 表示处理成功后删除源文件
  • camelContext.start() 启动 Camel
  • Awaitility.await() 等待文件移动完成(最长等待 DURATION_MILIS 毫秒)

文件名修改逻辑(FileProcessor 类):

@Component
public class FileProcessor implements Processor {
    @Override
    public void process(Exchange exchange) throws Exception {
        String originalFileName = (String) exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);

        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String changedFileName = dateFormat.format(date) + originalFileName;
        exchange.getIn().setHeader(Exchange.FILE_NAME, changedFileName);
    }
}

⚠️ 通过操作 Exchange 的消息头实现文件名修改

6.2 使用 Spring DSL

Spring DSL 通过 XML 配置路由和处理器,实现完全的控制反转。这里展示混合使用 Spring DSL 和 Java DSL 的推荐方式:

XML 配置(camel-context-test.xml):

<camelContext xmlns="http://camel.apache.org/schema/spring">
    <routeBuilder ref="fileRouter" />
</camelContext>

Java 路由定义(FileRouter 类):

@Component
public class FileRouter extends RouteBuilder {

    private static final String SOURCE_FOLDER =  "src/test/source-folder";
    private static final String DESTINATION_FOLDER = "src/test/destination-folder";

    @Override
    public void configure() throws Exception {
        from("file://" + SOURCE_FOLDER + "?delete=true")
          .process(new FileProcessor())
          .to("file://" + DESTINATION_FOLDER);
    }
}

测试代码:

@Test
public void givenSpringDSLRoute_whenCamelStart_thenMoveFolderContent() throws Exception {
    ClassPathXmlApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("camel-context-test.xml");

    Date date = new Date();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    File destinationFile1 = new File(DESTINATION_FOLDER + "/" + dateFormat.format(date) + "File1.txt");
    File destinationFile2 = new File(DESTINATION_FOLDER + "/" + dateFormat.format(date) + "File2.txt");

    Awaitility.await().atMost(DURATION_MILIS, TimeUnit.MILLISECONDS).untilAsserted(() -> {
      assertThat(destinationFile1.exists()).isTrue();
      assertThat(destinationFile2.exists()).isTrue();
    });
    applicationContext.close();
}

✅ 这种方式结合了 Spring 的灵活性和 Java DSL 的强大功能

7. 总结

本文介绍了 Apache Camel 的基础概念和路由定义方式。通过文件移动示例可以看出,Camel 能让我们专注于业务逻辑,大幅减少样板代码。对于系统集成任务,Camel 是一个简单粗暴的解决方案。

本文代码可在 GitHub 获取


原始标题:Introduction To Apache Camel