Spring Integration提供了基于Spring的EIP(Enterprise Integration Patterns,企业集成模式的实现。Spring Integration主要解决的问题是不同系统之间交互的问题,通过异步消息驱动来达到系统交互时系统之间的松耦合。本节将基于无XML配置的原则使用Java配置、注解以及Spring Integration Java DSL来使用Spring Integration。
Message是用来在不同部分之间传递的数据。Message由两部分组成:消息体(payload)与消息头(header)。消息体可以是任何数据类型(如XML、JSON,Java对象);消息头表示的元数据就是解释消息体的内容。
public interface Message {
T getPayload();
MessageHeaders getHeaders();
}
在消息系统中,消息发送者发送消息到通道(Channel),消息收受者从通道(Channel)接收消息。
MessageChannel是Spring Integration消息通道的顶级接口:
public interface MessageChannel{
public static final long INDEFINITE_TIMEOUT = -1;
boolean send(Message>message);
boolean send(Message>message,long timeout);
}
当使用send方法发送消息时,返回值为true,则表示消息发送成功。MessageChannel有两大子接口,分别为PollableChannel(可轮询)和SubscribableChannel(可订阅)。我们所有的消息通道类都是实现这两个接口。
PollableChannel具备轮询获得消息的能力,定义如下:
public interface PollableChannel extends MessageChannel {
Message> receive();
Message> receive(long timeout);
}
SubscribableChannel发送消息给订阅了MessageHanlder的订阅者:
public interface SubscribableChannel extends MessageChannel{
boolean subscibe(MessageHandler handler);
boolean unsubscribe(MessageHandler handler);
}
PublishSubscribeChannel允许广播消息给所有订阅者,配置方式如下:
@Bean
public PublishSubscribeChannel publishSubscribeChannel() {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
return channel;
}
其中,当前消息通道的id为publishSubscribeChannel。
(2)QueueChannel
QueueChannel允许消息接收者轮询获得信息,用一个队列(queue)接收消息,队列的容量大小可配置,配置方式如下:
@Bean
public QueueChannel queueChannel(){
QueueChannel channel = new QueueChannel(10);
return channel
}
其中QueueChannel构造参数10即为队列的容量。
PriorityCHannel可按照优先级将数据存储到对,它依据于消息的消息头priority属性,配置方式如下:
@Bean
public PriorityChannel priorityChannel(){
PriorityChannel channel = new PriorityChannel(10);
return channel;
}
RendezvousChannel确保每一个接收者都接收到消息后再发送消息,配置方式如下:
@Bean
public RendezvousChannel rendezvousChannel(){
RendezvousChannel channel = new RendezvousChannel();
return channel;
}
DirectChannel是Spring Integration默认的消息通道,它允许将消息发送给一个订阅者,然后阻碍发送直到消息被接收,配置方式如下
@Bean
public DirectChannel directChannel(){
DirectChannel channel = new DirectChannel();
return channel;
}
ExecutorChannel可绑定一个多线程的task executor,配置方式如下:
@Bean
public ExecutorChannel executorChannel(){
ExecutorChannel channel = new ExecutorChannel(executor());
return channel;
}
@Bean
public Executor executor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(25);
taskExecutor.initialize();
return taskExecutor;
}
Spring Integration给消息通道提供了通道拦截器(ChannelInterceptor),用来拦截发送和接收消息的操作。
ChannelInterceptor接口定义如下,我们只需实现这个接口即可:
public interface ChannelInterceptor {
Message>preSend(Message>message,MessageChannel channel);
void postSend(Message>message,MessageChannel channel,boolean sent);
void afterSendCompletion(Message>message,MessageChannel channel,boolean sent,Exception ex);
boolean preReceive(MessageChannel channel);
Message>postReceive(Message>messaeg,MessageChannel channel);
void afterReceiveCompletion(Message>message,MessageChannel channel,Exception ex);
}
我们通过下面的代码给所有的channel增加拦截器:
channel.addInterceptor(someInterceptor);
消息端点(Message Endpoint)是真正处理消息的(Message)组件,它还可以控制通道的路由。我们可用的消息端点包含如下:
通道适配器(Channel Adapter)是一种连接外部系统或传输协议的端点(EndPoint),可以分入站(inbound)和出站(outbound)。
通道适配器是单向的,入站通道适配器只支持接收消息,出站通道适配器只支持输出消息。
Spring Integration内置如下的适配器:
RabbitMQ、Feed、File、FTPd/SFTP、Gemfire、HTTP、TCP/UDP、JDBC、JPA、JMS、Mail、MongoDB、Redis、RMI、Twitter、XMPP、WebServices(SOAP、RESTaurant)、WebSocket等。
消息网关(Gateway)类似于Adapter,但是提供了双向的请求/返回集成方式,也分为入站(inbound)和出站(outbound)。Spring Integration对相应的Adapter多都提供了Gatew。
Service Activator可调用Spring的Bean来处理消息,并将处理后的结果输出到指定的消息通道。
路由(Router)可根据消息体类型(Payload Type Router)、消息头的值(Header Value Router)以及定义好的接收表(Recipient List Router)作为条件,来决定消息传递到的通道。
过滤器(Filter)类似于路由(Router),不同的是过滤器不决定消息路由到哪里,而是决定消息是否可以传递给消息通道。
拆分器(Splitter)将消息拆分为几个部分单独处理,拆分器处理的返回值是一个集合或者数组。
聚合器(Aggregator)与拆分器相反,它接收一个java.util.List作为参数,将多个消息合并为一个消息。
当我们从外部获得消息后,需要增加额外的消息到已有的消息中,这时就需要使用消息增强器(Enricher)。消息增强器主要有消息体增强器(Payload Enricher)和消息头增强器(Header Enricher)两种。
转换器(Transformer)是地获得的消息进行一定的逻辑转换处理(如数据格式转换)。
使用连接桥(Bridge)可以简单地将两个消息通道连接起来。
Spring Integration提供了一个IntegrationFlow来定义系统继承流程,而通过IntegrationFlows和IntegrationFlowBuilder来实现使用Fluent API来定义流程。在Fulent API里,分别提供了下面方法来映射Spring Integration的端点(EndPoint)。
transform() -> Transformer
filter() -> Filter
handle() -> ServiceActivator、Adapter、Gateway
split() -> Splitter
aggregate() -> Aggregator
route() -> Router
bridge() -> Bridge
一个简单的流程定义如下:
@Bean
public IntegrationFlow demoFlow(){
return IntegrationFlows.from("input") //从Channel input获取消息
.transform(Integer::parseint) //将消息转换成整数
.get(); //获得集成流程并注册为Bean
}
本章将演示读取https://spring.io/blog.atom的新闻聚合文件,atom是一种xml文件,且格式是固定的,示例如下:
Spring
http://spring.io/blog.atom
https://spring.io/favicon.ico
2015-07-29T14:46:00z
Spring Cloud Connectors 1.2.0 released
some author
tag:spring.io,2015-07-27:2196
2015-07-29T14:46:00z
...
我们将读取到消息通过分类(Category),将消息转到不同的消息通道,将分类为releases和engineering的消息写入磁盘文件,将分类为news的消息通过邮件发送。
新建Spring Boot项目,依赖为Integration(spring-boot-starter-integration)和mail(spring-boot-starter-mail)。
项目信息
groupId:com.wisely
arctifactId:ch9_4
package:com.wisely.ch9_4
另外,我们还要添加Spring Integration对atom及mail的支持。
4.0.0
org.springframework.boot
spring-boot-starter-parent
1.5.20
com.wisely
ch9_4
0.0.1-SNAPSHOT
ch9_3_5-1
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-integration
org.springframework.boot
spring-boot-starter-mail
org.springframework.boot
spring-boot-starter-test
test
org.springframework.integration
spring-integration-feed
org.springframework.integration
spring-integration-mail
org.springframework.integration
spring-integration-java-dsl
1.2.3.RELEASE
org.springframework.integration
spring-integration-file
org.springframework.boot
spring-boot-maven-plugin
本例所有的代码都在入口类中完成。
package com.wisely.ch9_4;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.feed.inbound.FeedEntryMessageSource;
import org.springframework.integration.scheduling.PollerMetadata;
import com.rometools.rome.feed.synd.SyndEntry;
@SpringBootApplication
public class Ch9351Application {
@Value("https://spring.io/blog.atom") //通过@Value注解自动获得https://spring.id/blog.atom的资源
Resource resource;
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() { //使用Fluent API和Pollers配置默认的轮询方式
return Pollers.fixedRate(500).get();
}
@Bean
public FeedEntryMessageSource feedEntryMessageSource()throws IOException{ //FeedEntryMessageSource实际为feed:inbound-channel-adapter,此处即构造feed的入站通道适配器作为数据输入
FeedEntryMessageSource messageSource = new FeedEntryMessageSource(resource.getURL(), "news");
return messageSource;
}
@Bean
public IntegrationFlow myFlow() throws IOException {
return IntegrationFlows.from(feedEntryMessageSource()) //流程从from方法开始
.route(payload ->
payload.getCategories().get(0).getName(), //通过路由方法route来选择路由,消息体(payload)的类型为SyndEntry,作为判断条件的类型为String,判断的值是通过payload获得的分类(Categroy);
mapping -> mapping.channelMapping("releases",
"releasesChannel") //通过不同分类的值转向不同的消息通道,若分类为releases,则转向releasesChannel;
.channelMapping("engineering",
"engineeringChannel")
.channelMapping("news","newsChannel"))
.get(); //通过get方法获得IntegrationFlow实体,配置为Spring的Bean。
}
public static void main(String[] args) {
SpringApplication.run(Ch9351Application.class, args);
}
}
@Bean
public IntegrationFlow releasesFlow() {
return IntegrationFlows.from(MessageChannels.queue("releasesChannel", 10)) //从消息通道releasesChannel开始获取数据。
. transform(
payload -> "《" + payload.getTitle() + "》 " + payload.getLink() + getProperty("line.separator")) //使用transform方法进行数据转换。payload类型为SyndEntry,将其转换为字符串类型,并自定义数据的格式。
.handle(Files.outboundAdapter(new File("e:/springblog")) //用handle方法处理file的出站适配器。Files类是由Spring Integration Java DSL提供的 Fluent API用来构造文件输出的适配器。
.fileExistsMode(FileExistsMode.APPEND)
.charset("UTF-8")
.fileNameGenerator(message -> "releases.txt")
.get())
.get();
}
@Bean
public IntegrationFlow engineeringFlow() {
return IntegrationFlows.from(MessageChannels.queue("engineeringChannel", 10))
. transform(
payload -> "《" + payload.getTitle() + "》" + payload.getLink() + getProperty("line.separator"))
.handle(Files.outboundAdapter(new File("e:/springblog"))
.fileExistsMode(FileExistsMode.APPEND)
.charset("UTF-8")
.fileNameGenerator(message -> "engineering.txt")
.get())
.get();
}
}
@Bean
public IntegrationFlow newsFlow() {
return
IntegrationFlows.from(MessageChannels.queue("newsChannel",10))
.transform(
payload -> "《" + payload.getTitle() + "》" +
payload.getLink() + getProperty("line.separator"))
.enrichHeaders( //通过enricherHeader来增加消息头的信息
Mail.headers()
.subject("来自Spring的新闻")
.to("[email protected]")
.from("[email protected]"))
.handle(Mail.outboundAdapter("smtp.qq.com") //邮件发送的相关信息通过Spring Integration JavaDSL提供的Mail的headers方法来构造。
.port(25)
.protocol("smtp")
.credentials("[email protected]", "******") //使用handle方法来定义邮件发送的出站适配器,使用Spring Integration Java DSL提供的Mail。来构造,这里使用[email protected]给自己发送邮件
.javaMailProperties(p -> p.put("mail.debug", false)),
e -> e.id("smtpOut"))
.get();
}
查看E:\springblog目录,发现多了两个文件,如图
engineering.txt文件内容如图
releases.txt文件内容如图
(2)邮箱接收结果
登录邮箱可以看取刚才发送的邮件