通道拦截器

消息架构的优势之一在于能够以无侵入的方式提供通用行为,并捕获流经系统的消息的有意义信息。由于 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);

返回 Message 实例的方法可用于转换 Message,或者可以返回 'null' 以阻止进一步处理(当然,任何方法都可以抛出 RuntimeException)。此外,preReceive 方法可以返回 false 以阻止接收操作继续进行。

请记住,receive() 调用仅与 PollableChannels 相关。事实上,SubscribableChannel 接口甚至没有定义 receive() 方法。这是因为当消息发送到 SubscribableChannel 时,它直接发送给零个或多个订阅者,具体取决于通道类型(例如,PublishSubscribeChannel 发送给所有订阅者)。因此,preReceive(…​)postReceive(…​)afterReceiveCompletion(…​) 拦截器方法仅在拦截器应用于 PollableChannel 时被调用。

Spring Integration 还提供了 Wire Tap(窃听器) 模式的实现。它是一个简单的拦截器,将 Message 发送到另一个通道,而不会以其他方式改变现有流程。这对于调试和监控非常有用。一个示例参见 Wire Tap

由于很少需要实现所有拦截器方法,该接口提供了无操作(no-op)方法(返回 void 的方法没有代码,返回 Message 的方法原样返回 Message,而返回 boolean 的方法返回 true)。

拦截器方法的调用顺序取决于通道类型。如前所述,基于队列的通道是唯一首先会拦截 receive() 方法的通道。此外,发送和接收拦截之间的关系取决于独立的发送者和接收者线程的时间安排。例如,如果接收者在等待消息时已被阻塞,顺序可能是这样:preSend, preReceive, postReceive, postSend。然而,如果发送者将消息放入通道并已返回后,接收者才进行轮询,顺序将是:preSend, postSend (经过一段时间), preReceive, postReceive。在这种情况下经过的时间取决于多种因素,因此通常是不可预测的(事实上,接收可能永远不会发生)。队列的类型也起作用(例如,会合队列与优先级队列)。简而言之,除了 preSendpostSend 之前以及 preReceivepostReceive 之前这一事实之外,你不能依赖于顺序。

从 Spring Framework 4.1 和 Spring Integration 4.1 开始,ChannelInterceptor 提供了新方法:afterSendCompletion()afterReceiveCompletion()。无论是否发生任何异常,它们都会在 send()receive() 调用之后被调用,以便进行资源清理。请注意,通道在 ChannelInterceptor 列表上以与初始 preSend()preReceive() 调用相反的顺序调用这些方法。

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

此外,从 5.1 版本开始,当没有接收到消息时,不再调用 ChannelInterceptor.postReceive();不再需要检查 nullMessage<?>。以前,该方法会被调用。如果你有一个依赖于先前行为的拦截器,请转而实现 afterReceiveCompleted(),因为无论是否接收到消息,都会调用该方法。

从 5.2 版本开始,ChannelInterceptorAware 已被弃用,转而使用 Spring Messaging 模块中的 InterceptableChannel,为了向后兼容,前者现在扩展了后者。