集成数据

本指南将引导您完成使用 Spring Integration 创建一个简单应用程序的过程,该应用程序从 RSS Feed (Spring Blog) 中检索数据,对数据进行处理,然后将其写入文件。本指南使用传统的 Spring Integration XML 配置。其他指南将介绍如何使用 Java 配置和 DSL(带或不带 Lambda 表达式)进行配置。

您将构建什么

您将使用传统的 XML 配置通过 Spring Integration 创建一个流程。

你需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以跳过您已熟悉的基本设置步骤。无论哪种方式,您最终都会得到可工作的代码。

从头开始,请转到从 Spring Initializr 开始

跳过基础知识,请执行以下操作

完成后,您可以将您的结果与 gs-integration/complete 中的代码进行对照。

从 Spring Initializr 开始

您可以使用这个 预初始化项目 并点击生成下载 ZIP 文件。此项目已配置为符合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。此服务会为您拉取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。

  3. 点击 Dependencies 并选择 Spring Integration

  4. 单击生成

  5. 下载生成的 ZIP 文件,这是一个已根据您的选择配置好的 Web 应用程序存档。

如果您的 IDE 集成了 Spring Initializr,您可以从 IDE 中完成此过程。
您还可以从 Github fork 该项目并在您的 IDE 或其他编辑器中打开它。

添加到构建文件

对于此示例,您需要添加两个依赖项。

  • spring-integration-feed

  • spring-integration-file

以下列表显示了最终的 pom.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>3.3.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>integration-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>integration-complete</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>17</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-integration</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-feed</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-file</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.integration</groupId>
			<artifactId>spring-integration-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

以下列表显示了最终的 build.gradle 文件

plugins {
	id 'org.springframework.boot' version '3.3.0'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-integration'
	implementation 'org.springframework.integration:spring-integration-feed'
	implementation 'org.springframework.integration:spring-integration-file'
	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation 'org.springframework.integration:spring-integration-test'
}

test {
	useJUnitPlatform()
}

定义集成流程

对于本指南的示例应用程序,您将定义一个 Spring Integration 流程,该流程将:

  • 从 spring.io 的 RSS feed 读取博客文章。

  • 将它们转换为易于阅读的 String,其中包含文章标题和文章的 URL。

  • 将该 String 追加到文件末尾(/tmp/si/SpringBlog)。

要定义集成流程,您可以创建一个 Spring XML 配置文件,其中包含 Spring Integration 的 XML 命名空间中的一些元素。具体来说,对于所需的集成流程,您将使用以下 Spring Integration 命名空间中的元素:core、feed 和 file。(获取后两个是我们需要修改 Spring Initializr 提供的构建文件的原因。)

以下 XML 配置文件(来自 src/main/resources/integration/integration.xml)定义了集成流程:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:int="http://www.springframework.org/schema/integration"
	xmlns:file="http://www.springframework.org/schema/integration/file"
	xmlns:feed="http://www.springframework.org/schema/integration/feed"
	xsi:schemaLocation="http://www.springframework.org/schema/integration/feed https://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/integration/file https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
		http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd">

    <feed:inbound-channel-adapter id="news" url="https://springjava.cn/blog.atom" auto-startup="${auto.startup:true}">
        <int:poller fixed-rate="5000"/>
    </feed:inbound-channel-adapter>

    <int:transformer
            input-channel="news"
            expression="payload.title + ' @ ' + payload.link + '#{systemProperties['line.separator']}'"
            output-channel="file"/>

    <file:outbound-channel-adapter id="file"
            mode="APPEND"
            charset="UTF-8"
            directory="/tmp/si"
            filename-generator-expression="'${feed.file.name:SpringBlog}'"/>

</beans>

这里有三个集成元素在起作用:

  • <feed:inbound-channel-adapter>:一个入站适配器,每隔轮询一次检索帖子。在此配置中,它每五秒轮询一次。帖子被放入一个名为 news 的通道(对应于适配器的 ID)。

  • <int:transformer>:转换 news 通道中的条目(com.rometools.rome.feed.synd.SyndEntry),提取条目的标题(payload.title)和链接(payload.link),并将它们连接成一个易于阅读的 String(并添加换行符)。然后将 String 发送到名为 file 的输出通道。

  • <file:outbound-channel-adapter>:一个出站通道适配器,它将内容从其通道(名为 file)写入文件。具体来说,在此配置中,它将 file 通道中的任何内容追加到位于 /tmp/si/SpringBlog 的文件中。

下图显示了这个简单的流程:

A flow that reads RSS feed entries

暂时忽略 auto-startup 属性。我们稍后在讨论测试时会重新讨论它。目前,请注意它默认设置为 true,这意味着在应用程序启动时就会获取帖子。还要注意 filename-generator-expression 中的属性占位符。这意味着默认值是 SpringBlog,但可以通过属性覆盖。

使应用程序可执行

