异常处理
RabbitMQ Java 客户端的许多操作都可能抛出受检查异常。例如,在许多情况下都可能抛出 IOException
实例。RabbitTemplate
、SimpleMessageListenerContainer
和其他 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
来恢复到之前的行为。
从 2.1.9 版本开始,默认情况下,即使容器确认模式是 MANUAL,具有这些致命异常的消息也会被拒绝且不重新入队。这些异常通常发生在监听器被调用之前,因此监听器没有机会确认或拒绝消息,导致消息在队列中保持未确认状态。要恢复到之前的行为,请将 ConditionalRejectingErrorHandler
上的 rejectManual
属性设置为 false
。