Spring Integration 核心消息传递_拦截器

本节涵盖了 Spring 集成中核心消息传递 API 的所有方面。 它涵盖消息、消息通道和消息端点。 它还涵盖了许多企业集成模式,例如过滤器、路由器、转换器、服务激活器、拆分器和聚合器。

本节还包含有关系统管理的材料,包括控制总线和消息历史记录支持。

消息传递渠道

消息通道

虽然它起着封装数据的关键作用,但它将消息生产者与消息消费者分离。​​Message​​​​MessageChannel​

消息通道接口

Spring 集成的顶级接口定义如下:​​MessageChannel​

public interface MessageChannel {

boolean send(Message message);

boolean send(Message message, long timeout);
}

发送消息时,返回值为消息发送成功。 如果发送调用超时或中断,它将返回。​​true​​​​false​

​PollableChannel​

由于消息通道可能会也可能不会缓冲消息(如Spring 集成概述中所述),因此两个子接口定义了缓冲(可轮询)和非缓冲(可订阅)通道行为。 以下清单显示了接口的定义:​​PollableChannel​

public interface PollableChannel extends MessageChannel {

Message<?> receive();

Message<?> receive(long timeout);

}

与 send 方法一样,在接收消息时,在超时或中断的情况下,返回值为 null。

​SubscribableChannel​

基本接口由将消息直接发送到其订阅实例的通道实现。 因此,它们不提供用于轮询的接收方法。 相反,它们定义了管理这些订阅者的方法。 以下清单显示了接口的定义:​​SubscribableChannel​​​​MessageHandler​​​​SubscribableChannel​

public interface SubscribableChannel extends MessageChannel {

boolean subscribe(MessageHandler handler);

boolean unsubscribe(MessageHandler handler);

}

消息通道实现

Spring 集成提供了不同的消息通道实现。 以下各节简要介绍了每个部分。

​PublishSubscribeChannel​

该实现将发送给它的任何发送到其所有订阅的处理程序。 这最常用于发送事件消息,其主要角色是通知(与文档消息相反,文档消息通常由单个处理程序处理)。 请注意,这仅用于发送。 由于它在调用其 smethod 时直接向其订阅者广播,因此使用者无法轮询消息(它不实现,因此没有方法)。 相反,任何订阅者本身都必须是 a,并且依次调用订阅者的方法。​​PublishSubscribeChannel​​​​Message​​​​PublishSubscribeChannel​​​​send(Message)​​​​PollableChannel​​​​receive()​​​​MessageHandler​​​​handleMessage(Message)​

在 3.0 版之前,调用该方法没有订阅者返回。 当与 a 一起使用时,awas 抛出。 从版本 3.0 开始,行为已更改,如果至少存在最小订阅者(并成功处理消息),则始终认为 ais 成功。 可以通过设置属性来修改此行为,该属性默认为。​​send​​​​PublishSubscribeChannel​​​​false​​​​MessagingTemplate​​​​MessageDeliveryException​​​​send​​​​minSubscribers​​​​0​

如果使用 ,则仅使用正确数量的订阅者进行此确定,因为消息的实际处理是异步执行的。​​TaskExecutor​

​QueueChannel​

该实现包装了一个队列。 与不同,它具有点对点语义。 换句话说,即使通道有多个使用者,也只有其中一个使用者应接收发送到该通道的任何内容。 它提供了一个默认的无参数构造函数(提供基本上无限的容量)以及一个接受队列容量的构造函数,如下面的清单所示:​​QueueChannel​​​​PublishSubscribeChannel​​​​QueueChannel​​​​Message​​​​Integer.MAX_VALUE​

public QueueChannel(int capacity)

未达到其容量限制的通道将消息存储在其内部队列中,该方法会立即返回,即使没有接收方准备好处理消息也是如此。 如果队列已达到容量,则发送方将阻止,直到队列中有可用空间。 或者,如果使用具有附加超时参数的 send 方法,则队列将一直阻塞,直到任一房间可用或超时期限过去(以先发生者为准)。 同样,如果队列中有消息可用,则 acall 会立即返回,但如果队列为空,则接收调用可能会阻塞,直到消息可用或超时(如果提供)结束。 在任一情况下,都可以通过传递超时值 0 来强制立即返回,而不管队列的状态如何。 但是请注意,对 andwith noparameter 版本的调用会无限期地阻止。​​send(Message<?>)​​​​receive()​​​​send()​​​​receive()​​​​timeout​

​PriorityChannel​

虽然强制实施先进先出 (FIFO) 排序,但这是一种替代实现,允许根据优先级在通道内对消息进行排序。 默认情况下,优先级由每条消息中的标头确定。 但是,对于自定义优先级确定逻辑,可以向构造函数提供类型的比较器。​​QueueChannel​​​​PriorityChannel​​​​priority​​​​Comparator<Message<?>>​​​​PriorityChannel​

​RendezvousChannel​

启用“直接切换”方案,其中发送方阻止,直到另一方调用通道的方法。 另一方阻止,直到发件人发送消息。 在内部,这种实现与 非常相似,只是它使用了 a(一种零容量实现)。 这在发送方和接收方在不同的线程中操作,但不合适的是异步删除队列中的消息的情况下,这很有效。 换句话说,使用 a,发送方知道某个接收方已接受消息,而使用 a,消息将被存储到内部队列中,并且可能永远不会收到。​​RendezvousChannel​​​​receive()​​​​QueueChannel​​​​SynchronousQueue​​​​BlockingQueue​​​​RendezvousChannel​​​​QueueChannel​

请记住,默认情况下,所有这些基于队列的通道仅将消息存储在内存中。 当需要持久性时,您可以在“queue”元素中提供“消息存储”属性来引用持久性实现,也可以将本地通道替换为由持久性代理支持的通道,例如 JMS 支持的通道或通道适配器。 后一个选项允许您利用任何 JMS 提供程序的消息持久性实现,如JMS 支持​中所述。 但是,当不需要在队列中进行缓冲时,最简单的方法是依赖 ,在下一节中讨论。​​MessageStore​​​​DirectChannel​

这对于实现请求-答复操作也很有用。 发送方可以创建一个临时的匿名实例,然后在构建时将其设置为“replyChannel”标头。 发送后,发送方可以立即调用(可选地提供超时值)以便在等待回复时阻止。 这与许多Spring Integration的请求-回复组件内部使用的实现非常相似。​​RendezvousChannel​​​​RendezvousChannel​​​​Message​​​​Message​​​​receive​​​​Message​

​DirectChannel​

具有点对点语义,但在其他方面比前面描述的任何基于队列的通道实现更相似。 它实现接口而不是接口,因此它将消息直接调度给订阅者。 但是,作为点对点通道,它与将每个通道发送给单个订阅者的不同之处在于。​​DirectChannel​​​​PublishSubscribeChannel​​​​SubscribableChannel​​​​PollableChannel​​​​PublishSubscribeChannel​​​​Message​​​​MessageHandler​

除了是最简单的点对点通道选项之外,它最重要的功能之一是它使单个线程能够在通道的“两侧”执行操作。 例如,如果处理程序订阅了 a,则向该通道发送 ato 会直接在发送方的线程中触发对该处理程序的方法的调用,然后该方法调用才能返回。​​DirectChannel​​​​Message​​​​handleMessage(Message)​​​​send()​

提供具有此行为的通道实现的关键动机是支持必须跨越通道的事务,同时仍受益于通道提供的抽象和松散耦合。 如果在事务范围内调用 thecall,则处理程序调用的结果(例如,更新数据库记录)在确定该事务的最终结果(提交或回滚)方面发挥作用。​​send()​

由于这是最简单的选项,并且不会增加调度和管理轮询器线程所需的任何额外开销,因此它是 Spring 集成中的默认通道类型。 一般的想法是定义应用程序的通道,考虑哪些通道需要提供缓冲或限制输入,并修改这些通道以基于队列。 同样,如果一个频道需要广播消息,它不应该是a而是a。 稍后,我们将展示如何配置这些通道中的每一个。​​DirectChannel​​​​PollableChannels​​​​DirectChannel​​​​PublishSubscribeChannel​