虽然通常在一个更大的应用程序(甚至可能是 Web 应用程序)中配置 Spring Integration 流程,但没有理由不能在一个更简单的独立应用程序中定义它。接下来您将要做的是:创建一个主类来启动集成流程,并声明一些 bean 来支持集成流程。您还将把应用程序构建成一个独立的、可执行的 JAR 文件。我们使用 Spring Boot 的 @SpringBootApplication 注解来创建应用程序上下文。由于本指南使用 XML 命名空间来定义集成流程,因此您必须使用 @ImportResource 注解将其加载到应用程序上下文中。以下列表(来自 src/main/java/com/example/integration/IntegrationApplication.java)显示了应用程序文件:

package com.example.integration;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource("/integration/integration.xml")
public class IntegrationApplication {
  public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext ctx = new SpringApplication(IntegrationApplication.class).run(args);
    System.out.println("Hit Enter to terminate");
    System.in.read();
    ctx.close();
  }

}

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源并运行的单个可执行 JAR 文件。构建可执行 JAR 使在整个开发生命周期中,跨不同环境等,轻松交付、版本化和部署服务作为应用程序。

如果您使用 Gradle,您可以通过使用 ./gradlew bootRun 运行应用程序。或者,您可以通过使用 ./gradlew build 构建 JAR 文件,然后按如下方式运行 JAR 文件

java -jar build/libs/gs-integration-0.1.0.jar

如果您使用 Maven,您可以通过使用 ./mvnw spring-boot:run 运行应用程序。或者,您可以使用 ./mvnw clean package 构建 JAR 文件,然后按如下方式运行 JAR 文件

java -jar target/gs-integration-0.1.0.jar
这里描述的步骤创建了一个可运行的 JAR。您还可以构建一个经典的 WAR 文件

运行应用程序

现在,您可以运行该应用程序的 jar 文件,运行以下命令:

java -jar build/libs/{project_id}-0.1.0.jar

... app starts up ...

应用程序启动后,它会连接到 RSS feed 并开始获取博客文章。应用程序通过您定义的集成流程处理这些文章,最终将文章信息追加到 /tmp/si/SpringBlog 的文件中。

应用程序运行一段时间后,您应该能够查看 /tmp/si/SpringBlog 中的文件,查看一些帖子的数据。在类 UNIX 操作系统上,您也可以使用 tail 命令实时查看写入的数据,运行以下命令:

tail -f /tmp/si/SpringBlog

您应该会看到类似以下的示例输出(但实际新闻会有所不同):

Spring Integration Java DSL 1.0 GA Released @ https://springjava.cn/blog/2014/11/24/spring-integration-java-dsl-1-0-ga-released
This Week in Spring - November 25th, 2014 @ https://springjava.cn/blog/2014/11/25/this-week-in-spring-november-25th-2014
Spring Integration Java DSL: Line by line tutorial @ https://springjava.cn/blog/2014/11/25/spring-integration-java-dsl-line-by-line-tutorial
Spring for Apache Hadoop 2.1.0.M2 Released @ https://springjava.cn/blog/2014/11/14/spring-for-apache-hadoop-2-1-0-m2-released

测试

检查 complete 项目,您会在 src/test/java/com/example/integration/FlowTests.java 中看到一个测试用例。

package com.example.integration;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;

import com.rometools.rome.feed.synd.SyndEntryImpl;

@SpringBootTest({ "auto.startup=false",   // we don't want to start the real feed
          "feed.file.name=Test" })   // use a different file
public class FlowTests {

  @Autowired
  private SourcePollingChannelAdapter newsAdapter;

  @Autowired
  private MessageChannel news;

  @Test
  public void test() throws Exception {
    assertThat(this.newsAdapter.isRunning()).isFalse();
    SyndEntryImpl syndEntry = new SyndEntryImpl();
    syndEntry.setTitle("Test Title");
    syndEntry.setLink("http://characters/frodo");
    File out = new File("/tmp/si/Test");
    out.delete();
    assertThat(out.exists()).isFalse();
    this.news.send(MessageBuilder.withPayload(syndEntry).build());
    assertThat(out.exists()).isTrue();
    BufferedReader br = new BufferedReader(new FileReader(out));
    String line = br.readLine();
    assertThat(line).isEqualTo("Test Title @ http://characters/frodo");
    br.close();
    out.delete();
  }

}

此测试使用 Spring Boot 的测试支持将名为 auto.startup 的属性设置为 false。依赖网络连接进行测试通常不是一个好主意,尤其是在 CI 环境中。相反,我们阻止 feed 适配器启动,并将一个 SyndEntry 注入到 news 通道中,以便其余流程处理。该测试还设置了 feed.file.name,以便测试写入不同的文件。然后它:

  • 验证适配器已停止。

  • 创建测试 SyndEntry

  • 删除测试输出文件(如果存在)。

  • 发送消息。

  • 验证文件是否存在。

  • 读取文件并验证数据是否符合预期。

总结

恭喜!您已经开发了一个简单的应用程序,该应用程序使用 Spring Integration 从 spring.io 获取博客文章,进行处理,然后将其写入文件。

另请参阅

以下指南也可能有所帮助:

想写新指南或为现有指南做贡献吗?请查看我们的贡献指南

所有指南的代码均采用 ASLv2 许可,文字内容采用署名-禁止演绎知识共享许可