异常处理

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

当监听器抛出异常时,它会被包装在 ListenerExecutionFailedException 中。通常情况下,消息会被 Broker 拒绝并重新排队。将 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 来恢复之前的行为。

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