Spring中使用JMS


Spring中使用JMS

       JMS為了Java開發人員與消息代理(message broker)交互和收發消息提供了一套標准API。而且,由於每個message broker都支持JMS,所以我們就不需要學習額外的消息API了。但是,由於JMS是如此的通用以至於使用它並不是十分方便。

<!--[if !supportLists]-->1.      <!--[endif]-->處理JMS樣本代碼

我們還記得一般的JDBC是如何笨拙地處理連接、語句、結果集和異常的。不幸地是,JMS處理方式和它類似,有很多樣本代碼。很多JMS例子中的代碼都是重復的。因此,與JdbcTemplate類似,Spring提供了JmsTemplate來處理樣本代碼。

<!--[if !supportLists]-->2.      <!--[endif]-->使用JMS模板

你可以使用JmsTemplate來創建連接,獲取會話並進行收發消息。這樣就可以只關注於發送消息的開發或是處理接收消息。另外,JmsTemplate還可以細化JMSException的處理。JmsTemplate能夠捕獲該異常並不同的未檢查(unchecked)子類異常,如:DestinationResolutionException、IllegalStateException等。

其實,JMSException提供了一組子類用於詳盡地表明異常原因,但是他們都是checked類型的,所以也就必須被捕獲,而JmsTemplate將會為你做這件事情。

綁定JmsTemplate

       若要使用JmsTemplate,你需要在Spring配置文件中將其聲明為一個bean,方法如下:

<bean id="jmsTemplate"

    class="org.springframework.jms.core.JmsTemplate">

    <property name="connectionFactory" ref="connectionFactory" />

</bean>

因此JmsTemplate需要知道如何連接到message broker,所以我們需要設置connectionFactory屬性,令其引用一個JMS的ConnectionFactory接口實現類,在這里是connectionFactory。JmsTemplate還有一些其他屬性,如果需要我們再介紹。

消息發送

對於RoadRantz例子來說,RoadRantz應用會給每個motorist發送一些有用的第三方信息,如果motorist對此感興趣,他的信息就將被加到一個單獨的銷售系統中。當首次注冊時,如果用戶選擇接收第三方信息,那么他們的名字和郵件地址就以JMS消息的方式被發送到這個系統中。

而在RoadRantz端,我們希望使用JmsTemplate來發送motorist信息給Roadantz銷售系統。下面的RantzMarketingGateImpl即實現了與銷售系統的交互。

public class RantzMarketingGatewayImpl

implements RantzMarketingGateway {

       public RantzMarketingGatewayImpl() {}

       public void sendMotoristInfo(final Motorist motorist) {

              jmsTemplate.send(destination, new MessageCreator() {

       public Message createMessage(Session s) throws JMSException

       {

              MapMessage msg = s.createMapMessage();

              msg.setString(“lastName”, motorist.getLastName());

              msg.setString(“firstName”, motorist.getFirstName());

              msg.setString(“email”, motorist.getEmail());

              return msg;

       }

});

}

private JmsTemplate jmsTemplate;

public void setJmsTemplate(JmsTemplate jmsTemplate) {

       this.jmsTemplate = jmsTemplate;

}

private Destination destination;

public void setDestination(Destination destination) {

       this.destination = destination;

}

}

上面代碼中的sendMotoristInfo方法最為關鍵。其實它只是調用JmsTemplate的send方法來發送消息。

       send方法的第一個參數是一個JMS Destination對象。這里使用了destination屬性被注入到RantzMarketingGatewayImpl類。當調用send方法時,JmsTemplate將會獲取一個JMS連接和會話並在發送端發送消息。代碼中的MessageCreator用於創建消息,這里使用了一個匿名內部類。MessageCreator類的createMessage方法使用JMS的MapMessage來保存motorist的姓名和郵件地址。createMessage方法是一個回調方法,JmsTemplate使用它來創建用於被發送的消息。

       JmsTemplate和Destination通過setter注入到RantzMarketingGateImpl。所以,在Spring中配置RantzMarketingGateImpl時,我們必須指定jmsTemplate和rantzDestination bean的引用:

<bean id="marketingGateway"

    class="org.roadrantz.marketing.RantzMarketingGatewayImpl">

    <property name="jmsTemplate" ref="jmsTemplate" />

    <property name="destination" ref="rantzDestination” />

</bean>

