異常處理

RabbitMQ Java 用戶端的許多操作都可能拋出受檢例外。例如,在許多情況下可能會拋出 IOException 實例。RabbitTemplateSimpleMessageListenerContainer 和其他 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