错误处理
正如本手册开篇概述所述,像 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 就会被发送到该通道。否则,错误处理器会发送到一个“全局”通道,其 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 提供了 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 的资源(resource)和来源(source),以便从异常中确定配置点。在XML配置的情况下,资源是XML文件路径,来源是带有 id 属性的XML标签。在Java和注解配置中,资源是 @Configuration 类,来源是 @Bean 方法。在大多数情况下,目标集成流解决方案基于开箱即用的组件及其配置选项。当在运行时发生异常时,堆栈跟踪中不涉及任何最终用户代码,因为执行是针对bean而不是其配置。包含bean定义的资源和来源有助于确定可能的配置错误,并提供更好的开发者体验。
从版本5.4.3开始,默认的错误通道配置了属性 requireSubscribers = true,以便在该通道上没有订阅者时(例如,当应用程序上下文停止时)不会静默忽略消息。在这种情况下,会抛出一个 MessageDispatchingException,该异常可能会传递到入站通道适配器的客户端回调中,从而对源系统中的原始消息进行否定确认(或回滚),以便重新投递或进行其他后续处理。要恢复之前的行为(忽略未分发的错误消息),必须将全局集成属性 spring.integration.channels.error.requireSubscribers 设置为 false。更多信息请参阅全局属性和PublishSubscribeChannel 配置(如果您手动配置全局 errorChannel)。
另请参阅错误处理示例以获取更多信息。