javascript
jms spring_JMS和Spring:有时很重要的小事情
jms spring
JmsTemplate和DefaultMessageListenerContainer是用于訪問JMS兼容MOM的Spring幫助器。 他們的主要目標是在JMS API之上形成一層,并處理諸如事務管理/消息確認之類的基礎結構,并隱藏JMS API的某些重復和笨拙的部分(保留在那里: JMS 2.0即將來臨!)。 要使用這些幫助程序中的任何一個,都必須為其提供(至少) JMS ConnectionFactory和有效的JMS Destination 。
在應用程序服務器上運行應用程序時,很有可能使用JEE體系結構定義ConnectionFactory。 這簡化了添加ConnectionFactory及其配置參數的過程,從而允許它們以給定的別名(例如jms / myConnectionFactory)在目錄服務中發布。 在你內
應用程序,例如,如果需要更多配置來查找ConnectionFactory并將其傳遞給JmsTemplate和/或DefaultMessageListenerContainer,則可以使用JEE命名空間或JndiTemplate / JndiObjectFactoryBean bean中的“ jndi-lookup”。
后者是JMS目的地,標識要向其產生消息或從中使用消息的JMS隊列或主題。 但是,這兩個JmsTemplate作為DefaultMessageListenerContainer都有兩個不同的屬性用于注入目標。 有一種方法將目的地作為String ,將目的地作為JMS Destination類型。 Spring并沒有發明這種功能, JMS規范提到了兩種方法:
4.4.4 Creating Destination Objects Most clients will use Destinations that are JMS administered objects that they have looked up via JNDI. This is the most portable approach. Some specialized clients may need to create Destinations by dynamically manufacturing one using a provider-specific destination name. Sessions provide a JMS provider-specific method for doing this.如果將目標作為String傳遞,則助手將隱藏將它們映射到有效JMS目標所需的額外步驟。 最后,JMS會話上的createConsumer希望您在返回MessageConsumer之前傳遞Destination對象,以指示從何處使用消息。 當將目的地配置為String時,Spring會使用JMS API本身來查找目的地。 默認情況下,JmsTemplate和DefaultMessageListenerContainer具有對DestinationResolver的引用,該引用默認為DynamicDestinationResolver (稍后將對此進行詳細介紹)。 下面的代碼是從DynamicDestinationResolver中摘錄的,突出顯示的行指示使用JMS API將String轉換為Destination(在此示例中為Queue):
protected Queue resolveQueue(Session session, String queueName) throws JMSException {if (session instanceof QueueSession) {// Cast to QueueSession: will work on both JMS 1.1 and 1.0.2return ((QueueSession) session).createQueue(queueName);}else {// Fall back to generic JMS Session: will only work on JMS 1.1return session.createQueue(queueName);}}規范提到的另一種方法(JNDI方法)是將Destinations配置為應用程序服務器上的可管理對象。 這遵循ConnectionFactory的原理。 目的地發布在應用程序服務器目錄中,并且可以通過其JNDI名稱(例如jms / myQueue)進行查找。 再次,您可以在應用程序中查找JMS目標,并使用以JMS目標為參數的屬性將其傳遞給JmsTemplate和/或DefaultMessageListenerContainer。
現在,為什么我們有這兩種選擇?
我一直認為這是在方便性(動態方法)和環境透明性/可配置性(JNDI方法)之間選擇的問題。 例如:在某些情況下,物理目標的名稱可能會有所不同,具體取決于應用程序運行的環境。 如果在應用程序內部配置物理目標名稱,則顯然會失去此優勢,因為如果不重建應用程序就無法更改它們。 另一方面,如果將它們配置為受管理對象,則只需更改應用程序服務器配置中的物理目標名稱即可。
記得; 可以配置物理目標名稱很有意義。 除了目標類型之外,處理消息傳遞的應用程序也不了解其詳細信息。 消息傳遞目標沒有功能約定,并且其屬性(物理目標,持久性等)對于您編寫的代碼都不重要。 實際合同位于消息本身(標題和正文)內部。 另一方面,數據庫表只是一個例子,它確實暴露了契約并與代碼緊密耦合。 在大多數情況下,重命名數據庫表確實會影響您的代碼,因此,與消息傳遞目標相比,使這種可配置項通常沒有附加值。
最近,我發現我對這的理解還不是全部。 該規范(摘自上面某些段落的“ 4.4.4創建目標對象”)已經給出了提示:“大多數客戶端將使用目標,這些目標是通過JNDI查找的JMS管理的對象。 這是最便攜的方法。” 基本上,這告訴我們另一種方法(將目標作為String的動態方法)是“最少可移植”的方法。 對我來說,這從來都不是很清楚,因為每個提供程序都必須實現這兩種方法,但是必須在更廣泛的范圍內考慮“便攜式”。
當將Destination配置為String時,Spring在創建新的JMS Session時默認會將其轉換為JMS Desintations。 當使用DefaultMessageListenerContainer消費消息時,您處理的每條消息都在事務中發生,并且默認情況下,不合并JMS會話和使用者,因此將為每個接收操作重新創建它們。 每次容器檢查新消息和/或接收新消息時,這都會導致將String轉換為JMS Destination。 “非便攜式”方面發揮了作用,因為這還意味著此轉換的細節和成本完全取決于MOM的驅動程序/實現。 在我們的案例中,我們在Oracle AQ作為MOM提供商方面經歷了這一過程。 每次發生目標轉換時,驅動程序都會執行一個特定的查詢:
select /*+ FIRST_ROWS */ t1.owner, t1.name, t1.queue_table, t1.queue_type, t1.max_retries, t1.retry_delay, t1.retention, t1.user_comment, t2. type , t2.object_type, t2.secure from all_queues t1, all_queue_tables t2 where t1.owner=:1 and t1.name=:2 and t2.owner=:3 and t1.queue_table=t2.queue_table論壇條目可以在這里找到 。
盡管此查詢在最新的驅動程序中得到了改進(如錯誤報告中所述),但仍在數據庫上造成大量開銷。 解決此問題的兩個選項:
- 執行規范建議的操作:將目標配置為應用程序服務器上的資源。 每次應用程序服務器都會分發相同的實例,因此它們已經被緩存在那里。 即使您每次查找都會收到相同的實例,但是在使用JndiTemplate(或JndiDestinationResolver,請參見下文)時,它也會在應用程序一側被阻塞,因此即使查找本身也只會發生一次。
- 在DefaultMessageListenerContainer上啟用會話/消費者緩存。 將緩存設置為使用方時,由于使用方持有對目標的引用,因此它還會間接重用目標。 這個池是Spring添加的功能, JavaDoc說它在使用資源本地事務時是安全的,而在使用XA事務時(在JBoss 4上運行除外)“應該”是安全的。
首先可能是最好的。 但是,在我們的情況下,所有目標均已在應用程序內部定義(并且有很多目標),因此無需對其進行配置。 僅出于此技術原因而對它們進行重構將產生大量開銷,而沒有其他優勢。 第二種解決方案是最不受歡迎的解決方案,因為這將意味著需要進行額外的測試和調查,以確保沒有任何問題。 同樣,這似乎還需要做更多,因為在我們的案例中,沒有跡象表明創建會話或使用者對性能有可衡量的影響。 根據JMS規范:
4.4 Session A JMS Session is a single-threaded context* for producing and consuming messages. Although it may allocate provider resources outside the Java virtual machine, it is considered a lightweight JMS object.順便說一句; 這對于MessageConsumers / Producers也有效。 它們都綁定到一個會話,因此,如果一個會話輕量級可以打開,那么這些對象也將打開。
但是,還有第三種解決方案。 自定義的DestinationResolver。 DestinationResolver是負責從String到Destination的抽象。 缺省( DynamicDestinationResolver )在JMS會話上使用createConsumer(javax.jms.Destination)進行轉換,但是不會緩存生成的Destination。 但是,如果將Destinations在應用程序服務器上配置為資源,則可以(除了使用Spring的JNDI支持并直接注入Destination之外)還可以使用JndiDestinationResolver 。 該解析器會將提供的String視為JNDI位置(而不是物理目標名稱),并為您執行查找。 默認情況下,它將緩存生成的目標,避免任何后續的JNDI查找。 現在,還可以將JndiDestinationResolver配置為DynamicDestinationResolver的緩存裝飾器。 如果將fallback設置為true,它將首先嘗試將String用作從JNDI查找的位置,如果失敗,它將使用JMS API將我們的String傳遞給DynamicDestinationResolver,以將我們的String轉換為Destination。 在這兩種情況下,都將生成的目標存儲在緩存中,因此將從緩存中為對同一目標的下一個請求提供服務。 使用此解析器,可以直接使用一個解決方案,而無需編寫任何代碼:
<bean id="cachingDestinationResolver" class="org.springframework.jms.support.destination.JndiDestinationResolver"><property name="cache" value="true"/><property name="fallbackToDynamicDestination" value="true"/> </bean><bean id="infra.abstractMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" abstract="true"><property name="destinationResolver" ref="cachingDestinationResolver"/>...</bean>通過內部使用ConcurrentHasmap存儲綁定,JndiDestinationResolver是線程安全的。 根據JMS 1.1規范(2.8多線程),JMS目標本身就具有線程安全性,并且可以安全地進行緩存:
這再次是一個很好的例子,說明簡單的事情有時會產生重要的影響。 這次,借助Spring,解決方案非常簡單。 但是,最好將緩存行為設置為默認值,因為這會使它與查找目的地的任何提供程序特定的怪癖脫鉤。 這不是默認值的原因可能是因為DefaultMessageListenerContainer支持動態更改目的地 (例如,使用JMX):
Note: The destination may be replaced at runtime, with the listener container picking up the new destination immediately (works e.g. with DefaultMessageListenerContainer, as long as the cache level is less than CACHE_CONSUMER). However, this is considered advanced usage; use it with care! 參考: JMS和Spring:有時很小的事情,有時在我們的JCG合作伙伴 Koen Serneels(技術博客博客)上很重要。翻譯自: https://www.javacodegeeks.com/2013/04/jms-and-spring-small-things-sometimes-matter.html
jms spring
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的jms spring_JMS和Spring:有时很重要的小事情的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gradle spring_使用Grad
- 下一篇: NAGIOS安装指南