值得注意的是,sendMotorist方法只關注於消息的創建和發送——JmsTemplate為我們處理其他的代碼,而且我們不需要手工去編寫捕獲JMSException的代碼,JmsTemplate將會捕獲被拋出的JMSException並將其以Spring的未檢查異常重新拋出。

設置默認destination

       前面我們已經指定了一個Destination用於將消息發送給它。這種方式非常方便,但是對於RantzMarketingGateImpl而言,我們總是發送motorist消息給相同的destination,所以這種方式就不那么方便了。除了每次發送消息時顯式地指定destination,我們可以采用默認destination的方式:

<bean id="jmsTemplate"

    class="org.springframework.jms.core.JmsTemplate">

    <property name="connectionFactory" ref="connectionFactory" />

    <property name="defaultDestination" ref="rantzDestination" />

</bean>

現在調用send方法就不用再指定了destination參數了,只需要一個MessageCreator即可。JmsTemplate會將消息發送到默認的destination處,我們也不需要在RantzMarketingGateImpl的bean配置中指定destination了:

<bean id="marketingGateway"

    class="org.roadrantz.marketing.RantzMarketingGatewayImpl">

    <property name="jmsTemplate" ref="jmsTemplate" />

</bean>

因為我們不再需要在RantzMarketingGateImpl中指定destination,所以destination屬性和代碼中的setter方法都可以去掉了。

接收消息

JmsTemplate可以負責發送消息,它可以接收消息嗎?是的,它可以。實際上用JmsTemplate接收消息更簡單。你只需調用JmsTemplate的receive方法。

public class MarketingReceiverGatewayImpl {

       public MarketingReceiverGatewayImpl() {}

       public SpammedMotorist receiveSpammedMotorist() {

              MapMessage msg = (MapMessage)jmsTemplate.receive();

              SpammedMotorist motorist = new SpammedMotorist();

              try {

                     motorist.setFistName(msg.getString(“firstName”));

                     motorist.setLastName(msg.getString(“lastName”));

                     motorist.setEmail(msg.getString(“email”));

}

catch (JMSException e) {

       throw JmsUtils.convertJmsAccessException(e);

}

Return motorist;

}

private JmsTemplate jmsTemplate;

public void setJmsTemplate(JmsTemplate jmsTemplate) {

       this.jmsTemplate = jmsTemplate;

}

}

當調用receive方法時,JmsTemplate會再次幫我們處理那些諸如連接,會話之類的樣本工作,然后再調用receive方法。JmsTemplate的receive方法是同步的。默認情況下,調用receive方法之后將會等待消息發送至destination。不過你可以指定一個接收超時receiveTimeout屬性,例如:

<bean id="jmsTemplate"

    class="org.springframework.jms.core.JmsTemplate">

    <property name="connectionFactory" ref="connectionFactory" />

    <property name="defaultDestination" ref="rantzDestination" />

    <property name="receiveTimeout" value="60000" />

</bean>

當然,你可以使用指定的destination,如:

MapMessage msg = (MapMessage) jmsTemplate.receive(destination);

另外,你還可以通過名字來指定一個destination,並讓Spring的destination解析器自動地解析destination,如:

MapMessage msg =

(MapMessage) jmsTemplate.receive(“rantz.marketing.queue”);

同步接收消息並不是唯一的選擇。Spring也提供了消息的異步接收方式。下面讓我們看看如何讓JmsTemplate自動實現XML消息和Java對象之間的轉換。

<!--[if !supportLists]-->3.      <!--[endif]-->消息轉換

被發送的消息是由createMessage方法中的異步MessageCreator實例創建的,通過使用Motorist對象屬性將其放入一個MapMessage對象。同時在接收端,MarketingReceiverGatewayImpl的receiveSpammedMotorist方法會從消息中獲取值並賦值到一個SpammedMotorist對象中。本例中的轉換工作並不復雜,但如果應用中的多個點多需要接收或發送相同的消息,那么就會出現很多重復代碼。

Spring提供了專門用於消息轉換的接口:MessageConverter。

public interface MessageConverter {

       public Message toMessage(Object object, Session session);

       public Object fromMessage(Message message);

}

MessageConverter接口很簡單,只有兩個方法。如果要發送消息,toMessage方法會將一個Java對象轉換成消息。在接收端,fromMessage方法將消息轉換成Java對象。我們需要在應用中實現該接口

