异常处理
在使用 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。这些异常通常会在调用监听器之前发生,因此监听器没有机会对消息进行 ack 或 nack 操作,导致消息保持在未确认状态并留在队列中。要恢复到之前的行为,请将 ConditionalRejectingErrorHandler 上的 rejectManual 属性设置为 false。