错误处理
如本手册开头的概览所述,消息导向框架(例如 Spring Integration)背后的主要动机之一是促进组件之间的松散耦合。消息通道在这方面起着重要作用,因为生产者和消费者不需要相互了解。然而,优点也带有一些缺点。在松散耦合的环境中,有些事情变得更加复杂,其中一个例子就是错误处理。
当向通道发送消息时,最终处理该消息的组件可能与发送者在同一个线程内运行,也可能不在。如果使用简单的默认 DirectChannel
(即 <channel>
元素没有 <queue>
子元素且没有 'task-executor' 属性),消息处理将在发送初始消息的同一线程中发生。在这种情况下,如果抛出一个 Exception
,它可以被发送者捕获,或者如果它是一个未捕获的 RuntimeException
,则可能会传播到发送者之外。这与正常 Java 调用栈中的异常抛出操作具有相同的行为。
运行在调用者线程上的消息流可能通过消息网关(见 消息网关)或 MessagingTemplate
(见 MessagingTemplate)来调用。在这两种情况下,默认行为是将任何异常抛给调用者。对于消息网关,详见 错误处理,其中描述了异常是如何抛出的以及如何配置网关以将错误路由到错误通道。当使用 MessagingTemplate
或直接发送到 MessageChannel
时,异常总是抛给调用者。
当添加异步处理时,事情变得相当复杂。例如,如果 'channel' 元素确实提供了一个 'queue' 子元素(Java 和注解配置中的 QueueChannel
),那么处理消息的组件会在与发送者不同的线程中运行。同样地,当使用 ExecutorChannel
时也是如此。发送者可能已经将 Message
放入通道并继续做其他事情。没有办法通过标准的 Exception
抛出技术直接将异常抛回到发送者。相反,为异步过程处理错误需要错误处理机制也必须是异步的。
Spring Integration 通过将错误发布到消息通道来为其组件提供错误处理支持。具体来说,Exception
会成为 Spring Integration ErrorMessage
的负载。然后该 Message
会被发送到一个以类似于 'replyChannel' 解析方式解析的消息通道。首先,如果在发生 Exception
时正在处理的请求 Message
包含一个 'errorChannel' 头(头名称在 MessageHeaders.ERROR_CHANNEL
常量中定义),则 ErrorMessage
将被发送到该通道。否则,错误处理器会发送到一个名为 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
来完成的。建议为任何自定义的 taskScheduler
使用该 MessagePublishingErrorHandler
,如果错误处理仍然需要使用标准的 'errorChannel' 集成流逻辑。在这种情况下,可以使用注册的 integrationMessagePublishingErrorHandler
bean。
要启用全局错误处理,在该通道上注册一个处理器。例如,您可以配置 Spring Integration 的 ErrorMessageExceptionTypeRouter
作为订阅 errorChannel
的端点的处理器。然后,该路由器可以根据 Exception
类型将错误消息分发到多个通道。
从 4.3.10 版本开始,Spring Integration 提供了 ErrorMessagePublisher
和 ErrorMessageStrategy
。你可以将它们作为发布 ErrorMessage
实例的通用机制。你可以在任何错误处理场景中调用或扩展它们。ErrorMessageSendingRecoverer
扩展了此类作为一个 RecoveryCallback
实现,可以与重试一起使用,例如 RequestHandlerRetryAdvice。ErrorMessageStrategy
用于根据提供的异常和 AttributeAccessor
上下文构建 ErrorMessage
。它可以注入到任何 MessageProducerSupport
或 MessagingGatewaySupport
中。requestMessage
存储在 AttributeAccessor
上下文中的 ErrorMessageUtils.INPUT_MESSAGE_CONTEXT_KEY
下。ErrorMessageStrategy
可以将该 requestMessage
用作它创建的 ErrorMessage
的 originalMessage
属性。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
。有关更多信息,请参阅全局属性和发布订阅通道配置(如果您手动配置了一个全局 errorChannel
)。
更多信息请参见 Error Handling Sample。