Error Handling

如本手册最开始 overview 所述,Spring 集成等面向消息的框架背后的一个主要动机是促进组件之间的松散耦合。消息信道扮演着重要角色,因为生产者和消费者不必互相了解。但是,这些优点也有一些缺点。在松散耦合的环境中,某些事情会变得更加复杂,错误处理就是其中一个示例。

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

通过消息网关(参见 Messaging Gateways)或 MessagingTemplate(参见 MessagingTemplate)可以调用在调用方线程中运行的消息流。在任一情况下,默认行为是向调用方抛出任何异常。对于消息网关,请参阅 Error Handling,了解有关如何抛出异常以及如何配置该网关以将错误路由到错误信道的信息。在使用 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。然而,如果你希望控制设置,你可以定义你自己的 errorChannel。以下示例显示如何在由具有容量为 500 的队列做后盾的 XML 配置中定义错误通道:

  • 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 集成提供 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 的配置,因此堆栈跟踪中不包含任何最终用户代码。包含 bean 定义的资源和源有助于确定可能的配置错误,并提供更好的开发人员体验。

从版本 5.4.3 开始,默认错误信道配置了属性 requireSubscribers = true,以便在该信道上没有订阅者时不静默忽略消息(例如,当应用程序上下文已停止时)。在这种情况下,将抛出一个 MessageDispatchingException,该异常可能会借用入站信道适配器的客户端回调函数,以否定确认(或回滚)源系统中的原始消息,以便重新传递或将来考虑。要恢复之前的行为(忽略未分派错误消息),则必须将全局集成属性 spring.integration.channels.error.requireSubscribers 设置为 false。有关详细信息,请参见 Global PropertiesPublishSubscribeChannel Configuration(如果您手动配置全局 errorChannel)。

另请参阅 Error Handling Sample 了解更多信息。