收發轉換過的消息

       雖然我們可以使用toMessage和fromMessage方法來發送和接收消息,但Spring提供了更好的方法。除了顯式地調用toMessage方法,我們可以調用JmsTemplate的convertAndSend方法。這樣的話,RantzMarketingGateImpl的sendMotoristInfo方法就可以得到極大地簡化:

public void sendMotoristInfo(final Motorist motorist) {

       jmsTemplate.convertAndSend(motorist);

}

JmsTemplate的convertAndSend方法會自動地調用toMessage方法用於發送消息。不適用默認destination的調用方式如下:

jmsTemplate.convertAndSend(destination, motorist);

jmsTemplate.convertAndSend(“rantz.marketing.queue”, motorist);

在接收端,我們也不必調用fromMessage方法,相反,我們可以使用receiveAndConvert方法:

public SpammedMotorist receiveSpammedMotorist() {

       return (SpammedMotorist) jmsTemplate.receiveAndConvert();

}

同樣你也可是傳遞一個destination參數來使用非默認的destination。現在還有一個問題需要搞清楚,JmsTemplate的convertAndSend和receiveAndConvert方法使用消息轉化器來進行消息轉換,那么JmsTemplate是怎么知道這個消息轉換器呢?

指定消息轉換器

       為了使之能夠工作,我們需要在Spring中對其進行配置:

<bean id="motoristConverter"

    class="com.roadrantz.marketing.MotoristMessageConverter" />

然后,在JmsTemplate的配置中你需要將此converter指定到messageConverter屬性上,如:

<bean id="jmsTemplate"

    class="org.springframework.jms.core.JmsTemplate">

    <property name="connectionFactory" ref="connectionFactory" />

    <property name="defaultDestination" ref="rantzDestination" />

    <property name="receiveTimeout" value="60000" />

    <property name="motoristConverter" ref="motoristConverter" />

</bean>

       你可能已經發現了JdbcTemplate和JmsTemplate的許多相同之處,下面就讓我們看看Spring是如何通過JmsGatewaySupport類來構建JMS通路的。

<!--[if !supportLists]-->4.      <!--[endif]-->JMS通路支持類

你可能還記得,因為有了JdbcDaoSupport這個用於編寫基於JDBC的DAO的類,Spring可以更加方便地使用JdbcTemplate。同樣地,Spring提供了JmsGatewaySupport類,它是提供JMS通路支持的基類。

目前為止我們創建了jmsTemplate和destination屬性。盡管現在的代碼量不多,但是如果應用中存在多個JMS通路,那必將導致大量重復的代碼。為了解決這個問題,我們需要令RantzMarketingGateImpl類繼承JmsGatewaySupport類,例如:

public class RantzMarketingGatewayImpl extends JmsGatewaySupport

       implements RantzMarketingGateway {

       public RantzMarketingGatewayImpl() {}

       public void sendMotoristInfo(final Motorist motorist) {

              getJmsTemplate().send(“rantz.marketing.queue”,

                     new MessageCreator() {

                     public Message createMessage(Session session)

throws JMSException {

MapMessage msg = session.createMapMessage();

msg.setString(“lastName”, motorist.getLastName());

msg.setString(“firstName”, motorist.getFirstName());

msg.setString(“email”, motorist.getEmail());

return msg;

                     }

              });

}

}

注意jmsTemplate屬性和setJmsTemplate方法不見了。這個版本的RantzMarketingGateImpl會調用getJmsTemplate來獲取JmsTemplate對象。所以,jmsTemplate和它的setter方法就不需要了。

       不過JmsGatewaySupport是從哪里獲得JmsTemplate對象的呢?你可以將一個JmsTemplate對象直接注入到jmsTemplate屬性中,你也可以利用一個連接工廠屬性:

<bean id="marketingGateway"

    class="com.roadrantz.marketing.RantzMarketingGatewayImpl" />

    <property name="connectionFactory" ref="connectionFactory" />

</bean>

這樣配置的話,JmsGatewaySupport將自動根據指定的連接工廠來創建JmsTemplate對象。在Spring中不需額外聲明JmsTemplate。不過這種方法也有不足之處:

·你只能為一個JmsTemplate指定一個默認destination。如果JmsGatewaySupport創建了自己的JmsTemplate,你就不能再指定默認destination了。你就只能顯式地調用帶有destination參數的send或receive方法。

·你只能為一個JmsTemplate編寫一個消息轉換器。如果JmsGatewaySupport創建了JmsTemplate,你就不能使用消息轉換器了。你也必須顯式地處理消息轉換。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM