《Spring 5 官方文件》26. JMS
26.2 Spring JMS的使用
26.2.1 JmsTemplate
JmsTemplate
類是JMS核心包中的中心類。它簡化了 JMS 的使用,因為在傳送或同步接收訊息時它幫我們處理了資源的建立和釋放。
使用JmsTemplate
的程式碼只需要實現規範中定義的回撥介面。在JmsTemplate
中通過呼叫程式碼讓MessageCreator
回撥介面用所提供的會話(Session
)建立訊息。然而,為了顧及更復雜的 JMS API 應用,回撥介面SessionCallback
將 JMS 會話提供給使用者,回撥介面ProducerCallback
則公開了Session
和MessageProducer
JMS API 公開了傳送方法的兩種型別,一種接受交付模式、優先順序和存活時間作為服務質量(QOS)引數,另一種則使用預設值作為 QOS 引數(無需引數)方式。由於 JmsTemplate 中有很多傳送方法,QOS 引數用 bean 屬性進行暴露設定,從而避免在一系列傳送方法中的重複。同樣地,使用setReceiveTimeout
屬性設定用於同步接收呼叫的超時值。
一些 JMS 提供者通過配置ConnectionFactory
,管理方式上允許預設的 QOS值 的設定。MessageProducer
的傳送方法send(Destination destination, Message message)
JmsTemplate
必須通過設定布林值屬性isExplicitQosEnabled
為true,使它能夠使用自己的QOS值。
為了方便起見,JmsTemplate
還暴露了一個基本的請求-回覆操作,允許在一個作為操作一部分而被建立的臨時佇列上,進行訊息的傳送與等待回覆。
配置的
JmsTemplate
類的例項是執行緒安全的。這很重要,因為這意味著你可以配置一個JmsTemplate
單例,然後安全地將這個共享引用注入給多個協作者。 要清楚,保持對ConnectionFactory
引用的JmsTemplate
是有狀態的,但該狀態不是會話狀態。
從 Spring Framework 4.1開始,JmsMessagingTemplate
構建在JmsTemplate
之上,並提供與訊息抽象層(即org.springframework.messaging.Message
)的整合。 這允許你以通用的方式來建立要傳送的訊息。
26.2.2 Connections
JmsTemplate
需要一個對ConnectionFactory
的引用。 ConnectionFactory
是 JMS 規範的一部分,並被作為使用 JMS 的入口。客戶端應用通常作為一個工廠配合 JMS 提供者去建立連線,並封裝一系列的配置引數,其中一些是和供應商相關的,例如 SSL 的配置選項。
當在 EJB 內使用 JMS 時,供應商提供 JMS 介面的實現,以至於可以參與宣告式事務的管理,提供連線池和會話池。為了使用這個實現,J2EE 容器一般要求你在 EJB 或 servlet 部署描述符中將 JMS 連線工廠宣告為 resource-ref。為確保可以在 EJB 內使用JmsTemplate
的這些特性,客戶端應當確保它能引用其中的ConnectionFactory
實現。
快取訊息資源
標準的API涉及建立許多中間物件。要傳送訊息,將執行以下“API”步驟
ConnectionFactory->Connection->Session->MessageProducer->send
在ConnectionFactory
和Send
操作之間,有三個中間物件被建立和銷燬。 為了優化資源使用並提高效能,提供了兩個ConnectionFactory
的實現。
SingleConnectionFactory
Spring 提供ConnectionFactory
介面的一個實現,SingleConnectionFactory
,它將在所有的createConnection
呼叫中返回同一個的連線,並忽略close
的呼叫。這在測試和獨立的環境中相當有用,因為同一個連線可以被用於多個JmsTemplate
呼叫以跨越多個事務。 SingleConnectionFactory
接受一個通常來自 JNDI 的標準ConnectionFactory
的引用。
CachingConnectionFactory
CachingConnectionFactory
擴充套件了SingleConnectionFactory
的功能,它添加了會話、訊息生產者、訊息消費者的快取。 初始快取大小設定為1,使用sessionCacheSize
屬性來增加快取會話的數量。請注意,實際快取會話的數量將超過該值,因為會話的快取是基於確認模式的,因此當設定sessionCacheSize
為1時,快取的會話可能達到4個,因為每個確認模式都會快取一個。當快取的時候,訊息生產者和訊息消費者被快取在他們自己的會話中同時也考慮到生產者和消費者的唯一屬性。訊息生產者基於他們的目的地被快取,訊息消費者基於目的地、選擇器、非本地傳送標識和持久訂閱名稱(假設建立持久消費者)的組合鍵被快取。
26.2.3 Destination 管理
目的地(Destination),像ConnectionFactories
一樣,是可以在 JNDI 中進行儲存和提取的 JMS 管理物件。當配置一個 Spring 應用上下文,可以使用 JNDI 工廠類JndiObjectFactoryBean / <jee:jndi-lookup>
將你的物件引用依賴注入到 JMS 目的地。然而,如果在應用中有大量的目的地,或者 JMS 供應商提供了特有的高階目的地管理特性,這個策略常常顯得很笨重。高階目的地管理的例子如建立動態目的地或支援目的地的命名層次。JmsTemplate
將目的地名稱到 JMS 目的地物件的解析委派給一個DestinationResolver
介面的實現。DynamicDestinationResolver
是JmsTemplate
使用的預設實現,並且提供動態目的地解析。同時JndiDestinationResolver
作為 JNDI 包含的目的地的服務定位器,並且可選擇地退回來使用DynamicDestinationResolver
提供的行為。
相當常見的是在一個 JMS 應用中所使用的目的地只有在執行時才知道,因此,當一個應用被部署時,它不能被建立。這經常是因為互動系統元件之間的共享應用邏輯是在執行時按照已知的命名規範建立目的地。雖然動態目的地的建立不是 JMS 規範的一部分,但是許多供應商已經提供了這個功能。使用者為所建的動態目的地定義名稱,這樣區別於臨時的目的地,並且動態目的地不會被註冊到 JNDI 中。建立動態目的地所使用的 API 在不同的供應商之間差別很大,因為目的地所關聯的屬性是供應商特有的。然而,有時由供應商作出的一個簡單的實現選擇是忽略 JMS 規範中的警告,並使用TopicSession
的方法createTopic(String topicName)
或者QueueSession
的方法createQueue(String queueName)
來建立一個擁有預設屬性的新目的地。依賴於供應商的實現,DynamicDestinationResolver
也可能建立一個物理上的目的地,而不是隻是解析。
布林屬性PubSubDomain
被用來配置JmsTemplate
使用什麼樣的 JMS 域。這個屬性的預設值是 false,使用點到點的佇列。JmsTemplate
使用該屬性決定了通過DestinationResolver
的實現來解析動態目的地的行為。
你還可以通過屬性DefaultDestination
配置一個帶有預設目的地的JmsTemplate
。預設的目的地被使用時,它的傳送和接收操作不需要指定一個特定的目的地。
26.2.4 訊息監聽容器
在 EJB 世界裡,JMS 訊息最常用的功能之一是用於實現訊息驅動 Bean(MDB)。Spring 提供了一個方法來建立訊息驅動的 POJO(MDP),並且不會把使用者繫結在某個 EJB 容器上。(參見第26.4.2節“非同步接收 – 訊息驅動的 POJO”,詳細介紹了 Spring 的 MDP 支援)。從 Spring Framework 4.1開始,端點方法可以簡單使用 @JmsListener 註解,參見第26.6節“註釋驅動的偵聽器端點 “ 更多細節。
用訊息監聽容器從 JMS 訊息佇列接收訊息,並驅動被注入該訊息的訊息監聽器。監聽容器負責訊息接收和分發到對應的監聽器的所有執行緒。訊息監聽容器是 MDP 和訊息提供者之間的一箇中介,負責處理訊息接收的註冊、事務管理、資源獲取與釋放和異常轉換等。這使得應用開發人員可以專注於開發和接收訊息(可能的響應)相關的(複雜)業務邏輯,把和 JMS 基礎框架有關的樣板化的部分委託給框架處理。
有兩個標準的 JMS 訊息監聽容器包含在 Spring 中,每一個都有它特殊的功能集。
SimpleMessageListenerContainer
這個訊息監聽容器是兩種標準風格中比較簡單的一個,它在啟動時建立固定數量的 JMS 會話和消費者,使用標準的 JMS 方法MessageConsumer.setMessageListener()
註冊監聽,並且讓 JMS 提供者做監聽回撥。它不適於動態執行要求或者參與額外管理事務。相容上,它與標準的 JMS 規範很近,但它通常情況下不相容 Java EE 的 JMS 限制條件。
雖然
SimpleMessageListenerContainer
不允許參與外部管理的事務,但它確實支援原生 JMS 事務:只需將sessionTransacted
標誌切換為 true,或者在名稱空間中將acknowledge
屬性設定為 transacted:監聽器丟擲的異常將會導致回滾,然後訊息被重新傳遞。或者,考慮使用CLIENT_ACKNOWLEDGE
模式,在異常的情況下提供重新傳遞,但沒有使用事務會話,因此在事務協議中不包括任何其他會話操作(例如傳送響應訊息)。
DefaultMessageListenerContainer
這個訊息監聽容器用於大部分的案例中。與SimpleMessageListenerContainer
相反的是,這個容器適於動態執行要求並且能參與額外管理事務。 在配置JtaTransactionManager
的時候,每一個被接收的訊息使用 XA 事務註冊,因此可能利用 XA 事務語法處理。該監聽容器在 JMS 提供者低要求、高階功能(如外部管理事務的參與)以及與 Java EE 環境的相容性之間取得了良好的平衡。
容器快取等級可以定製,注意當快取不可用的時候,每一次訊息接收,一個新的連線和新的會話就會被建立。使用高負載的非持久化訂閱可能導致訊息丟失,在這種情況下,確保使用合適的快取等級。
當代理掛掉時,此容器也具備可恢復的能力。預設情況下,一個簡單的BackOff
實現會每5秒重試一次。可以為更細粒度的恢復選項指定自定義的BackOff
實現,請參見ExponentialBackOff
示例。
與同級的
SimpleMessageListenerContainer
一樣,DefaultMessageListenerContainer
支援原生 JMS 事務,並允許自定義確認模式。如果可行的話,強烈建議您使用外部管理的事務:即,如果你可以忍受在 JVM 掛掉的情況下偶爾會重複傳送訊息。業務邏輯中的自定義重複訊息檢測步驟可能涵蓋這些情況,例如以業務實體的形式存在的檢查或協議表檢查。這樣的安排將比任何其他方式顯著更有效:用 XA 事務(通過使用JtaTransactionManager
配置你的DefaultMessageListenerContainer
)來包裹整個過程,覆蓋了 JMS 訊息的接收以及訊息監聽器中業務邏輯的執行(包括資料庫操作等)。
26.2.5 事務管理
Spring 提供了一個JmsTransactionManager
用於對 JMS ConnectionFactory
做事務管理。這將允許 JMS 應用利用 Spring 的事務管理特性。第13章事務管理中所述的 Spring 的託管事務功能。JmsTransactionManager
在執行本地資源事務管理時將從指定的ConnectionFactory
繫結一個ConnectionFactory/Session
這樣的配對到執行緒中。JmsTemplate
會自動檢測這樣的事務資源,並對它們進行相應操作。
在 Java EE 環境中,ConnectionFactory
會池化連線和會話,這樣這些資源將會在整個事務中被有效地重複利用。在一個獨立的環境中,使用 Spring 的SingleConnectionFactory
時所有的事務將公用一個JMS 連線,但是每個事務將保留自己獨立的會話。或者,請考慮使用具體提供者的池介面卡,如 ActiveMQ 的PooledConnectionFactory
類。
JmsTemplate
也利用JtaTransactionManager
和支援 XA 的 JMS ConnectionFactory
一起來執行分散式事務。請注意,這需要使用 JTA 事務管理器以及正確的 XA 配置的ConnectionFactory
!(檢查您的 Java EE 服務/ JMS 提供者的文件。)
在使用 JMS API 從連線中建立會話時,通過託管和非託管事務環境重用程式碼可能會令人困惑。 這是因為 JMS API 只有一種工廠方法來建立會話,它需要對事務和確認模式賦值。在託管環境中,設定這些值是環境事務性基礎架構的責任,因此供應商對 JMS 連線的包裝器將忽略這些值。 在非託管環境中使用JmsTemplate
時,可以通過使用屬性sessionTransacted
和sessionAcknowledgeMode
來指定這些值。 當與JmsTemplate
一起使用PlatformTransactionManager
時,模板將始終被賦予一個事務性 JMS 會話。