内部委托给消息调度程序以调用其订阅的消息处理程序,并且该调度程序可以按属性(互斥)公开负载平衡策略。 消息调度程序使用负载平衡策略来帮助确定当多个消息处理程序订阅同一通道时如何在消息处理程序之间分发消息。 为方便起见,该属性公开了指向预先存在的实现的值枚举。 A(轮换处理程序之间的负载平衡)和(对于想要显式禁用负载平衡的情况)是唯一可用的值。 将来的版本中可能会添加其他策略实现。 但是,从版本 3.0 开始,您可以使用 theattribute 提供自己的实现和注入它,该属性应指向实现的 Bean,如以下示例所示:​​DirectChannel​​​​load-balancer​​​​load-balancer-ref​​​​load-balancer​​​​LoadBalancingStrategy​​​​round-robin​​​​none​​​​LoadBalancingStrategy​​​​load-balancer-ref​​​​LoadBalancingStrategy​

仅支持无法取消订阅的单个订阅者。 这对于不涉及其他订阅者且不需要通道拦截器的高吞吐量性能用例非常有用。​​FixedSubscriberChannel​​​​SubscribableChannel​​​​MessageHandler​

<int:channel id="lbRefChannel">
<int:dispatcher load-balancer-ref="lb"/>
</int:channel>

<bean id="lb" class="foo.bar.SampleLoadBalancingStrategy"/>

请注意,和属性是互斥的。​​load-balancer​​​​load-balancer-ref​

负载平衡还与布尔属性结合使用。 如果值为 true(默认值),则在前面的处理程序引发异常时,调度程序将回退到任何后续处理程序(如有必要)。 顺序由处理程序本身定义的可选订单值确定,或者,如果不存在此类值,则由处理程序订阅的顺序确定。​​failover​​​​failover​

如果某种情况要求调度程序始终尝试调用第一个处理程序,然后在每次发生错误时以相同的固定顺序回退,则不应提供负载平衡策略。 换句话说,即使未启用负载平衡,调度程序仍支持布尔属性。 但是,如果没有负载平衡,处理程序的调用始终根据其顺序从第一个开始。 例如,当对主要、次要、第三等有明确的定义时,此方法非常有效。 使用命名空间支持时,任何终结点上的属性都会确定顺序。​​failover​​​​order​

请记住,仅当通道具有多个订阅的消息处理程序时,才会应用负载平衡和应用。 使用命名空间支持时,这意味着多个终结点共享属性中定义的相同通道引用。​​failover​​​​input-channel​

从版本 5.2 开始,如果为 true,则记录当前处理程序的故障以及失败的消息,或者如果分别配置。​​failover​​​​debug​​​​info​

​ExecutorChannel​

这是一个点对点通道,支持与(负载平衡策略和布尔属性)相同的调度程序配置。 这两种调度通道类型之间的主要区别在于委托给实例来执行调度。 这意味着 send 方法通常不会阻塞,但也意味着处理程序调用可能不会在发送方的线程中发生。 因此,它不支持跨越发送方和接收方处理程序的事务。​​ExecutorChannel​​​​DirectChannel​​​​failover​​​​ExecutorChannel​​​​TaskExecutor​

发件人有时可能会阻止。 例如,当使用 awith 限制客户端(如 )的拒绝策略时,只要线程池达到其最大容量并且执行程序的工作队列已满,发送方的线程就可以执行该方法。 由于这种情况只会以不可预测的方式发生,因此您不应依赖它进行交易。​​TaskExecutor​​​​ThreadPoolExecutor.CallerRunsPolicy​

​FluxMessageChannel​

这些实现将发送的消息引入内部供下游反应式订阅者按需消费。 此通道实现既不是 a,也不是 a,因此只能使用实例从此通道使用,以尊重反应流的背压性质。 另一方面,通过其合约实现,允许从反应式源发布者接收事件,将反应式流桥接到集成流中。 要实现整个集成流的完全反应行为,必须在流中的所有端点之间放置这样的通道。​​FluxMessageChannel​​​​org.reactivestreams.Publisher​​​​"sinking"​​​​reactor.core.publisher.Flux​​​​SubscribableChannel​​​​PollableChannel​​​​org.reactivestreams.Subscriber​​​​FluxMessageChannel​​​​ReactiveStreamsSubscribableChannel​​​​subscribeTo(Publisher<Message<?>>)​

有关与反应式流交互的更多信息,请参阅反应式流支持。

作用域通道

Spring Integration 1.0 提供了一个实现,但从 2.0 开始已被删除。 现在,处理相同需求的更通用方法是向通道添加属性。 属性的值可以是上下文中可用的范围的名称。 例如,在 Web 环境中,某些范围可用,并且可以向上下文注册任何自定义范围实现。 以下示例显示了应用于通道的线程本地作用域,包括作用域本身的注册:​​ThreadLocalChannel​​​​scope​

<int:channel id="threadScopedChannel" scope="thread">
<int:queue />
</int:channel>

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread" value="org.springframework.context.support.SimpleThreadScope" />
</map>
</property>
</bean>

上一示例中定义的通道也在内部委托给队列,但通道绑定到当前线程,因此队列的内容也类似地绑定。 这样,发送到通道的线程以后可以接收这些相同的消息,但其他线程无法访问它们。 虽然很少需要线程范围的通道,但在实例用于强制实施单个操作线程但任何回复消息都应发送到“终端”通道的情况下,它们非常有用。 如果该终端通道是线程范围的,则原始发送线程可以从终端通道收集其回复。​​DirectChannel​

现在,由于任何通道都可以限定作用域,因此除了 thread-Local 之外,您还可以定义自己的作用域。

通道拦截器

消息传递体系结构的优点之一是能够提供常见行为,并以非侵入性方式捕获有关通过系统的消息的有意义信息。 由于实例是发送到实例和从实例接收的,因此这些通道提供了拦截发送和接收操作的机会。 策略接口(如以下清单所示)为每个操作提供了方法:​​Message​​​​MessageChannel​​​​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<?> message, MessageChannel channel);

void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex);
}

实现接口后,向通道注册拦截器只需进行以下调用:

channel.addInterceptor(someChannelInterceptor);

返回 ainstance 的方法可用于转换 theor 可以返回 'null' 以防止进一步处理(当然,任何方法都可以抛出 a)。 此外,该方法可以返回以防止接收操作继续进行。​​Message​​​​Message​​​​RuntimeException​​​​preReceive​​​​false​

请记住,调用仅与 相关。 事实上,接口甚至没有定义方法。 这样做的原因是,当 ais 发送到 a 时,它会直接发送给零个或多个订阅者,具体取决于通道的类型(例如, 发送给其所有订阅者)。 因此,仅当侦听器应用于 a 时,才会调用 、 和侦听器方法。​​receive()​​​​PollableChannels​​​​SubscribableChannel​​​​receive()​​​​Message​​​​SubscribableChannel​​​​PublishSubscribeChannel​​​​preReceive(…)​​​​postReceive(…)​​​​afterReceiveCompletion(…)​​​​PollableChannel​

弹簧集成还提供了丝锥模式的实现。 它是一个简单的拦截器,可将该通道发送到另一个通道,而不会以其他方式更改现有流。 它对于调试和监视非常有用。 窃听中显示了一个示例。​​Message​

由于很少需要实现所有拦截器方法,因此接口提供了无操作方法(那些返回方法没有代码,返回方法返回the-is,方法返回)。​​void​​​​Message​​​​Message​​​​boolean​​​​true​

侦听器方法的调用顺序取决于通道的类型。 如前所述,基于队列的通道是唯一首先拦截该方法的通道。 此外,发送和接收拦截之间的关系取决于单独的发送方和接收方线程的时间。 例如,如果接收方在等待消息时已被阻止,则顺序可能如下所示:,,,. 但是,如果接收方在发送方将消息放在通道上并已返回后轮询,则顺序如下:,(一段时间后),,. 在这种情况下经过的时间取决于许多因素,因此通常是不可预测的(事实上,接收可能永远不会发生)。 队列的类型也起着作用(例如,会合与优先级)。 简而言之,你不能依赖先于先于先的事实的顺序。​​receive()​​​​preSend​​​​preReceive​​​​postReceive​​​​postSend​​​​preSend​​​​postSend​​​​preReceive​​​​postReceive​​​​preSend​​​​postSend​​​​preReceive​​​​postReceive​

从 Spring Framework 4.1 和 Spring Integration 4.1 开始,提供了新的方法:and。 无论引发的任何异常如何,它们都会在调用后调用,从而允许资源清理。 请注意,通道以与 initialandcall 相反的顺序调用列表上的这些方法。​​ChannelInterceptor​​​​afterSendCompletion()​​​​afterReceiveCompletion()​​​​send()' and 'receive()​​​​ChannelInterceptor​​​​preSend()​​​​preReceive()​

