一、Spring Integration功能概览
集成流是由一个或多个如下介绍的组件组成的。在继续编写代码之前,我们先看一下这些组件在集成流中所扮演的角色:
- 通道(channel):将消息从一个元素传递到另一个元素。
- 过滤器(filter):基于某些断言,条件化地允许某些消息通过流。
- 转换器(transformer):改变消息的值和/或将消息载荷从一种类型转换成另一种类型。
- 切分器(splitter):将传入的消息切割成两个或更多的消息,然后将每个消息发送至不同的通道;
- 聚合器(aggregator):切分器的反向操作,将来自不同通道的多个消息合并成一个消息。
- 服务激活器(service activator):将消息传递给某个Java方法来进行处理,并将返回值发布到输出通道上。
- 网关(gateway):通过接口将数据传递到集成流中。
1. 消息通道
消息通道是消息穿行集成通道的一种方式。它们是连接SpringIntegration其他组成部分的管道。
Spring Integration提供了多种通道实现。
- PublishSubscribeChannel:发送到PublishSubscribeChannel的消息会被传递到一个或多个消费者中。如果有多个消费者,它们都会接收到消息。
- QueueChannel:发送到QueueChannel的消息会存储到一个队列中,会按照先进先出(First In First Out,FIFO)的方式被拉取出来。如果有多个消费者,只有其中的一个消费者会接收到消息。
- PriorityChannel:与QueueChannel类似,但它不是FIFO的方式,而是会基于消息的priority头信息被消费者拉取出来。
- RendezvousChannel:与QueueChannel类似,但是发送者会一直阻塞通道,直到消费者接收到消息为止,实际上会同步发送者和消费者。
- DirectChannel:与PublishSubscribeChannel类似,但是消息只会发送至一个消费者,它会在与发送者相同的线程中调用消费者。这种方式允许事务跨通道。
- ExecutorChannel:类似于DirectChannel,但是消息分发是通过TaskExecutor实现的,这样会在与发送者独立的线程中执行。这种通道类型不支持事务跨通道。
- FluxMessageChannel:反应式流的发布者消息通道,基于Reactor项目的Flux。(我们将会在第10章讨论反应式流、Reactor和Flux。)
二、SpringBoot中导入依赖
通常来讲,在使用Spring Integration创建集成流时,是通过声明一个应用程序能够接收或发送哪些数据到应用程序之外的资源来实现的。应用程序可能集成的资源之一就是文件系统。因此,Spring Integration的很多组件都有读入和写入文件的通道适配器(channel adapter)
为了熟悉Spring Integration,我们将会创建一个集成流,这个流会写入数据到文件系统中。首先,我们需要添加Spring Integration到项目的构建文件中。对于Maven构建来讲,必要的依赖如下所示
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<!-- 文件端点模块,提供了将文件从系统文件导入集成流/或将流中的数据写入文件的能力 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-file</artifactId>
</dependency>
第一项依赖是Spring Integration的Spring Boot starter。不管我们与哪种流进行交互,对于Spring Integration流的开发来讲,这个依赖都是必需的。与所有的Spring Boot starter一样,在Initializr表单中,这个依赖也可以通过复选框进行选择。
第二项依赖是Spring Integration的文件端点模块。我们只需要知道文件端点模块提供了将文件从文件系统导入集成流和/或将流中的数据写入文件系统的能力即可。
三、使用Spring Intergration创建集成流
首先我们需要创建一个能发送数据到集成流中的方法
package sia5;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.file.FileHeaders;
import org.springframework.messaging.handler.annotation.Header;
//声明消息网关
@MessagingGateway(defaultRequestChannel="textInChannel")
public interface FileWriterGateway {
void writeToFile(
@Header(FileHeaders.FILENAME) String filename, String data);
}
首先我们这里使用了@MessagingGateway注解,这个注解会告诉Spring Integration要在运行时生成该接口的实例。
@MessagingGateway的defaultRequestChannel属性表明接口方法调用时所返回的消息要发送至给定的消息通道(message channel)。在本例中,我们声明调用writeToFile()所形成的消息应该发送至名为textInChannel的通道中。
对于writeToFile()方法来说,它以String类型的形式接受一个文件名,另外一个String包含了要写入文件中的文本。关于这个方法的签名,还需要注意filename参数上带有@Header。在本例中,@Header注解表明传递给filename的值应该包含在消息头信息中(通过FileHeaders.FILENAME声明,它将会被解析成file_name),而不是放到消息载荷(payload)中。
现在,我们已经有了消息网关,接下来就需要配置集成流了。尽管我们往构建文件中添加的Spring Integration starter依赖能够启用Spring Integration的自动配置功能,但是满足应用需求的流定义则需要我们自行编写额外的配置。在声明集成流方面,我们有3种配置方案可供选择:
- 配置
- Java配置
- 使用DSL的Java配置
我们会依次看一下Spring Integration的这3种配置风格,较为老式的XML配置现在已经越来越少人使用了,我们直接从java配置开始。
1. 使用java配置集成流
package sia5;
import java.io.File;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.file.FileWritingMessageHandler;
import org.springframework.integration.file.dsl.Files;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.transformer.GenericTransformer;
@Configuration
public class FileWriterIntegrationConfig {
@Bean
@Transformer(inputChannel="textInChannel",outputChannel="fileWriterChannel")
public GenericTransformer<String, String> upperCaseTransformer() {
return text -> text.toUpperCase();
}
@Bean
@ServiceActivator(inputChannel="fileWriterChannel")
public FileWritingMessageHandler fileWriter() {
FileWritingMessageHandler handler =new FileWritingMessageHandler(new File("/tmp/sia5/files"));
handler.setExpectReply(false);
handler.setFileExistsMode(FileExistsMode.APPEND);
handler.setAppendNewLine(true);
return handler;
}
}
在Java配置中,我们声明了两个bean:一个转换器,还有一个文件写入的消息处理器。
转换器:这里的转换器是GenericTransformer。因为GenericTransformer是一个函数式接口,所以我们可以使用lambda表达式为其提供实现,这里调用了消息文本的toUpperCase()方法。转换器bean使用了@Transformer注解,这样会将其声明成集成流中的一个转换器,它接受来自textInChannel通道的消息,然后将消息写入到名为fileWriterChannel的通道中。
文件写入的消息处理器:负责文件写入的bean则使用了@ServiceActivator注解,表明它会接受来自fileWriter Channel的消息,并且会将消息传递给FileWritingMessageHandler实例所定义的服务。FileWritingMessageHandler是一个消息处理器,它会将消息的载荷写入特定目录的一个文件中,而文件的名称是通过消息的file_name头信息指定的。与XML样例类似,FileWritingMessageHandler也配置为以新行的方式为文件追加内容。
FileWritingMessageHandler bean的一个独特之处在于它调用了setExpectReply(false)方法,通过这个方法能够告知服务激活器(serviceactivator)不要期望会有答复通道(reply channel,通过这样的通道,我们可以将某个值返回到流中的上游组件)。如果我们不调用setExpectReply(),文件写入bean的默认值是true。尽管管道的功能和预期一样,但是在日志中会看到一些错误日志,提示我们没有设置答复通道。
2. 使用Spring Integration的DSL配置集成流
我们再次尝试一下文件写入集成流的定义。这一次,我们依然使用Java进行定义,但是会使用Spring Integration的Java DSL。这一次我们不再将流中的每个组件都声明为单独的bean,而是使用一个bean来定义整个流
package sia5;
import java.io.File;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.file.FileWritingMessageHandler;
import org.springframework.integration.file.dsl.Files;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.transformer.GenericTransformer;
@Configuration
public class FileWriterIntegrationConfig {
@Bean
public IntegrationFlow fileWriterFlow() {
return IntegrationFlows
.from(MessageChannels.direct("textInChannel"))
.<String, String>transform(t -> t.toUpperCase())
.handle(Files
.outboundAdapter(new File("/tmp/sia5/files"))
.fileExistsMode(FileExistsMode.APPEND)
.appendNewLine(true))
.get();
}
}