官方文档
顶层接口,返回true即为发送成功,返回false即为发送失败或者超时。
public interface MessageChannel {
boolean send(Message message);
boolean send(Message message, long timeout);
}
轮询接口,返回一个消息,超时或者打断返回null。会缓冲消息。
public interface PollableChannel extends MessageChannel {
Message> receive();
Message> receive(long timeout);
}
订阅接口,直接发送消息到处理器。不轮询,不会缓冲消息。
public interface SubscribableChannel extends MessageChannel {
boolean subscribe(MessageHandler handler);
boolean unsubscribe(MessageHandler handler);
}
通道实现,实现了上述接口的主要通道有:
拦截器,拦截通道的消息,加入自定义过程
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> message, MessageChannel channel);
void afterReceiveCompletion(Message> message, MessageChannel channel, Exception ex);
}
通道添加拦截器
channel.addInterceptor(someChannelInterceptor);
消息模板,提供非倾入式的消息分发接口
MessagingTemplate template = new MessagingTemplate();
Message reply = template.sendAndReceive(someChannel, new GenericMessage("test"));
还有一些其他的交互方法
public boolean send(final MessageChannel channel, final Message> message) { ...
}
public Message> sendAndReceive(final MessageChannel channel, final Message> request) { ...
}
public Message> receive(final PollableChannel> channel) { ...
}
配置消息通道,各种通道的个性化配置
特殊通道
本章节介绍轮询怎么工作。
轮询消费者
当一个消息端点(通道适配器)连接到适配器时,会产生其中的一个消费者实例:
具体的实现取决于端点所连接的通道类型。当连接到SubscribableChannel时,会生成一个EventDrivenConsumer实例;而连接到PollableChannel时,会产生一个PollingConsumer实例。
轮询消费者使得Spring Integration组件以轮询而不是以事件驱动的方式处理消息。
它们代表了在很多消息场景中出现的关键交叉问题。在Spring Integration中,轮询消费模式基于Gregor Hohpe and Bobby Woolf的书中的Enterprise Integration Patterns的概念。
可轮询的消息源
Spring Integration提供轮询消费模式的第二个变体,用在输入通道适配器中,这些适配器通常被包装为SourcePollingChannelAdapter。比如,从一个远程FTP服务器检索消息时,FTP Inbound Channel Adapter提供的适配器提供了一个轮询器周期性地检索消息。所以,当一个组件配置了轮询器时,最终的实例是下面的一种:
这意味者轮询器既可以同时使用在输入和输出场景中。下面是轮询器的一些使用场景:
延迟确认的轮询消息源
从5.0.1版本开始,某些模块提供MessageSource 实现支持延迟确认直到下游数据完成(或者在其他线程处理消息)。这个目前只能在spring-integration-kafka模块提供的AmqpMessageSource和KafkaMessageSource上使用。
使用这些消息源时,IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK消息头(参考MessageHeaderAccessor)被添加到消息。消息头的值是一个AcknowledgmentCallback实例,如下所示:
@FunctionalInterface
public interface AcknowledgmentCallback {
void acknowledge(Status status);
boolean isAcknowledged();
void noAutoAck();
default boolean isAutoAck();
enum Status {
/**
* Mark the message as accepted.
*/
ACCEPT,
/**
* Mark the message as rejected.
*/
REJECT,
/**
* Reject the message and requeue so that it will be redelivered.
*/
REQUEUE
}
}
不是所有消息源(如Kafka)都支持REJECT状态。被认为是和ACCEPT等效。
程序可以在任何时间确认消息,如下面显示的那样:
Message> received = source.receive();
...
StaticMessageHeaderAccessor.getAcknowledgmentCallback(received)
.acknowledge(Status.ACCEPT);
如果一个MessageSource连接到SourcePollingChannelAdapter。当轮询线程的下游数据流结束并返回到适配器时,适配器确认状态是否被确认,如果没有,设置状态为ACCEPT(或者REJECT如果下游抛出异常)。状态的值定义在AcknowledgmentCallback.Status枚举中。
Spring Integration提供MessageSourcePollingTemplate实现对MessageSource的点对点轮询。同样,当MessageHandler返回(或者抛出异常时)时,小心设置AcknowledgmentCallback的ACCEPT和REJECT状态。下面例子演示如果使用MessageSourcePollingTemplate轮询:
MessageSourcePollingTemplate template =
new MessageSourcePollingTemplate(this.source);
template.poll(h -> {
...
});
两种用例中(SourcePollingChannelAdapter 和MessageSourcePollingTemplate),可以在回调上调用noAutoAck()来禁用自动ack/nack。当需要在其他线程处理消息或者延迟确认的时候,可以这么做。并不是所有的实现都支持(比如Apache Kafka就不支持,因为必须在同一个线程提交offset)。
条件轮询器
轮询器中advice-chain里的Advice对象,切入整个轮询任务(包括消息检索和处理)。这些“环绕advice”方法没有切入轮询所有上下文的权限,只能切入轮询本身。如前面所讲,对于像事务化一个任务或由于某些外部条件跳过轮询的需求是没有问题的。但是如果想根据轮询的receive返回值采取某些操作或根据条件调整轮询器呢?对于这些场景,Spring Integration提供“智能”轮询。
版本4.2引入AbstractMessageSourceAdvice。任何在advice-chain中继承了该类的Advice对象只作用于receive操作。这些类实现了以下方法:
线程安全
如果一个advice转换一个MessageSource,切记不要使用TaskExecutor配置轮询器。如果一个advice转换数据源,这些转换不是线程安全的并且可能导致意外的结果,特别是高频率的轮询器。如果需要并发地处理轮询结果,考虑使用一个下游的ExecutorChannel而不是添加一个executor到轮询器中。
Advice链的顺序
首先需要理解advice链在初始化时是如何处理的。没有继承AbstractMessageSourceAdvice的Advice对象作用域整个轮询过程并且最先执行,比任何AbstractMessageSourceAdvice对象都早。AbstractMessageSourceAdvice对象在MessageSource的receive()方法周围顺序执行。假设,有四个Advice对象a,b,c,d,其中b和d是AbstractMessageSourceAdvice,这些对象按以下顺序作用:a,c,b,d。另外,如果MessageSource已经是一个Proxy,AbstractMessageSourceAdvice会在任何存在的Advice对象之后执行。如果要改变顺序,必须手动装配proxy。
SimpleActiveIdleMessageSourceAdvice
该advice是AbstractMessageSourceAdvice的一个简单实现。和DynamicPeriodicTrigger配合使用,用来调整轮询频率,取决于上次的轮询是否有新消息。轮询器必须引用同一个DynamicPeriodicTrigger。
重要:异步切换
SimpleActiveIdleMessageSourceAdvice根据receive()方法的返回值修改触发器。只有当advice在轮询线程调用时才起作用。如果轮询器有一个task-executor时不起作用。如果想在轮询返回结果后使用异步操作时,可以在之后进行异步切换,比如使用一个ExecutorChannel。
CompoundTriggerAdvice
该advice根据轮询返回消息与否选择两个触发器的一个。考虑一个轮询器使用CronTrigger。CronTrigger实例是不可更改的,所以一旦创建就不能修改了。考虑一个使用场景:使用一个cron表达式触发每个小时一次的轮询,但是,如果没有消息到达时,每分钟轮询一次,但当检索到消息时,回退使用cron表达式。
该advice(和轮询器)使用CompoundTrigger来达到这个目的。这个触发器开始是一个CronTrigger。当adivce监测到没有消息检索到时,就添加第二个触发器到CompoundTrigger。当CompoundTrigger实例的nextExecutionTime方法调用第二个触发器时,advice代理到第二个触发器,否则,代理到第一个触发器。
轮询器也必须引用同一个CompoundTrigger。
下面的例子显示了每小时的cron表达式,报错是每分钟的配置:
重要:异步切换
CompoundTriggerAdvice根据receive()方法的返回值修改触发器。只有当advice在轮询线程调用时才起作用。如果轮询器有一个task-executor时不起作用。如果想在轮询返回结果后使用异步操作时,可以在之后进行异步切换,比如使用一个ExecutorChannel。