Exception Handling

许多带有 RabbitMQ Java 客户端的操作可能会抛出已检查异常。例如,有很多情况都可能会抛出 IOException 实例。RabbitTemplateSimpleMessageListenerContainer 和其他 Spring AMQP 组件捕获这些异常并将其转换为 AmqpException 层次结构中的异常之一。这些在“org.springframework.amqp”程序包中定义,AmqpException 是该层次结构的基类。

Many operations with the RabbitMQ Java client can throw checked exceptions. For example, there are a lot of cases where IOException instances may be thrown. The RabbitTemplate, SimpleMessageListenerContainer, and other Spring AMQP components catch those exceptions and convert them into one of the exceptions within AmqpException hierarchy. Those are defined in the 'org.springframework.amqp' package, and AmqpException is the base of the hierarchy.

当监听器抛出异常时,该异常将被封装在 ListenerExecutionFailedException 中。通常,消息会被代理拒绝并重新排队。将 defaultRequeueRejected 设置为 false 会导致消息被丢弃(或路由到死信交换)。如 Message Listeners and the Asynchronous Case 中所讨论的,监听器可以抛出 AmqpRejectAndDontRequeueException(或 ImmediateRequeueAmqpException)来有条件地控制此行为。

When a listener throws an exception, it is wrapped in a ListenerExecutionFailedException. Normally the message is rejected and requeued by the broker. Setting defaultRequeueRejected to false causes messages to be discarded (or routed to a dead letter exchange). As discussed in Message Listeners and the Asynchronous Case, the listener can throw an AmqpRejectAndDontRequeueException (or ImmediateRequeueAmqpException) to conditionally control this behavior.

但是,有一类错误是侦听器无法控制行为的。当遇到不可转换的消息时(例如,无效的 content_encoding 标头),一些异常会在消息到达用户代码之前抛出。如果将 defaultRequeueRejected 设置为 true (默认) (或抛出 ImmediateRequeueAmqpException),此类消息将被反复重新传递。在 1.3.2 版本之前,用户需要编写一个自定义 ErrorHandler,如 Exception Handling 中所述,以避免这种情况。

However, there is a class of errors where the listener cannot control the behavior. When a message that cannot be converted is encountered (for example, an invalid content_encoding header), some exceptions are thrown before the message reaches user code. With defaultRequeueRejected set to true (default) (or throwing an ImmediateRequeueAmqpException), such messages would be redelivered over and over. Before version 1.3.2, users needed to write a custom ErrorHandler, as discussed in Exception Handling, to avoid this situation.

从版本 1.3.2 开始,默认 ErrorHandler 现在是一个 ConditionalRejectingErrorHandler,它会拒绝(且不重新排队)出现无法恢复错误的消息。具体而言,它拒绝出现以下错误的消息:

Starting with version 1.3.2, the default ErrorHandler is now a ConditionalRejectingErrorHandler that rejects (and does not requeue) messages that fail with an irrecoverable error. Specifically, it rejects messages that fail with the following errors:

  • o.s.amqp…​MessageConversionException: Can be thrown when converting the incoming message payload using a MessageConverter.

  • o.s.messaging…​MessageConversionException: Can be thrown by the conversion service if additional conversion is required when mapping to a @RabbitListener method.

  • o.s.messaging…​MethodArgumentNotValidException: Can be thrown if validation (for example, @Valid) is used in the listener and the validation fails.

  • o.s.messaging…​MethodArgumentTypeMismatchException: Can be thrown if the inbound message was converted to a type that is not correct for the target method. For example, the parameter is declared as Message<Foo> but Message<Bar> is received.

  • java.lang.NoSuchMethodException: Added in version 1.6.3.

  • java.lang.ClassCastException: Added in version 1.6.3.

您可以使用 FatalExceptionStrategy 配置此错误处理程序的实例,以便用户可以为有条件消息拒绝提供自己的规则 - 例如,对 Spring Retry (Message Listeners and the Asynchronous Case) 的 BinaryExceptionClassifier 委托实现。此外,ListenerExecutionFailedException 现在有一个 failedMessage 属性,您可以在决策中使用它。如果 FatalExceptionStrategy.isFatal() 方法返回 true,错误处理程序将抛出 AmqpRejectAndDontRequeueException。默认 FatalExceptionStrategy 会在确定异常为致命时记录警告消息。

You can configure an instance of this error handler with a FatalExceptionStrategy so that users can provide their own rules for conditional message rejection — for example, a delegate implementation to the BinaryExceptionClassifier from Spring Retry (Message Listeners and the Asynchronous Case). In addition, the ListenerExecutionFailedException now has a failedMessage property that you can use in the decision. If the FatalExceptionStrategy.isFatal() method returns true, the error handler throws an AmqpRejectAndDontRequeueException. The default FatalExceptionStrategy logs a warning message when an exception is determined to be fatal.

自版本 1.6.3 以来,向 fatal 列表添加用户异常的便捷方式是,对 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 进行子类化,并重写 isUserCauseFatal(Throwable cause) 方法,使其对 fatal 异常返回 true

Since version 1.6.3, a convenient way to add user exceptions to the fatal list is to subclass ConditionalRejectingErrorHandler.DefaultExceptionStrategy and override the isUserCauseFatal(Throwable cause) method to return true for fatal exceptions.

处理 DLQ 消息的常用模式是设置这些消息的 存活时间 以及额外的 DLQ 配置,这些消息将过期并路由回主队列进行重试。此技术的问题是导致 fatal 异常的消息会无限循环。从版本 2.1 开始,ConditionalRejectingErrorHandler 检测导致抛出 fatal 异常的消息上的 x-death 头。消息被记录并丢弃。你可以通过在 ConditionalRejectingErrorHandler 上将 discardFatalsWithXDeath 属性设置为 false 来还原为以前的行为。

A common pattern for handling DLQ messages is to set a time-to-live on those messages as well as additional DLQ configuration such that these messages expire and are routed back to the main queue for retry. The problem with this technique is that messages that cause fatal exceptions loop forever. Starting with version 2.1, the ConditionalRejectingErrorHandler detects an x-death header on a message that causes a fatal exception to be thrown. The message is logged and discarded. You can revert to the previous behavior by setting the discardFatalsWithXDeath property on the ConditionalRejectingErrorHandler to false.

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

Starting with version 2.1.9, messages with these fatal exceptions are rejected and NOT requeued by default, even if the container acknowledge mode is MANUAL. These exceptions generally occur before the listener is invoked so the listener does not have a chance to ack or nack the message so it remained in the queue in an un-acked state. To revert to the previous behavior, set the rejectManual property on the ConditionalRejectingErrorHandler to false.