1. 概述
在本文中,我們將Apache Camel及其核心概念——消息路由進行介紹。
我們將首先涵蓋一些基礎概念和術語,然後介紹定義路由的兩種選項:Java DSL 和 Spring DSL。
此外,我們將通過定義一條路由來演示這些內容,該路由從一個文件夾中消費文件並將其移動到另一個文件夾,同時在文件名中添加日期。
2. 關於 Apache Camel
Apache Camel 是一個開源集成框架,旨在簡化和易於集成系統.
它允許最終用户使用相同的 API 集成各種系統,同時支持多種協議和數據類型,並具有可擴展性和允許引入自定義協議的功能。
3. Maven 依賴項
首先,讓我們在我們的 pom.xml 中聲明 camel-spring-boot-starter 和 spring-boot-starter-web 依賴項:
<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>此外,為了測試,我們需要將 camel-test-spring-junit5 和 awaitility 依賴項添加到 pom.xml 文件中:
<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. 領域特定語言
路由和路由引擎是 Camel 的核心組成部分。 路由包含不同系統之間集成中的流程和邏輯。
為了更輕鬆、更清晰地定義路由,Camel 提供了多種領域特定語言 (DSL) 用於編程語言(如 Java 或 Groovy)。 另一方面,它還提供了使用 XML 和 Spring DSL 定義路由。
使用 Java DSL 或 Spring DSL 主要取決於用户偏好,因為大多數功能在兩者之間都可用。
Java DSL 提供了比 Spring DSL 更多的功能,但 Spring DSL 在配置 XML 時更具優勢,無需重新編譯代碼。
5. 術語與架構
現在,我們來討論一下基本的 Camel 術語和架構。
首先,我們將查看核心 Camel 概念:
- 消息包含在傳輸到路由過程中傳輸的數據。每個消息都有一個唯一的標識符,並且由正文、標頭和附件組成。
- 交換是消息的容器,並在消費者接收消息並進行路由過程中創建。Exchange 允許不同類型的系統間交互——它可以定義單向消息或請求-響應消息。
- 端點是系統接收或發送消息的通道。它可以引用 Web 服務 URI、隊列 URI、文件或電子郵件地址等。
- 組件充當端點工廠的角色。簡單來説,組件提供了一種使用相同的方法和語法與不同的技術進行交互的方式。Camel 已經支持大量的組件,這些組件存在於其 DSL 中,幾乎涵蓋了所有可能的技術,但它也提供了編寫自定義組件的能力。
- 處理器是一個簡單的 Java 接口,用於向路由添加自定義集成邏輯。它包含一個 process 方法,用於在消費者接收到消息時執行自定義業務邏輯。
在較高層面上,Camel 的架構很簡單。 CamelContext 代表 Camel 運行時系統,並連接不同的概念,如路由、組件或端點。
並且在下面,處理器處理端點之間的路由和轉換,而端點則集成不同的系統。
6. 定義路由
路由可以使用 Java DSL 或 Spring DSL 定義
我們將通過定義一個路由來演示這兩種風格,該路由從一個文件夾消費文件,並將它們移動到另一個文件夾,同時為每個文件名添加日期前綴。
6.1. 使用 Java DSL 進行路由
要使用 Java DSL 定義路由,首先需要創建一個 <em >DefaultCamelContext</em> 實例。然後,需要擴展 <em >RouteBuilder</em> 類並實現 <em >configure</em> 方法,該方法將包含路由流程:
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表示文件在成功處理後將從源文件夾中刪除。
為了啓動 Camel,我們需要調用CamelContext上的start方法。之後,我們使用Awaitility類中的靜態方法await(),以便為 Camel 提供足夠的時間來將文件從一個文件夾移動到另一個文件夾。atMost()方法設置了 Awaitility 等待條件滿足的上限。在這種情況下,它將最多等待DURATION_MILIS毫秒。
此外,我們還可以使用untilAsserted()方法。此方法允許我們提供一個 lambda 或方法引用,其中包含一個或多個斷言。Awaitility 將反覆運行這些斷言,直到它們全部通過或達到指定的atMost()持續時間。assertThat()方法對兩個斷言進行了執行。在這些斷言中,我們檢查目標文件夾中文件的存在。
FileProcessor實現了Processor接口,幷包含一個process方法,其中包含用於修改文件名邏輯:
@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);
}
}為了檢索文件名,我們需要檢索來自交換的傳入消息並訪問其標頭。 同樣,要修改文件名,我們需要更新消息標頭。
6.2. 使用 Spring DSL 進行路由
定義 Spring DSL 路由時,我們使用 XML 文件來配置路由和處理器。 這樣可以避免編寫代碼,利用 Spring 並最終實現完全的控制反轉。
這已經在現有文章中得到涵蓋,因此我們將重點關注同時使用 Spring DSL 和 Java DSL,後者通常是定義路由的首選方式。
在這種配置中,CamelContext 在 Spring XML 文件中使用自定義的 Camel XML 語法進行定義,但不會像“純”Spring DSL 使用 XML 那樣包含路由定義。
<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeBuilder ref="fileRouter" />
</camelContext>
這樣一來,我們告訴 Camel 使用 FileRouter 類,該類包含我們 Java DSL 中定義的路由定義:
@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);
}
}為了測試此功能,我們需要創建一個 ClassPathXmlApplicationContext 的實例,這將加載我們的 Spring 中的 CamelContext:
@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 利用 Java 語言的所有可能性。
7. 結論
在本文中,我們對 Apache Camel 進行了介紹,並展示了使用 Camel 進行集成任務(如從一個文件夾路由文件到另一個文件夾)的優勢。
在我們的示例中,我們看到 Camel 允許我們專注於業務邏輯,並減少樣板代碼的量。