跳到主要内容

异常处理

DeepSeek V3 中英对照 Exception Handling

在使用 RabbitMQ Java 客户端时,许多操作可能会抛出受检异常。例如,在很多情况下可能会抛出 IOException 异常。RabbitTemplateSimpleMessageListenerContainer 和其他 Spring AMQP 组件会捕获这些异常,并将它们转换为 AmqpException 层次结构中的某个异常。这些异常定义在 org.springframework.amqp 包中,而 AmqpException 是该层次结构的基类。

当监听器抛出异常时,它会被包装在一个 ListenerExecutionFailedException 中。通常,消息会被代理拒绝并重新排队。将 defaultRequeueRejected 设置为 false 会导致消息被丢弃(或路由到死信交换器)。正如在 消息监听器和异步情况 中讨论的那样,监听器可以抛出 AmqpRejectAndDontRequeueException(或 ImmediateRequeueAmqpException)来有条件地控制此行为。

然而,有一类错误是监听器无法控制其行为的。当遇到无法转换的消息时(例如,无效的 content_encoding 头信息),在消息到达用户代码之前,会抛出一些异常。如果 defaultRequeueRejected 设置为 true(默认值)(或者抛出 ImmediateRequeueAmqpException),这些消息会被反复重新投递。在 1.3.2 版本之前,用户需要编写一个自定义的 ErrorHandler,如异常处理部分所讨论的,以避免这种情况的发生。

从版本 1.3.2 开始,默认的 ErrorHandler 现在是一个 ConditionalRejectingErrorHandler,它会拒绝(并且不会重新排队)那些因不可恢复的错误而失败的消息。具体来说,它会拒绝那些因以下错误而失败的消息:

  • o.s.amqp…​MessageConversionException:在使用 MessageConverter 转换传入消息的有效负载时可能会抛出此异常。

  • o.s.messaging…​MessageConversionException:如果在映射到 @RabbitListener 方法时需要额外的转换,转换服务可能会抛出此异常。

  • o.s.messaging…​MethodArgumentNotValidException:如果在监听器中使用了验证(例如 @Valid)并且验证失败,可能会抛出此异常。

  • o.s.messaging…​MethodArgumentTypeMismatchException:如果传入的消息被转换为目标方法不正确的类型,可能会抛出此异常。例如,参数声明为 Message<Foo>,但接收到了 Message<Bar>

  • java.lang.NoSuchMethodException:在 1.6.3 版本中添加。

  • java.lang.ClassCastException:在 1.6.3 版本中添加。

你可以使用 FatalExceptionStrategy 配置该错误处理器的实例,以便用户可以为条件性消息拒绝提供自己的规则——例如,将委托实现交给 Spring Retry 中的 BinaryExceptionClassifier(参见 消息监听器和异步情况)。此外,ListenerExecutionFailedException 现在有一个 failedMessage 属性,你可以在决策中使用它。如果 FatalExceptionStrategy.isFatal() 方法返回 true,错误处理器将抛出 AmqpRejectAndDontRequeueException。默认的 FatalExceptionStrategy 在确定异常为致命时会记录一条警告消息。

从版本 1.6.3 开始,一个方便的方法是将用户异常添加到致命列表中,即通过子类化 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 并重写 isUserCauseFatal(Throwable cause) 方法,使其在致命异常时返回 true

处理 DLQ 消息的一种常见模式是为这些消息设置一个 time-to-live,并配置额外的 DLQ 设置,使得这些消息在过期后被路由回主队列进行重试。这种技术的问题是,导致致命异常的消息会无限循环。从 2.1 版本开始,ConditionalRejectingErrorHandler 会检测消息中的 x-death 头信息,该头信息会导致抛出致命异常。消息会被记录并丢弃。你可以通过将 ConditionalRejectingErrorHandler 上的 discardFatalsWithXDeath 属性设置为 false 来恢复到之前的行为。

important

从 2.1.9 版本开始,带有这些致命异常的消息会被拒绝,并且默认情况下不会重新入队,即使容器的确认模式是 MANUAL。这些异常通常会在调用监听器之前发生,因此监听器没有机会对消息进行 ack 或 nack 操作,导致消息保持在未确认状态并留在队列中。要恢复到之前的行为,请将 ConditionalRejectingErrorHandler 上的 rejectManual 属性设置为 false