异常处理
使用 RabbitMQ Java 客户端的许多操作都可能抛出受检异常。例如,在很多情况下可能抛出 IOException 实例。RabbitTemplate、SimpleMessageListenerContainer 以及其他 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 版本开始,带有这些致命异常的消息默认会被拒绝且不再入队,即使容器确认模式为 MANUAL 也是如此。这些异常通常发生在监听器被调用之前,因此监听器没有机会确认(ack)或拒绝(nack)消息,消息会以未确认状态留在队列中。要恢复以前的行为,请将 ConditionalRejectingErrorHandler 上的 rejectManual 属性设置为 false。
从 4.1 版本开始,ConditionalRejectingErrorHandler 暴露了 stopListenerOnFatal 布尔选项,用于抛出 FatalListenerExecutionException 而不是 AmqpRejectAndDontRequeueException。监听器容器会停止进一步消费,消息会被重新入队,供目标上的其他消费者处理。