错误处理

正如本手册开头的概览所述,像 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。但是,如果您想控制其设置,可以定义自己的。以下示例展示了如何在 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 中。requestMessageAttributeAccessor 上下文中的 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)。

另请参见错误处理示例获取更多信息。