上一篇關於ActiveMQ的博客僅僅是一個簡單的小案例,真正的企業中業務更復雜,情況更多變,所以用到的配置和內容也略顯複雜。今天就來簡單看下一般企業項目中的一些常用配置。
-
通用配置
這些主要指connection和一些Destination的一些配置。
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="failover:(tcp://localhost:61616?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=10000"/>
<property name="optimizedAckScheduledAckInterval" value="10000" />
</bean>
<!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!--連接池-->
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
<property name="connectionFactory" ref="targetConnectionFactory"/>
<property name="maxConnections" value="10"/>
</bean>
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="pooledConnectionFactory"/>
</bean>
<!--Topic-->
<bean id="msgTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="com.gh.test.topic"/>
</bean>
<!--Queue-->
<bean id="msgQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="com.gh.test.queue"/>
</bean>
注意:
1、上述連接池和普通的連接方式選擇其一就好,根據具體項目選擇。
2、Topic和Queue是指消息目的地,生產者把消息發送到這裏,消費者從這裏取消息消費。
3、Queue(點對點模式),一對一,一個生產者一個消費者。Topic(訂閱發佈模式),一對多,一個生產者多個消費者。 -
生產者
生產者一般是圍繞JmsTemplate做配置,這個類是Spring對ActiveMQ的支撐類,有很多的屬性,下面的一些配置也是針對該類的一些屬性的配置,具體更詳細的大家自己看下源碼。
<!-- 消息轉換器 -->
<bean id="msgConverter" class="com.gh.test.mq.send.MsgConverter" />
<!-- 消息發送者 -->
<bean id="msgJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="explicitQosEnabled" value="true"/>
<property name="timeToLive" value="600000"/>
<property name="deliveryMode" value="2" />
<property name="priority" value="2"/>
<property name="messageConverter" ref="msgConverter"/>
</bean>
<!-- 發送工具類 -->
<bean id="msgSend" class="com.gh.test.mq.send.MsgSend">
<property name="msgJmsTemplate" ref="msgJmsTemplate"/>
<property name="msgTopic" ref="msgTopic"/>
</bean>
這裏我是做了三個配置,下面分別簡單介紹下。
1.消息轉換器:
這個類是實現MessageConverter接口,它的作用主要有兩方面,一方面它可以把我們的非標準化Message對象轉換成我們的目標Message對象,這主要是用在發送消息的時候;另一方面它又可以把我們的Message對象轉換成對應的目標對象,這主要是用在接收消息的時候。一般與監聽器(MessageListenerAdapter)聯合使用,後面具體用到了我們再講。
2.消息發送者:
connectionFactory:MQ連接,這裏沒用連接池
explicitQosEnabled:默認false,是否開啓是否開啓 deliveryMode, priority, timeToLive的配置
deliveryMode:默認2,表示持久化,1爲非持久化
priority:消息優先級,默認爲4
timeToLive:消息過期時間
messageConverter:消息轉換器
3.發送工具類:
沒什麼特殊的地方,就是集成了JmsTemplate和Destination,作爲一個工具類方便使用。項目中最好有一個。
-
消費者
在Spring整合JMS的應用中在定義消息監聽器的時候一共可以定義三種類型的消息監聽器,分別是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。注意:三種方式都要配一個DefaultMessageListenerContainer消息監聽容器。
-
MessageListener方式
這個很簡單,參考http://blog.csdn.net/u013185616/article/details/51891965 這裏的消費者配置。一般簡單的MQ應用可以直接這麼配置。 -
SessionAwareMessageListener方式
生產者就直接用上面的那個topic的配置,不再貼代碼了,看下消費者配置:
<!-- SessionAwareMessageListener方式 begin -->
<!-- 消息監聽器 -->
<bean id="sessionAwareMsgListener" class="com.gh.test.mq.receive.SessionAwareMsgReceiver" >
<property name="destination" ref="msgTopic"></property>
</bean>
<!--消息監聽容器 -->
<bean id="sessionAwareContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="msgTopic" />
<property name="messageListener" ref="sessionAwareMsgListener" />
</bean>
<!-- SessionAwareMessageListener方式 end -->
SessionAwareMsgReceiver這個類繼承了SessionAwareMessageListener,它是Spring爲我們提供的,不是標準的JMS MessageListener。MessageListener的設計只是純粹用來接收消息的,假如我們在使用MessageListener處理接收到的消息時我們需要發送一個消息通知對方我們已經收到這個消息了,那麼這個時候我們就需要在代碼裏面去重新獲取一個Connection或Session。SessionAwareMessageListener的設計就是爲了方便我們在接收到消息後發送一個回覆的消息,它同樣爲我們提供了一個處理接收到的消息的onMessage方法,但是這個方法可以同時接收兩個參數,一個是表示當前接收到的消息Message,另一個就是可以用來發送消息的Session對象。來看下消費者的代碼:
public class SessionAwareMsgReceiver implements SessionAwareMessageListener<Message>{
private static Logger log = Logger.getLogger(SessionAwareMsgReceiver.class);
public void onMessage(Message message, Session session) throws JMSException {
//前提知道生產者發的是個TextMessage類型
TextMessage msg = (TextMessage) message;
System.out.println("消息內容是:" + msg.getText());
//回傳一條消息給目的地
MessageProducer producer = session.createProducer(destination);
Message textMessage = session.createTextMessage("SessionAwareMsgReceiver已經成功收到消息。。。");
producer.send(textMessage);
}
private Destination destination;
public Destination getDestination() {
return destination;
}
public void setDestination(Destination destination) {
this.destination = destination;
}
}
在上面代碼中定義了一個SessionAwareMessageListener,在這個Listener中我們在接收到了一個消息之後,利用對應的Session創建了一個到destination的生產者和對應的消息,然後利用創建好的生產者發送對應的消息。OK,來測試一下:依然通過界面點擊發起Action調用發送消息的方式,代碼如下:
@Controller("sendMessageAction")
public class SendMessageAction {
private String msg;
@Resource
private MsgSend msgSend;
public String sendMessage() {
try{
String str = "test send textMessage";
msgSend.SendMsg(str);
msg="發送成功";
}catch(Exception e){
msg="發送失敗";
e.printStackTrace();
}
return "success";
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
這裏就用到了上面所說的發送工具類,我這裏就很簡單的寫了一個發送方法,其實這裏可以擴展很多的工具方法,對應我們想發送的不同的消息類型。來看下代碼。
public class MsgSend {
public void SendMsg(Object obj){
msgJmsTemplate.convertAndSend(msgTopic, obj);//採用轉換器發送
}
private JmsTemplate msgJmsTemplate;
private Destination msgTopic;
public JmsTemplate getMsgJmsTemplate() {
return msgJmsTemplate;
}
public void setMsgJmsTemplate(JmsTemplate msgJmsTemplate) {
this.msgJmsTemplate = msgJmsTemplate;
}
public Destination getMsgTopic() {
return msgTopic;
}
public void setMsgTopic(Destination msgTopic) {
this.msgTopic = msgTopic;
}
}
上面的代碼採用了轉換器發送,還記得上面生產者的配置中,有<property name="messageConverter" ref="msgConverter"/>這麼一句話吧,這個就是說不使用它默認的轉換器,直接用我自定義的消息轉換器,來看下它的代碼。
public class MsgConverter implements MessageConverter{
@SuppressWarnings("unchecked")
public Message toMessage(Object object, Session session)
throws JMSException, MessageConversionException {
if(object instanceof Map){
Map<String,String> map = (Map<String, String>) object;
MapMessage msg = session.createMapMessage();
msg.setString("id", MapUtils.getString(map, "id"));
msg.setString("name", MapUtils.getString(map, "name"));
msg.setString("value", MapUtils.getString(map, "value"));
return msg;
}else if(object instanceof String){
String str = (String) object;
TextMessage msg = session.createTextMessage(str);
return msg;
}else{
return null;
}
}
public Object fromMessage(Message message) throws JMSException,
MessageConversionException {
if (message instanceof MapMessage){
MapMessage msg = (MapMessage) message;
StringBuilder mapStr = new StringBuilder("id:"+msg.getString("id")+";name:"+msg.getString("name")+";value:"+msg.getString("value"));
return mapStr.toString();
}else if(message instanceof TextMessage){
TextMessage msg = (TextMessage) message;
String msgStr = "this is textMessage:" + msg.getText();
return msgStr;
}else{
return message;
}
}
}
我們現在再來理一下這個過程:Action中通過工具類msgSend.SendMsg(str)方法,MsgSend工具類採用的是轉換器的發送方式,所以到了MsgConverter類的toMessage(Object object, Session session)方法。這裏就是講我們的剛剛的String對象轉換成Message,然後我們的消費者SessionAwareMsgReceiver收到了這條消息,又返回了了一條消息到目的地那裏。所以這個消息又被監聽到了,然後又走了一遍這個過程,是的你沒想錯,會一直走下去,停不下來。所以上面所說的Session估計不是這麼用的,具體估計還有更好的方式,只是我沒用過這種方式,也不知道。。。
-
MessageListenerAdapter方式
MessageListenerAdapter類實現了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是將接收到的消息進行類型轉換,然後通過反射的形式把它交給一個普通的Java類進行處理。簡單的說呢:就是我可以自己定義一個類,寫一個方法,參數也可以自定義,然後就用這個類來接收消息,不用再繼承和實現其他類或者接口。
MessageListenerAdapter會把接收到的消息做如下轉換:TextMessage轉換爲String對象;
BytesMessage轉換爲byte數組;
MapMessage轉換爲Map對象;
ObjectMessage轉換爲對應的Serializable對象。
所以呢,由於這種方式比較靈活,一般採用的也比較多,目前我公司這邊一個挺大的項目就是採用這種方式。來看下配置:
<!-- MessageListenerAdapter方式 begin -->
<!-- 消息監聽器 -->
<bean id="msgListenerOfAdapter" class="com.gh.test.mq.receive.MsgAdapterReceiver" />
<!-- 消息監聽適配器 -->
<bean id="msgListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="msgListenerOfAdapter"/>
<property name="defaultListenerMethod" value="receiveMsg"/>
<property name="messageConverter" ref="msgConverter"/>
</bean>
<!-- 消息監聽適配器對應的監聽容器 -->
<bean id="msgListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="msgTopic" />
<property name="messageListener" ref="msgListenerAdapter" />
</bean>
<!-- MessageListenerAdapter方式 end -->
MsgAdapterReceiver這個就是一個普通的類,沒有太多道道。重點在於msgListenerAdapter這個的配置,通過設置delegate屬性讓MQ知道我要採用msgListenerOfAdapter這個類來接收消息,通過defaultListenerMethod這個屬性讓MQ知道我要採用
receiveMsg(自定義)的方法來實現接收邏輯。這裏一般需要messageConverter消息轉換配置,因爲我們自定義的方法參數可以使多種多樣,所以需要我們自己去轉換。
我們依次看下代碼,先是消費者:
public class MsgAdapterReceiver {
public void receiveMsg(String message){
System.out.println("收到消息:"+message);
}
}
很簡單吧,就一個方法,參數爲String。那麼爲了體現轉換器的作用,Action中我講發一個Map消息,然後轉換器會轉成String,被消費者所接收,看下Action代碼:
@Controller("sendMessageAction")
public class SendMessageAction {
private String msg;
@Resource
private MsgSend msgSend;
public String sendMessage() {
try{
Map map = new HashMap();
map.put("id", "1001");
map.put("name", "浩浩");
map.put("value", "你好啊");
msgSend.SendMsg(map);
msg="發送成功";
}catch(Exception e){
msg="發送失敗";
e.printStackTrace();
}
return "success";
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
解釋下代碼流程:Action中一個map類型的消息,通過工具類發送,工具類採用的是轉換器方式發送,重點來了。發送者進入toMessage方法,將Map裏面的消息轉成了MapMessage。到了接收階段,由於消費者msgListenerAdapter的參數也配置了轉換器,所以會先進轉換器MsgConverter的fromMessage方法,前一階段map消息已經被轉成了MapMessage,所以也就進了第一個if,這裏我把它轉成了String,因爲我們的消費者的入參就是String,所以也就可以被消費了。代碼over。
看下結果:
收到消息:id:1001;name:浩浩;value:你好啊
MessageListenerAdapter除了會自動的把一個普通Java類當做MessageListener來處理接收到的消息之外,其另外一個主要的功能是可以自動的發送返回消息。這裏由於我本人沒研究過,項目中也沒用過,所以不再闡述,有機會再說這個吧。
上述內容只是個人項目中所用的經驗,如有問題和錯誤,定當虛心學習。