从版本 5.1 开始,全局通道拦截器现在适用于动态注册的通道 - 例如使用 using 或使用 Java DSL 初始化的通过 bean。 以前,在刷新应用程序上下文后创建 Bean 时,不会应用拦截器。​​beanFactory.initializeBean()​​​​IntegrationFlowContext​

此外,从版本 5.1 开始,当未收到消息时不再调用;不再需要检查 A。 以前,调用该方法。 如果拦截器依赖于前面的行为,请实现 place,因为无论是否收到消息,都会调用该方法。​​ChannelInterceptor.postReceive()​​​​null​​​​Message<?>​​​​afterReceiveCompleted()​

从 5.2 版开始,不推荐使用 Spring 消息模块,现在它扩展了该模块以实现向后兼容性。​​ChannelInterceptorAware​​​​InterceptableChannel​

​MessagingTemplate​

当引入端点及其各种配置选项时,Spring 集成为消息传递组件提供了基础,该组件支持从消息传递系统无创地调用应用程序代码。 但是,有时需要从应用程序代码调用消息传递系统。 为了方便实现此类用例,Spring 集成提供了支持跨消息通道的各种操作的 a,包括请求和回复场景。 例如,可以发送请求并等待回复,如下所示:​​MessagingTemplate​

MessagingTemplate template = new MessagingTemplate();

Message reply = template.sendAndReceive(someChannel, new GenericMessage("test"));

在前面的示例中,模板将在内部创建一个临时匿名通道。 也可以在模板上设置“sendTimeout”和“receiveTimeout”属性,并且还支持其他交换类型。 下面的清单显示了此类方法的签名:

public boolean send(final MessageChannel channel, final Message<?> message) { ...
}

public Message<?> sendAndReceive(final MessageChannel channel, final Message<?> request) { ...
}

public Message<?> receive(final PollableChannel<?> channel) { ...
}

一种侵入性较小的方法,允许您调用具有有效负载或标头值而不是实例的简单接口,如输入网关代理工厂Bean​中所述。​​Message​

配置消息通道

要创建消息通道实例,可以使用 xml 元素或 Java 实例配置,如下所示:​​<channel/>​​​​DirectChannel​

@Bean
public MessageChannel exampleChannel() {
return new DirectChannel();
}

当您使用没有任何子元素的元素时,它会创建一个实例 (a)。​​<channel/>​​​​DirectChannel​​​​SubscribableChannel​

要创建发布-订阅通道,请使用元素(在 Java 中),如下所示:​​<publish-subscribe-channel/>​​​​PublishSubscribeChannel​

@Bean
public MessageChannel exampleChannel() {
return new PublishSubscribeChannel();
}

您也可以提供各种子元素来创建任何可轮询通道类型(如消息通道实现中所述)。 以下部分显示了每种通道类型的示例。​​<queue/>​

​DirectChannel​​配置

如前所述,是默认类型。 以下清单显示了定义谁:​​DirectChannel​

@Bean
public MessageChannel directChannel() {
return new DirectChannel();
}

默认通道具有轮循机制负载均衡器,并且还启用了故障转移(有关详细信息,请参阅DirectChannel)。 要禁用其中一个或两个,请添加一个子元素(构造函数)并按如下方式配置属性:​​<dispatcher/>​​​​LoadBalancingStrategy​​​​DirectChannel​

@Bean
public MessageChannel failFastChannel() {
DirectChannel channel = new DirectChannel();
channel.setFailover(false);
return channel;
}

@Bean
public MessageChannel failFastChannel() {
return new DirectChannel(null);
}
数据类型通道配置

有时,使用者只能处理特定类型的有效负载,从而强制您确保输入消息的有效负载类型。 首先想到的可能是使用消息过滤器。 但是,邮件筛选器所能做的只是筛选出不符合使用者要求的邮件。 另一种方法是使用基于内容的路由器,并将具有不合规数据类型的消息路由到特定的转换器,以强制转换和转换为所需的数据类型。 这将起作用,但完成相同操作的更简单方法是应用数据类型通道模式。 您可以为每个特定的有效负载数据类型使用单独的数据类型通道。

若要创建仅接受包含特定有效负载类型的消息的数据类型通道,请在通道元素的属性中提供数据类型的完全限定类名,如以下示例所示:​​datatype​

@Bean
public MessageChannel numberChannel() {
DirectChannel channel = new DirectChannel();
channel.setDatatypes(Number.class);
return channel;
}

请注意,类型检查会针对可分配给通道数据类型的任何类型进行传递。 换句话说,在前面的示例中,将接受有效负载为 is or 的消息。 可以以逗号分隔的列表形式提供多种类型,如以下示例所示:​​numberChannel​​​​java.lang.Integer​​​​java.lang.Double​

@Bean
public MessageChannel numberChannel() {
DirectChannel channel = new DirectChannel();
channel.setDatatypes(String.class, Number.class);
return channel;
}

因此,前面示例中的“数字通道”仅接受数据类型为 of 的消息。 但是,如果消息的有效负载不是所需的类型,会发生什么情况? 这取决于您是否定义了一个名为 Spring 转换服务的实例的 bean。 如果没有,那么将立即被扔掉。 但是,如果已定义 anbean,则会使用它来尝试将消息的有效负载转换为可接受的类型。​​java.lang.Number​​​​integrationConversionService​​​​Exception​​​​integrationConversionService​

您甚至可以注册自定义转换器。 例如,假设您将带有有效载荷的消息发送到我们上面配置的“数字通道”。 您可以按如下方式处理该消息:​​String​

MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
inChannel.send(new GenericMessage<String>("5"));

通常,这将是一个完全合法的操作。 但是,由于我们使用数据类型通道,因此此类操作的结果将生成类似于以下内容的异常:

Exception in thread "main" org.springframework.integration.MessageDeliveryException:
Channel 'numberChannel'
expected one of the following datataypes [class java.lang.Number],
but received [class java.lang.String]

发生异常是因为我们需要有效负载类型为 a,但我们发送了 a。 所以我们需要一些东西来转换 ato a。 为此,我们可以实现一个类似于以下示例的转换器:​​Number​​​​String​​​​String​​​​Number​

public static class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.parseInt(source);
}
}

然后,我们可以将其注册为集成转换服务的转换器,如以下示例所示:

@Bean
@IntegrationConverter
public StringToIntegerConverter strToInt {
return new StringToIntegerConverter();
}

或者在类上,当它标有用于自动扫描的注释时。​​StringToIntegerConverter​​​​@Component​

当解析 'converter' 元素时,如果尚未定义 bean,它会创建 bean。 有了该转换器,操作现在就会成功,因为数据类型通道使用该转换器将有效负载转换为 an。​​integrationConversionService​​​​send​​​​String​​​​Integer​

有关负载类型转换的详细信息,请参阅负载类型转换。

从版本 4.0 开始,由 调用,它在应用程序上下文中查找转换服务。 要使用不同的转换技术,您可以在通道上指定属性。 这必须是对实现的引用。 仅使用该方法。 它为转换器提供了对消息标头的访问(以防转换可能需要来自标头的信息,例如)。 该方法只能返回转换后的有效负载或完整对象。 如果是后者,转换器必须小心地从入站消息中复制所有标头。​​integrationConversionService​​​​DefaultDatatypeChannelMessageConverter​​​​message-converter​​​​MessageConverter​​​​fromMessage​​​​content-type​​​​Message​

或者,您可以使用 ID 声明 aof 类型,并且该转换器被所有带有 a 的通道使用。​​<bean/>​​​​MessageConverter​​​​datatypeChannelMessageConverter​​​​datatype​

​QueueChannel​​配置

要创建 a,请使用子元素。 您可以按如下方式指定通道的容量:​​QueueChannel​​​​<queue/>​

@Bean
public PollableChannel queueChannel() {
return new QueueChannel(25);
}

如果未在此子元素上为“capacity”属性提供值,则生成的队列是无限的。 为了避免内存不足等问题,强烈建议您为有界队列设置显式值。​​<queue/>​

持久配置​​QueueChannel​

由于 提供了缓冲消息的功能,但默认情况下仅在内存中执行此操作,因此它还引入了在系统故障时消息丢失的可能性。 为了降低这种风险,可以通过策略接口的持久实现来支持。 有关和的更多详细信息,请参阅消息存储。​​QueueChannel​​​​QueueChannel​​​​MessageGroupStore​​​​MessageGroupStore​​​​MessageStore​

