错误处理

如本手册开头处的概述中所述,消息导向框架(如 Spring Integration)背后的主要动机之一是促进组件之间的松耦合。消息通道在此起着重要作用,因为生产者和消费者无需了解彼此。但是,优势也存在一些弊端。在松耦合的环境中,某些事情变得更加复杂,错误处理就是一个例子。

当向通道发送消息时,最终处理该消息的组件可能在与发送方相同的线程中运行,也可能不在。如果使用简单的默认 DirectChannel(当 <channel> 元素没有 <queue> 子元素且没有 'task-executor' 属性时),则消息处理发生在发送初始消息的同一线程中。在这种情况下,如果抛出 Exception,则发送方可以捕获它,或者如果它是一个未捕获的 RuntimeException,则它可能会传播到发送方之外。这与普通 Java 调用栈中抛出异常的操作行为相同。

在调用方线程上运行的消息流可以通过消息网关(请参阅消息网关)或 MessagingTemplate(请参阅MessagingTemplate)调用。在这两种情况下,默认行为都是将任何异常抛给调用方。对于消息网关,请参阅错误处理,了解有关如何抛出异常以及如何配置网关将错误路由到错误通道的详细信息。当使用 MessagingTemplate 或直接发送到 MessageChannel 时,异常始终会抛给调用方。

添加异步处理后,事情变得更加复杂。例如,如果 'channel' 元素确实提供了 'queue' 子元素(Java 和注解配置中的 QueueChannel),则处理消息的组件在与发送方不同的线程中运行。当使用 ExecutorChannel 时也是如此。发送方可能已将 Message 放入通道并继续执行其他操作。使用标准 Exception 抛出技术无法将 Exception 直接抛回该发送方。相反,异步进程的错误处理要求错误处理机制也为异步。

Spring Integration 通过将错误发布到消息通道来支持其组件的错误处理。具体来说,Exception 成为 Spring Integration ErrorMessage 的有效负载。然后将该 Message 发送到以类似于 'replyChannel' 解析方式解析的消息通道。首先,如果在发生 Exception 时正在处理的请求 Message 包含 'errorChannel' 标头(标头名称在 MessageHeaders.ERROR_CHANNEL 常量中定义),则 ErrorMessage 将发送到该通道。否则,错误处理程序将发送到一个 bean 名称是 errorChannel 的“全局”通道(这也定义为一个常量:IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)。

框架在内部创建了一个默认的 errorChannel bean。但是,如果要控制设置,可以定义自己的 bean。以下示例显示如何在 XML 配置中定义一个由容量为 500 的队列支持的错误通道。

  • Java

  • XML

@Bean
QueueChannel errorChannel() {
    return new QueueChannel(500);
}
<int:channel id="errorChannel">
    <int:queue capacity="500"/>
</int:channel>
默认错误通道是 PublishSubscribeChannel。默认情况下,它有一个 LoggingHandler 作为订阅者,其日志级别为 ERROR,订阅顺序为 Ordered.LOWEST_PRECEDENCE - 100。如果订阅了其他消费端点,这些端点可能会抛出异常,并且您不想抢占日志记录,请确保其他处理程序具有更高的顺序。

这里需要理解的最重要的一点是,基于消息的错误处理仅适用于在 TaskExecutor 内执行的 Spring Integration 任务抛出的异常。这并不适用于由在与发送方相同的线程中运行的处理程序抛出的异常(例如,通过前面本节中描述的 DirectChannel)。

当计划的轮询任务执行中发生异常时,这些异常也会包装在 ErrorMessage 实例中并发送到 'errorChannel'。这是通过注入到全局 taskScheduler bean 中的 MessagePublishingErrorHandler 完成的。如果错误处理仍然需要使用标准的 'errorChannel' 集成流逻辑来完成,建议对任何自定义 taskScheduler 使用该 MessagePublishingErrorHandler。在这种情况下,可以使用注册的 integrationMessagePublishingErrorHandler bean。

要启用全局错误处理,请在该通道上注册一个处理程序。例如,您可以将 Spring Integration 的 ErrorMessageExceptionTypeRouter 配置为订阅 errorChannel 的端点的处理程序。然后,该路由器可以根据 Exception 类型将错误消息分散到多个通道中。

从版本 4.3.10 开始,Spring Integration 提供了 ErrorMessagePublisherErrorMessageStrategy。您可以将它们用作发布 ErrorMessage 实例的通用机制。您可以在任何错误处理场景中调用或扩展它们。ErrorMessageSendingRecoverer 扩展此类作为 RecoveryCallback 实现,可用于重试,例如RequestHandlerRetryAdviceErrorMessageStrategy 用于基于提供的异常和 AttributeAccessor 上下文构建 ErrorMessage。它可以注入到任何 MessageProducerSupportMessagingGatewaySupport 中。requestMessage 存储在 AttributeAccessor 上下文中的 ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY 下。ErrorMessageStrategy 可以使用该 requestMessage 作为其创建的 ErrorMessageoriginalMessage 属性。DefaultErrorMessageStrategy 正是这样做的。

从版本 5.2 开始,框架组件抛出的所有 MessageHandlingException 实例都包含一个组件 BeanDefinition 资源和源,以从异常中确定配置点。在 XML 配置的情况下,资源是 XML 文件路径,源是带有其 id 属性的 XML 标签。使用 Java 和注解配置时,资源是 @Configuration 类,源是 @Bean 方法。在大多数情况下,目标集成流解决方案基于开箱即用的组件及其配置选项。当运行时发生异常时,堆栈跟踪中不涉及任何最终用户代码,因为执行针对 bean,而不是它们的配置。包含 bean 定义的资源和源有助于确定可能的配置错误并提供更好的开发人员体验。

从版本 5.4.3 开始,默认错误通道配置了属性 requireSubscribers = true,以便在该通道上没有订阅者时(例如,当应用程序上下文停止时)不会静默忽略消息。在这种情况下,将抛出 MessageDispatchingException,这可能会导致传入通道适配器的客户端回调对源系统中的原始消息进行否定确认(或回滚),以便重新传递或其他将来考虑。要恢复以前的行为(忽略未分派的消息),必须将全局集成属性 spring.integration.channels.error.requireSubscribers 设置为 false。请参阅全局属性PublishSubscribeChannel 配置(如果手动配置全局 errorChannel)以获取更多信息。

有关更多信息,另请参阅错误处理示例