Exception Handling
许多带有 RabbitMQ Java 客户端的操作可能会抛出已检查异常。例如,有很多情况都可能会抛出 IOException
实例。RabbitTemplate
、SimpleMessageListenerContainer
和其他 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 aMessageConverter
. -
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 asMessage<Foo>
butMessage<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
.