使用该属性时,不允许使用该属性。​​capacity​​​​message-store​

当收到 a 时,它会将消息添加到消息存储库中。 当 ais 从 轮询时,它会从消息存储库中删除。​​QueueChannel​​​​Message​​​​Message​​​​QueueChannel​

默认情况下, 将其消息存储在内存中队列中,这可能会导致前面提到的消息丢失情况。 但是,Spring Integration 提供了持久存储,例如。​​QueueChannel​​​​JdbcChannelMessageStore​

您可以通过添加属性来为 any 配置消息存储库,如以下示例所示:​​QueueChannel​​​​message-store​

<int:channel id="dbBackedChannel">
<int:queue message-store="channelStore"/>
</int:channel>

<bean id="channelStore" class="o.s.i.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="dataSource"/>
<property name="channelMessageStoreQueryProvider" ref="queryProvider"/>
</bean>

(有关 Java/Kotlin 配置选项,请参阅下面的示例。

Spring Integration JDBC 模块还为许多流行的数据库提供了模式数据定义语言 (DDL)。 这些模式位于该模块 () 的 org.springframework.integration.jdbc.store.channel 包中。​​spring-integration-jdbc​

一个重要的功能是,对于任何事务持久性存储(例如),只要轮询器配置了事务,仅当事务成功完成时,才能永久删除从存储中删除的消息。 否则,事务将回滚,并且不会丢失。​​JdbcChannelMessageStore​​​​Message​

消息存储的许多其他实现都是可用的,因为越来越多的与“NoSQL”数据存储相关的 Spring 项目开始为这些存储提供底层支持。 如果找不到满足特定需求的接口,也可以提供自己的接口实现。​​MessageGroupStore​

从版本 4.0 开始,我们建议将实例配置为使用 a(如果可能)。 与常规邮件存储相比,这些通常针对此用途进行了优化。 如果是 a,则按优先级顺序在 FIFO 中接收消息。 优先级的概念由消息存储库实现确定。 例如,以下示例显示了MongoDB 通道消息存储库的 Java 配置:​​QueueChannel​​​​ChannelMessageStore​​​​ChannelMessageStore​​​​ChannelPriorityMessageStore​

@Bean
public BasicMessageGroupStore mongoDbChannelMessageStore(MongoDbFactory mongoDbFactory) {
MongoDbChannelMessageStore store = new MongoDbChannelMessageStore(mongoDbFactory);
store.setPriorityEnabled(true);
return store;
}

@Bean
public PollableChannel priorityQueue(BasicMessageGroupStore mongoDbChannelMessageStore) {
return new PriorityChannel(new MessageGroupQueue(mongoDbChannelMessageStore, "priorityQueue"));
}

注意课堂。 这是使用操作的实现。​​MessageGroupQueue​​​​BlockingQueue​​​​MessageGroupStore​

自定义环境的另一个选项由子元素的属性或其特定构造函数提供。 此属性提供对任何实现的引用。 例如,Hazelcast 分布式IQueue可以按如下方式配置:​​QueueChannel​​​​ref​​​​<int:queue>​​​​java.util.Queue​

@Bean
public HazelcastInstance hazelcastInstance() {
return Hazelcast.newHazelcastInstance(new Config()
.setProperty("hazelcast.logging.type", "log4j"));
}

@Bean
public PollableChannel distributedQueue() {
return new QueueChannel(hazelcastInstance()
.getQueue("springIntegrationQueue"));
}
​PublishSubscribeChannel​​配置

若要创建 ,请使用 <发布-订阅-通道/> 元素。 使用此元素时,还可以指定用于发布消息(如果未指定,则在发件人的线程中发布),如下所示:​​PublishSubscribeChannel​​​​task-executor​

爪哇岛

.XML

@Bean
public MessageChannel pubsubChannel() {
return new PublishSubscribeChannel(someExecutor());
}

如果在 的下游提供重新排序器或聚合器,则可以将通道上的“应用序列”属性设置为 。 这样做表示通道应在传递消息之前设置与消息标头以及相关 ID。 例如,如果有五个订阅者,则将设置为 ,并且消息的标头值范围为 fromto。​​PublishSubscribeChannel​​​​true​​​​sequence-size​​​​sequence-number​​​​sequence-size​​​​5​​​​sequence-number​​​​1​​​​5​

除此之外,您还可以配置一个。 默认情况下,使用一个实现将错误发送到 从标头或全局实例。 如果未配置 anis,则会忽略 theis,并将异常直接抛出到调用方的线程。​​Executor​​​​ErrorHandler​​​​PublishSubscribeChannel​​​​MessagePublishingErrorHandler​​​​MessageChannel​​​​errorChannel​​​​errorChannel​​​​Executor​​​​ErrorHandler​

如果提供 a 的主动脉下游,则可以将通道上的“应用序列”属性设置为。 这样做表示通道应在传递消息之前设置序列大小和序列号消息标头以及相关 ID。 例如,如果有五个订阅者,则序列大小将设置为 ,并且消息的序列号标头值范围为 fromto。​​Resequencer​​​​Aggregator​​​​PublishSubscribeChannel​​​​true​​​​5​​​​1​​​​5​

下面的示例演示如何将标头设置为:​​apply-sequence​​​​true​

@Bean
public MessageChannel pubsubChannel() {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
channel.setApplySequence(true);
return channel;
}

该值是默认的,以便发布-订阅通道可以将完全相同的消息实例发送到多个出站通道。 由于 Spring 集成强制有效负载和标头引用的不可变性,因此当该标志设置为 时,通道会创建具有相同有效负载引用但不同标头值的新实例。​​apply-sequence​​​​false​​​​true​​​​Message​

从版本 5.4.3 开始,还可以配置其选项以指示此通道在没有订阅者时不会静默忽略消息。 当没有订阅者并且此选项设置为 时,将引发带有消息的 Awith amessage。​​PublishSubscribeChannel​​​​requireSubscribers​​​​BroadcastingDispatcher​​​​MessageDispatchingException​​​​Dispatcher has no subscribers​​​​true​

​ExecutorChannel​

要创建一个,请添加带有属性的子元素。 属性的值可以引用上下文中的任何内容。 例如,这样做可以配置线程池,以便将消息调度到订阅的处理程序。 如前所述,这样做会破坏发送方和接收方之间的单线程执行上下文,以便处理程序的调用不会共享任何活动的事务上下文(即,处理程序可能会抛出一个,但调用已经成功返回)。 下面的示例演示如何使用 element 并在属性中指定执行器:​​ExecutorChannel​​​​<dispatcher>​​​​task-executor​​​​TaskExecutor​​​​Exception​​​​send​​​​dispatcher​​​​task-executor​

@Bean
public MessageChannel executorChannel() {
return new ExecutorChannel(someExecutor());
}


Theandoptions 在 <dispatcher/> 子元素上也可用,如前面的DirectChannel配置​中所述。 相同的默认值适用。 因此,通道具有启用故障转移的轮循机制负载平衡策略,除非为其中一个或两个属性提供显式配置,如以下示例所示:​​load-balancer​​​​failover​






<int:channel id="executorChannelWithoutFailover">
<int:dispatcher task-executor="someExecutor" failover="false"/>
</int:channel>





​PriorityChannel​​配置

若要创建 ,请使用子元素,如以下示例所示:​​PriorityChannel​​​​<priority-queue/>​

@Bean
public PollableChannel priorityChannel() {
return new PriorityChannel(20);
}

默认情况下,通道查询消息的标头。 但是,您可以改为提供自定义引用。 另外,请注意,(像其他类型一样)确实支持属性。 与 一样,它也支持 aattribute。 以下示例演示了所有这些:​​priority​​​​Comparator​​​​PriorityChannel​​​​datatype​​​​QueueChannel​​​​capacity​

@Bean
public PollableChannel priorityChannel() {
PriorityChannel channel = new PriorityChannel(20, widgetComparator());
channel.setDatatypes(example.Widget.class);
return channel;
}

从版本 4.0 开始,子元素支持选项(在这种情况下不允许)。 邮件存储区必须是 a。 的实现目前提供,和。 有关详细信息,请参阅队列通道配置和消息存储库。 您可以在支持消息通道中找到示例配置。​​priority-channel​​​​message-store​​​​comparator​​​​capacity​​​​PriorityCapableChannelMessageStore​​​​PriorityCapableChannelMessageStore​​​​Redis​​​​JDBC​​​​MongoDB​

​RendezvousChannel​​配置

当队列子元素为 a 时创建的 Ais。 它不提供前面描述的任何其他配置选项,并且其队列不接受任何容量值,因为它是零容量直接切换队列。 下面的示例演示如何声明 a:​​RendezvousChannel​​​​<rendezvous-queue>​​​​RendezvousChannel​

@Bean
public PollableChannel rendezvousChannel() {
return new RendezvousChannel();
}
作用域通道配置

任何通道都可以配置属性,如以下示例所示:​​scope​

<int:channel id="threadLocalChannel" scope="thread"/>
通道拦截器配置

消息通道也可能具有侦听器,如通道侦听器中所述。 可以将子元素添加到(或更具体的元素类型)中。 您可以提供属性来引用实现接口的任何 Spring 管理对象,如以下示例所示:​​<interceptors/>​​​​<channel/>​​​​ref​​​​ChannelInterceptor​

<int:channel id="exampleChannel">
<int:interceptors>
<ref bean="trafficMonitoringInterceptor"/>
</int:interceptors>
</int:channel>

通常,我们建议在单独的位置定义拦截器实现,因为它们通常提供可跨多个通道重用的常见行为。

全局通道拦截器配置

通道拦截器提供了一种简洁明了的方法,用于为每个通道应用横切行为。 如果要在多个通道上应用相同的行为,则为每个通道配置同一组拦截器将不是最有效的方法。 为了避免重复配置,同时使拦截器能够应用于多个通道,Spring 集成提供了全局拦截器。 请考虑以下两个示例:

<int:channel-interceptor pattern="input*, thing2*, thing1, !cat*" order="3">
<bean class="thing1.thing2SampleInterceptor"/>
</int:channel-interceptor>
<int:channel-interceptor ref="myInterceptor" pattern="input*, thing2*, thing1, !cat*" order="3"/>

<bean id="myInterceptor" class="thing1.thing2SampleInterceptor"/>

Each元素允许您定义一个全局拦截器,该拦截器应用于与属性定义的任何模式匹配的所有通道。 在前面的情况下,全局拦截器应用于“thing1”通道和所有其他以“thing2”或“input”开头的通道,但不应用于以“thing3”开头的通道(从版本 5.0 开始)。​​<channel-interceptor/>​​​​pattern​

将此语法添加到模式中会导致一个可能(尽管可能不太可能)的问题。 如果你有一个名为 bean,并且你在通道拦截器的模式中包含了一个模式,它不再匹配。 该模式现在匹配所有未命名的 bean。 在这种情况下,您可以转义该模式。 模式与名为的 Bean 匹配。​​!thing1​​​​!thing1​​​​pattern​​​​thing1​​​​!​​​​\​​​​\!thing1​​​​!thing1​

order 属性允许您管理当给定通道上有多个拦截器时注入此侦听器的位置。 例如,通道“inputChannel”可以在本地配置单独的拦截器(见下文),如以下示例所示:

<int:channel id="inputChannel">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>

一个合理的问题是“全局拦截器相对于本地配置的其他拦截器或通过其他全局拦截器定义注入的是什么? 当前的实现提供了一种简单的机制来定义拦截器执行的顺序。 属性中的正数可确保在任何现有拦截器之后注入拦截器,而负数可确保拦截器在现有拦截器之前注入。 这意味着,在前面的示例中,全局拦截器是在本地配置的“窃听”拦截器之后注入的(因为它大于)。 如果存在另一个具有匹配的全局拦截器,则其顺序将通过比较两个拦截器属性的值来确定。 要在现有侦听器之前注入全局侦听器,请对属性使用负值。​​order​​​​order​​​​0​​​​pattern​​​​order​​​​order​

请注意,这两个属性都是可选的。 默认值 forwill 为 0,for,默认值为“*”(匹配所有通道)。​​order​​​​pattern​​​​order​​​​pattern​

丝锥

如前所述,Spring Integration提供了一个简单的窃听拦截器。 您可以在元素内的任何通道上配置窃听器。 这样做对于调试特别有用,可以与 Spring 集成的日志记录通道适配器结合使用,如下所示:​​<interceptors/>​

<int:channel id="in">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>

<int:logging-channel-adapter id="logger" level="DEBUG"/>

“日志记录通道适配器”还接受“表达式”属性,以便您可以根据“有效负载”和“标头”变量评估 SpEL 表达式。 或者,要记录完整的消息结果,请为“log-full-message”属性提供一个值。 默认情况下,仅记录有效负载。 将其设置为启用除有效负载之外的所有标头的日志记录。 “表达式”选项提供了最大的灵活性(例如,)。​​toString()​​​​true​​​​false​​​​true​​​​expression="payload.user.name"​

关于窃听和其他类似组件(消息发布配置)的常见误解之一是它们本质上是自动异步的。 默认情况下,不会异步调用作为组件的窃听。 相反,Spring 集成侧重于配置异步行为的单一统一方法:消息通道。 使消息流的某些部分同步或异步的是已在该流中配置的消息通道的类型。 这是消息通道抽象的主要优点之一。 从框架诞生之初,我们就一直强调消息通道作为框架一等公民的需求和价值。 它不仅仅是 EIP 模式的内部隐式实现。 它作为可配置组件完全公开给最终用户。 因此,丝锥组件仅负责执行以下任务:

  • 通过接入通道(例如,channelA)
  • 抓取每条消息
  • 将消息发送到另一个通道(例如,channelB)

它本质上是桥接模式的变体,但它封装在通道定义中(因此更容易在不中断流的情况下启用和禁用)。 此外,与网桥不同,它基本上分叉了另一个消息流。 该流是同步的还是异步的?答案取决于“通道B”是消息通道的类型。 我们有以下选项:直接通道、可轮询通道和执行程序通道。 最后两个打破了线程边界,使通过此类通道的通信异步,因为将消息从该通道调度到其订阅的处理程序发生在与用于将消息发送到该通道的线程不同的线程上。 这就是使您的窃听流同步或异步的原因。 它与框架中的其他组件(如消息发布者)一致,并且通过使您免于提前担心(除了编写线程安全代码)特定代码段是否应实现为同步或异步,从而增加了一定程度的一致性和简单性。 两段代码(例如,组件 A 和组件 B)在消息通道上的实际连接是使它们的协作同步或异步的原因。 您甚至可能希望将来从同步更改为异步,消息通道可让您在不接触代码的情况下快速完成。

关于窃听的最后一点是,尽管上面提供了默认情况下不异步的理由,但您应该记住,通常希望尽快交出消息。 因此,使用异步通道选项作为窃听器的出站通道是很常见的。 但是,默认情况下不强制实施异步行为。 如果我们这样做,有许多用例会中断,包括您可能不想打破事务边界。 也许您使用窃听模式进行审核,并且确实希望在原始事务中发送审核消息。 例如,您可以将分路器连接到 JMS 出站通道适配器。 这样,您可以两全其美:1) JMS 消息的发送可以在事务中发生,而 2) 它仍然是“即发即弃”操作,从而防止主消息流中出现任何明显的延迟。

从版本 4.0 开始,当拦截器(如WireTap类​)引用通道时,避免循环引用非常重要。 您需要从当前拦截器拦截的通道中排除此类通道。 这可以通过适当的模式或以编程方式完成。 如果您有引用 a 的自定义,请考虑实现。 这样,框架就会根据提供的模式询问拦截器是否可以拦截作为候选的每个通道。 还可以在侦听器方法中添加运行时保护,以确保该通道不是侦听器引用的通道。 这两种技术都使用。​​ChannelInterceptor​​​​channel​​​​VetoCapableInterceptor​​​​WireTap​

从 4.3 版开始,具有额外的构造函数,它们采用 a而不是 ainstance。 这对于 Java 配置和使用通道自动创建逻辑时非常方便。 目标 Bean 是从提供的稍后解析的,在与 拦截 器。​​WireTap​​​​channelName​​​​MessageChannel​​​​MessageChannel​​​​channelName​

通道解析需要 a,因此窃听实例必须是 Spring 管理的 bean。​​BeanFactory​

这种后期绑定方法还允许使用 Java DSL 配置简化典型的窃听模式,如以下示例所示:

@Bean
public PollableChannel myChannel() {
return MessageChannels.queue()
.wireTap("loggingFlow.input")
.get();
}

@Bean
public IntegrationFlow loggingFlow() {
return f -> f.log();
}
条件窃听器

窃听可以通过使用理论属性来成为条件。 引用 abean,它可以在运行时确定消息是否应转到 tap 通道。 同样,这是一个布尔 SpEL 表达式,其执行相同的目的:如果表达式的计算结果为 ,则消息将发送到分路通道。​​selector​​​​selector-expression​​​​selector​​​​MessageSelector​​​​selector-expression​​​​true​

全局线分接头配置

可以将全局窃听器配置为全局信道拦截器配置的特例。 为此,请配置顶级元素。 现在,除了 normalnamespace 支持之外,还支持 theandattributes,并且其工作方式与 完全相同。 以下示例演示如何配置全局窃听:​​wire-tap​​​​wire-tap​​​​pattern​​​​order​​​​channel-interceptor​

@Bean
@GlobalChannelInterceptor(patterns = "input*,thing2*,thing1", order = 3)
public WireTap wireTap(MessageChannel wiretapChannel) {
return new WireTap(wiretapChannel);
}

全局分路器提供了一种在外部配置单通道分接头的便捷方法,而无需修改现有通道配置。 为此,请将属性设置为目标通道名称。 例如,您可以使用此技术配置测试用例以验证通道上的消息。​​pattern​

特殊频道

默认情况下,在应用程序上下文中定义了两个特殊通道:and。 “nullChannel”(一个实例)的作用类似于,记录在级别发送给它的任何消息并立即返回。 特殊处理适用于传输消息的有效负载:它立即在此通道中订阅,以启动反应式流处理,尽管数据被丢弃。 从反应式流处理 (see) 引发的错误记录在级别下,以便进行可能的调查。 如果需要对此类错误执行任何操作,可以将自定义应用于生成回复的消息处理程序。 每当遇到您不关心的回复的通道解析错误时,都可以将受影响组件的属性设置为“nullChannel”(名称“nullChannel”在应用程序上下文中保留)。​​errorChannel​​​​nullChannel​​​​NullChannel​​​​/dev/null​​​​DEBUG​​​​org.reactivestreams.Publisher​​​​Subscriber.onError(Throwable)​​​​warn​​​​ReactiveRequestHandlerAdvice​​​​Mono.doOnError()​​​​Mono​​​​nullChannel​​​​output-channel​

“errorChannel”在内部用于发送错误消息,可以用自定义配置覆盖。 错误处理中对此进行了更详细的讨论。

另请参阅Java DSL一章中的消息通道,了解有关消息通道和拦截器的更多信息。

波勒

本节介绍轮询在 Spring 集成中的工作原理。

轮询消费者

当消息端点(通道适配器)连接到通道并实例化时,它们将生成以下实例之一:

  • PollingConsumer
  • EventDrivenConsumer

实际实现取决于这些终结点连接到的通道类型。 连接到实现org.springframework.messaging.SubsclibableChannel接口的通道的通道适配器会生成 的实例。 另一方面,连接到实现org.springframework.messaging.PollableChannel接口(如 a)的通道的通道适配器生成一个实例。​​EventDrivenConsumer​​​​QueueChannel​​​​PollingConsumer​

轮询消费者让 Spring 集成组件主动轮询消息,而不是以事件驱动的方式处理消息。

在许多消息传递方案中,它们代表关键的跨领域问题。 在Spring Integration中,轮询使用者基于具有相同名称的模式,该模式在Gregor Hohpe和Bobby Woolf的《企业集成模式》一书中有所描述。 您可以在该书的网站上找到该模式的描述。

可轮询消息源

Spring Integration提供了轮询消费者模式的第二种变体。 使用入站通道适配器时,这些适配器通常由 包装。 例如,从远程 FTP 服务器位置检索邮件时,FTP 入站通道适配器中描述的适配器配置了轮询器以定期检索邮件。 因此,当组件配置了轮询器时,生成的实例属于以下类型之一:​​SourcePollingChannelAdapter​

  • PollingConsumer
  • SourcePollingChannelAdapter

这意味着轮询器同时用于入站和出站消息传递方案。 以下是使用轮询器的一些用例:

  • 轮询某些外部系统,如 FTP 服务器、数据库和 Web 服务
  • 轮询内部(可轮询)消息通道
  • 轮询内部服务(例如在 Java 类上重复执行方法)

AOP建议类可以应用于轮询器,在诸如事务建议的情况下开始交易。 从版本 4.1 开始,提供了 ais。 轮询器使用触发器来确定下一次轮询的时间。 可用于抑制(跳过)轮询,可能是因为存在一些下游条件会阻止处理消息。 要使用此建议,您必须为其提供 a. 从版本 4.2.5 开始,提供了 ais。 要使用它,您可以将实例作为 Bean 添加到应用程序上下文中,将其注入 ,然后将其添加到轮询器的建议链中。 要跳过轮询,请致电。 要恢复轮询,请致电。 版本 4.2 在这方面增加了更大的灵活性。 请参阅消息源的条件轮询器​。​​advice-chain​​​​PollSkipAdvice​​​​PollSkipAdvice​​​​PollSkipStrategy​​​​SimplePollSkipStrategy​​​​PollSkipAdvice​​​​skipPolls()​​​​reset()​

本章仅对轮询使用者以及它们如何适应消息通道(请参阅消息通道)和通道适配器(请参阅通道适配器)的概念进行高级概述。 有关消息传递终结点(特别是轮询使用者)的详细信息,请参阅消息终结点。

延迟确认可轮询消息源

从版本 5.0.1 开始,某些模块提供的实现支持延迟确认,直到下游流完成(或将消息交给另一个线程)。 这目前仅限于和。​​MessageSource​​​​AmqpMessageSource​​​​KafkaMessageSource​

使用这些消息源,标头(请参阅消息标头访问器API)将添加到消息中。 与可轮询消息源一起使用时,标头的值是 的实例,如以下示例所示:​​IntegrationMessageHeaderAccessor.ACKNOWLEDGMENT_CALLBACK​​​​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

}

}

并非所有消息源(例如,a)都支持状态。 它的处理方式与。​​KafkaMessageSource​​​​REJECT​​​​ACCEPT​

应用程序可以随时确认消息,如以下示例所示:

Message<?> received = source.receive();

...

StaticMessageHeaderAccessor.getAcknowledgmentCallback(received)
.acknowledge(Status.ACCEPT);

如果连接到 中,则当轮询器线程在下游流完成后返回到适配器时,适配器将检查确认是否已得到确认,如果没有,则将其状态设置为 it(或者如果流引发异常)。 状态值在AcknowledgmentCallback.Status枚举中定义。​​MessageSource​​​​SourcePollingChannelAdapter​​​​ACCEPT​​​​REJECT​

Spring 集成提供对 a 执行临时轮询。 这也负责 setoron the何时回调返回(或抛出异常)。 以下示例演示如何使用:​​MessageSourcePollingTemplate​​​​MessageSource​​​​ACCEPT​​​​REJECT​​​​AcknowledgmentCallback​​​​MessageHandler​​​​MessageSourcePollingTemplate​

MessageSourcePollingTemplate template =
new MessageSourcePollingTemplate(this.source);
template.poll(h -> {
...
});

在这两种情况下(和),您都可以通过调用回调来禁用自动 ack/nack。 如果您将消息传递给另一个线程并希望稍后确认,则可以执行此操作。 并非所有实现都支持此功能(例如,Apache Kafka 不支持,因为偏移提交必须在同一线程上执行)。​​SourcePollingChannelAdapter​​​​MessageSourcePollingTemplate​​​​noAutoAck()​

消息源的条件轮询器

本节介绍如何使用条件轮询器。

背景

​Advice​​对象,在 anon 轮询器中,建议整个轮询任务(消息检索和处理)。 这些“围绕建议”的方法无法访问民意调查的任何上下文 - 只能访问民意调查本身。 如前所述,这对于诸如使任务事务化或由于某些外部条件而跳过轮询之类的要求来说很好。 如果我们希望根据民意调查部分的结果采取一些行动,或者我们想根据条件调整轮询器怎么办?对于这些情况,Spring Integration 提供“智能”轮询。​​advice-chain​​​​receive​

“智能”轮询

5.3版本引入了界面。 实现此接口的任何对象仅适用于操作 -and。 因此,它们只能应用于理论。 此类类实现以下方法:​​ReceiveMessageAdvice​​​​Advice​​​​advice-chain​​​​receive()​​​​MessageSource.receive()​​​​PollableChannel.receive(timeout)​​​​SourcePollingChannelAdapter​​​​PollingConsumer​

  • ​beforeReceive(Object source)​​此方法在方法之前调用。 它允许您检查和重新配置源。 返回取消此轮询(类似于前面提到的)。Object.receive()falsePollSkipAdvice
  • ​Message<?> afterReceive(Message<?> result, Object source)​​此方法在方法之后调用。 同样,您可以重新配置源或执行任何操作(可能取决于结果,如果源没有创建任何消息,则可能)。 您甚至可以返回不同的消息receive()null

螺纹安全


如果对源进行突变,则不应使用 a 配置轮询器。 如果对源进行突变,则此类突变不是线程安全的,并且可能导致意外结果,尤其是对于高频轮询器。 如果需要同时处理轮询结果,请考虑使用下游,而不是向轮询器添加执行程序。​​Advice​​​​TaskExecutor​​​​Advice​​​​ExecutorChannel​


建议链订购


您应该了解在初始化期间如何处理建议链。未实现的对象将应用于整个轮询过程,并且所有对象都首先按顺序调用,然后调用任何对象。 然后围绕源方法按顺序调用对象。 例如,如果您有对象,其中,则按以下顺序应用对象: 此外,如果源已经是 a,则在任何现有对象之后调用 theis。 如果您想更改订单,则必须自己连接代理。​​Advice​​​​ReceiveMessageAdvice​​​​ReceiveMessageAdvice​​​​ReceiveMessageAdvice​​​​receive()​​​​Advice​​​​a, b, c, d​​​​b​​​​d​​​​ReceiveMessageAdvice​​​​a, c, b, d​​​​Proxy​​​​ReceiveMessageAdvice​​​​Advice​


​SimpleActiveIdleReceiveMessageAdvice​

这个建议是一个简单的实现。 当与 a 结合使用时,它会根据上一次轮询是否生成消息来调整轮询频率。 轮询器还必须具有对相同的引用。​​ReceiveMessageAdvice​​​​DynamicPeriodicTrigger​​​​DynamicPeriodicTrigger​


重要:异步切换

​SimpleActiveIdleReceiveMessageAdvice​​​根据结果修改触发器。 仅当在轮询器线程上调用建议时,这才有效。 如果轮询器有一个,它不起作用。 若要在轮询结果后使用此建议,请稍后执行异步切换,也许使用 an。​​receive()​​​​task-executor​​​​ExecutorChannel​

​CompoundTriggerAdvice​

此建议允许根据轮询是否返回消息来选择两个触发器之一。 考虑一个使用 a.instances 的轮询器是不可变的,因此一旦构造它们就无法更改。 考虑一个用例,我们希望使用 cron 表达式每小时触发一次轮询,但是,如果没有收到消息,则每分钟轮询一次,并在检索消息时恢复使用 cron 表达式。​​CronTrigger​​​​CronTrigger​

建议(和轮询者)为此目的使用 a。 触发器的触发器可以是 a。 当建议检测到未收到任何消息时,它会将辅助触发器添加到 。 调用实例的方法时,它会委托给辅助触发器(如果存在)。 否则,它将委托给主触发器。​​CompoundTrigger​​​​primary​​​​CronTrigger​​​​CompoundTrigger​​​​CompoundTrigger​​​​nextExecutionTime​

轮询器还必须具有对相同的引用。​​CompoundTrigger​

以下示例显示了每小时 cron 表达式的配置,回退到每分钟一次:

<int:inbound-channel-adapter channel="nullChannel" auto-startup="false">
<bean class="org.springframework.integration.endpoint.PollerAdviceTests.Source" />
<int:poller trigger="compoundTrigger">
<int:advice-chain>
<bean class="org.springframework.integration.aop.CompoundTriggerAdvice">
<constructor-arg ref="compoundTrigger"/>
<constructor-arg ref="secondary"/>
</bean>
</int:advice-chain>
</int:poller>
</int:inbound-channel-adapter>

<bean id="compoundTrigger" class="org.springframework.integration.util.CompoundTrigger">
<constructor-arg ref="primary" />
</bean>

<bean id="primary" class="org.springframework.scheduling.support.CronTrigger">
<constructor-arg value="0 0 * * * *" /> <!-- top of every hour -->
</bean>

<bean id="secondary" class="org.springframework.scheduling.support.PeriodicTrigger">
<constructor-arg value="60000" />
</bean>

重要:异步切换

​CompoundTriggerAdvice​​​根据结果修改触发器。 仅当在轮询器线程上调用建议时,这才有效。 如果轮询器有一个,它不起作用。 若要在轮询结果后使用此建议,请稍后执行异步切换,也许使用 an。​​receive()​​​​task-executor​​​​ExecutorChannel​

消息仅来源建议

有些建议可能只适用于它们没有意义。 为此,接口(扩展)仍然存在。 有关详细信息,请参阅入站通道适配器:轮询多个服务器和目录。​​MessageSource.receive()​​​​PollableChannel​​​​MessageSourceMutator​​​​ReceiveMessageAdvice​

通道适配器

通道适配器是一个消息端点,用于将单个发送方或接收方连接到消息通道。 Spring 集成提供了许多适配器来支持各种传输,例如 JMS、文件、HTTP、Web 服务、邮件等。 本参考指南的后续章节将讨论每个适配器。 但是,本章重点介绍简单但灵活的方法调用通道适配器支持。 有入站适配器和出站适配器,每个适配器都可以使用核心命名空间中提供的 XML 元素进行配置。 这些提供了一种扩展 Spring 集成的简单方法,只要您有一个可以作为源或目标调用的方法。

配置入站通道适配器

Anelement(ain Java 配置)可以调用 Spring 管理对象上的任何方法,并在将方法的输出转换为 a 后向 a 发送非空返回值。 激活适配器的订阅后,轮询器会尝试从源接收消息。 轮询器根据提供的配置进行调度。 要为单个通道适配器配置轮询间隔或 cron 表达式,您可以为具有调度属性之一的“轮询器”元素提供,例如“固定速率”或“cron”。 以下示例定义了两个实例:​​inbound-channel-adapter​​​​SourcePollingChannelAdapter​​​​MessageChannel​​​​Message​​​​TaskScheduler​​​​inbound-channel-adapter​

@Bean
public IntegrationFlow source1() {
return IntegrationFlow.from(() -> new GenericMessage<>(...),
e -> e.poller(p -> p.fixedRate(5000)))
...
.get();
}

@Bean
public IntegrationFlow source2() {
return IntegrationFlow.from(() -> new GenericMessage<>(...),
e -> e.poller(p -> p.cron("30 * 9-17 * * MON-FRI")))
...
.get();
}

另请参阅​​通道适配器表达式和脚本​​。

如果未提供轮询器,则必须在上下文中注册单个默认轮询器。 有关更多详细信息,请参阅终结点命名空间支持。

重要:轮询器配置


所有类型都由 a 支持,这意味着它们包含一个轮询器配置,该配置根据轮询器中指定的配置轮询(以调用生成成为有效负载的值的自定义方法)。 以下示例显示了两个轮询器的配置:​​inbound-channel-adapter​​​​SourcePollingChannelAdapter​​​​MessageSource​​​​Message​

<int:poller max-messages-per-poll="1" fixed-rate="1000"/>

<int:poller max-messages-per-poll="10" fixed-rate="1000"/>


在第一个配置中,每次轮询调用一次轮询任务,并且在每个任务(轮询)期间,根据属性值调用一次方法(导致生成消息)。 在第二种配置中,轮询任务每次轮询被调用 10 次,或者直到它返回“null”,因此每次轮询可能会产生 10 条消息,而每次轮询每隔一秒进行一次。 但是,如果配置类似于以下示例,会发生什么情况:​​max-messages-per-poll​

<int:poller fixed-rate="1000"/>


请注意,没有指定。 正如我们稍后介绍的,(例如,,,, 和其他)中的相同轮询器配置将具有默认值 offor,这意味着“除非轮询方法返回 null(可能是因为中没有更多消息)否则不停止执行轮询任务”,然后休眠一秒钟。​​max-messages-per-poll​​​​PollingConsumer​​​​service-activator​​​​filter​​​​router​​​​-1​​​​max-messages-per-poll​​​​QueueChannel​



但是,在,它有点不同。 默认值 foris,除非您将其显式设置为负值(例如)。 这确保了轮询器可以对生命周期事件(例如启动和停止)做出反应,并防止它在无限循环中旋转,如果自定义方法的实现有可能永远不会返回 null 并且恰好是不可中断的。​​SourcePollingChannelAdapter​​​​max-messages-per-poll​​​​1​​​​-1​​​​MessageSource​



但是,如果您确定您的方法可以返回 null,并且您需要轮询每个轮询尽可能多的可用源,则应显式设置为负值,如以下示例所示:​​max-messages-per-poll​

<int:poller max-messages-per-poll="-1" fixed-rate="1000"/>


从版本 5.5 开始,avalue for具有特殊含义 - 完全跳过调用,这可能被视为暂停此入站通道适配器,直到稍后更改为非零值,例如通过控制总线。​​0​​​​max-messages-per-poll​​​​MessageSource.receive()​​​​maxMessagesPerPoll​


另请参阅全局默认轮询器以获取更多信息。


配置出站通道适配器

Anelement(afor Java 配置)还可以将 a连接到任何 POJO 消费者方法,该方法应使用发送到该通道的消息的有效负载进行调用。 以下示例演示如何定义出站通道适配器:​​outbound-channel-adapter​​​​@ServiceActivator​​​​MessageChannel​

@Bean
public IntegrationFlow outboundChannelAdapterFlow(MyPojo myPojo) {
return f -> f
.handle(myPojo, "handle");
}

如果要改编的通道是 a,则必须提供轮询器子元素(子注释上),如以下示例所示:​​PollableChannel​​​​@Poller​​​​@ServiceActivator​

public class MyPojo {

@ServiceActivator(channel = "channel1", poller = @Poller(fixedRate = "3000"))
void handle(Object payload) {
...
}

}

如果 POJO 使用者实现可以在其他定义中重用,则应使用 aattribute。 但是,如果使用者实现仅由单个定义引用,则可以将其定义为内 Bean,如以下示例所示:​​ref​​​​<outbound-channel-adapter>​​​​<outbound-channel-adapter>​

<int:outbound-channel-adapter channel="channel" method="handle">
<beans:bean class="org.Foo"/>
</int:outbound-channel-adapter>

不允许在同一配置中同时使用属性和内部处理程序定义,因为它会产生不明确的条件。 此类配置会导致引发异常。​​ref​​​​<outbound-channel-adapter>​

可以在没有引用的情况下创建任何通道适配器,在这种情况下,它会隐式创建 的实例。 创建的通道的名称与 theorelement 的属性匹配。 因此,ifis 未提供,是必需的。​​channel​​​​DirectChannel​​​​id​​​​<inbound-channel-adapter>​​​​<outbound-channel-adapter>​​​​channel​​​​id​

通道适配器表达式和脚本

与许多其他Spring Integration组件一样,该组件也为SpEL表达评估提供支持。 要使用 SpEL,请在“表达式”属性中提供表达式字符串,而不是提供用于在 Bean 上进行方法调用的“ref”和“method”属性。 计算表达式时,它遵循与方法调用相同的协定,其中:每当计算结果为非 null 值时,an 的表达式都会生成一条消息,而 an的表达式必须等效于 void 返回方法调用。​​<inbound-channel-adapter>​​​​<outbound-channel-adapter>​​​​<inbound-channel-adapter>​​​​<outbound-channel-adapter>​

从Spring Integration 3.0开始,还可以配置一个SpEL(甚至一个)子元素,用于需要比简单的“表达式”属性更复杂时。 如果使用属性提供脚本作为 aby,则还可以设置,这允许定期刷新资源。 如果要在每次轮询时检查脚本,则需要将此设置与轮询器的触发器协调,如以下示例所示:​​<int:inbound-channel-adapter/>​​​​<expression/>​​​​<script/>​​​​Resource​​​​location​​​​refresh-check-delay​

<int:inbound-channel-adapter ref="source1" method="method1" channel="channel1">
<int:poller max-messages-per-poll="1" fixed-delay="5000"/>
<script:script lang="ruby" location="Foo.rb" refresh-check-delay="5000"/>
</int:inbound-channel-adapter>

另请参阅属性上使用子元素时。 有关表达式的更多信息,请参阅Spring 表达式语言 (SpEL)。 有关脚本,请参阅Groovy支持和脚本支持。​​cacheSeconds​​​​ReloadableResourceBundleExpressionSource​​​​<expression/>​

The() 是一个端点,它通过定期触发轮询一些底层来启动消息流。 由于在轮询时没有消息对象,因此表达式和脚本无权访问根,因此大多数其他消息传递 SpEL 表达式中没有可用的有效负载或标头属性。 该脚本可以生成并返回包含标头和有效负载的完整对象,也可以仅生成并返回有效负载,该有效负载由框架添加到具有基本标头的消息中。​​<int:inbound-channel-adapter/>​​​​SourcePollingChannelAdapter​​​​MessageSource​​​​Message​​​​Message​

消息传递桥

消息网桥是一个相对简单的端点,用于连接两个消息通道或通道适配器。 例如,您可能希望连接 ato a,以便订阅端点不必担心任何轮询配置。 相反,消息网桥提供轮询配置。​​PollableChannel​​​​SubscribableChannel​

通过在两个通道之间提供中间轮询器,您可以使用消息传递桥来限制入站消息。 轮询器的触发器确定消息到达第二个通道的速率,轮询器的属性对吞吐量实施限制。​​maxMessagesPerPoll​

消息网桥的另一个有效用途是连接两个不同的系统。 在这种情况下,Spring Integration的作用仅限于在这些系统之间建立连接并在必要时管理轮询器。 在两个系统之间至少有一个转换器,以便在它们的格式之间进行转换可能更常见。 在这种情况下,可以将通道作为转换器端点的“输入通道”和“输出通道”提供。 如果不需要数据格式转换,则消息传递桥接可能确实足够了。

使用 XML 配置网桥

您可以使用 element 用于在两个消息通道或通道适配器之间创建消息网桥。 为此,请提供 theandattributes,如以下示例所示:​​<bridge>​​​​input-channel​​​​output-channel​

<int:bridge input-channel="input" output-channel="output"/>

如上所述,消息网桥的一个常见用例是连接 ato a。 执行此角色时,消息传递桥还可以用作限制器:​​PollableChannel​​​​SubscribableChannel​

<int:bridge input-channel="pollable" output-channel="subscribable">
<int:poller max-messages-per-poll="10" fixed-rate="5000"/>
</int:bridge>

您可以使用与连接通道适配器类似的机制。 下面的例子显示了来自 Spring Integration 命名空间的 theandadapters 之间的简单“echo”:​​stdin​​​​stdout​​​​stream​

<int-stream:stdin-channel-adapter id="stdin"/>

<int-stream:stdout-channel-adapter id="stdout"/>

<int:bridge id="echo" input-channel="stdin" output-channel="stdout"/>

类似的配置适用于其他(可能更有用的)通道适配器桥接,例如文件到 JMS 或邮件到文件。 接下来的章节将介绍各种通道适配器。

如果未在网桥上定义“输出通道”,则使用入站消息提供的回复通道(如果可用)。 如果输出和回复通道都不可用,则会引发异常。

使用 Java 配置配置网桥

以下示例演示如何使用注释在 Java 中配置桥:​​@BridgeFrom​

@Bean
public PollableChannel polled() {
return new QueueChannel();
}

@Bean
@BridgeFrom(value = "polled", poller = @Poller(fixedDelay = "5000", maxMessagesPerPoll = "10"))
public SubscribableChannel direct() {
return new DirectChannel();
}

以下示例演示如何使用注释在 Java 中配置桥:​​@BridgeTo​

@Bean
@BridgeTo(value = "direct", poller = @Poller(fixedDelay = "5000", maxMessagesPerPoll = "10"))
public PollableChannel polled() {
return new QueueChannel();
}

@Bean
public SubscribableChannel direct() {
return new DirectChannel();
}

或者,您可以使用 a,如以下示例所示:​​BridgeHandler​

@Bean
@ServiceActivator(inputChannel = "polled",
poller = @Poller(fixedRate = "5000", maxMessagesPerPoll = "10"))
public BridgeHandler bridge() {
BridgeHandler bridge = new BridgeHandler();
bridge.setOutputChannelName("direct");
return bridge;
}

使用 Java DSL 配置网桥

您可以使用 Java 域特定语言 (DSL) 来配置网桥,如以下示例所示:

@Bean
public IntegrationFlow bridgeFlow() {
return IntegrationFlow.from("polled")
.bridge(e -> e.poller(Pollers.fixedDelay(5000).maxMessagesPerPoll(10)))
.channel("direct")
.get();
}