文章對SpringRedis和ActiveMQ的源碼及使用簡要分析一下,希望能起到拋磚引玉的作用。筆者之前在博客中寫了一篇關於生產消費者模式的一個工程使用案例,10W級數據更新操作__生產消費者模式。其實發布訂閱模式與生產消費者只是兩種模式上的不同,在業務處理邏輯上還是比較相似的。前者數據結構是一對多隊列模式,後者是點對點隊列模式。
Spring-Redis
Spring-Redis的隊列監聽容器爲org.springframework.data.redis.listener.RedisMessageListenerContainer
,繼承關係如下。
如何注入的監聽?
註冊監聽對象和Topic保存在Map中,注入的set方法創建MessageListener-Topic映射,決定哪個Topic由哪個MessageListener去處理。一個Listener可對應多個Topic.
Topic如何創建
Topic是個接口,有兩個實現類ChannelTopic和PatternTopic,一般Redis的發佈訂閱模式用ChannelTopic,只需要創建ChannelTopic注入到上述的Map即可。
SpringIOC一般有4種注入方式,set方法、構造器、靜態工廠方法、實例工廠方法,常見的有set和構造器注入。如果採用註解的方法的話就只需要在屬性上註解即可,Spring會通過反射進行注入。而Xml的方法只能通過上述4種方法之一。
查看ChannelTopic的源碼,發現沒有set方法注入channelName,只有一個構造器方法。因此需要通過構造器方法注入這點要尤其注意。
- MessageListener的創建和注入
org.springframework.data.redis.connection.MessageListener接口的實現類如下,顯然這不是一個標準的JMS協議接口實現:
常見的是用MessageListenerAdapter,通過注入delegate來處理訂閱的消息。需要注入3個屬性,實現handleMessage(Object[] obj)方法的delegate實例、實現Message序列化的serializer屬性、實現topicName的stringSerializer屬性。
注意:兩個序列化方法要與Redis發佈消息所用的序列化對象一致,下文會講到。
delegate處理消息對象
- 爲什麼delegate屬性需要實現handleMessage(Object[] obj)方法?
反射調用的地方如下
invoker實例初始化
methoName的獲取,默認是handleMessage
注意:delegate也可以實現MessageListener接口,實現onMessage方法處理消息。
- 序列化方法
關於在訂閱消息在哪裏將Message反序列化成Object,可以看調用serializer屬性的源碼。
RedisTemplate 中的convertAndSend方法即是把message內容發佈到channel通道中。
convertAndSend發佈時channel的序列化是採用stringSerializer,該屬性在RedisTemplate中已經初始化new了一個對象,因此默認不用注入該屬性。
關於RedisTemplate 的一些屬性
message(Value)的序列化是採用valueSerivalizer,默認爲null,需要入注入。也可以注入defaultSerializer,也可以不用注入,RedisTemplate在Spring初始化時已經new JdkSerializationRedisSerializer方法了。所以最好保證在訂閱方也採用這種方法序列化。
- SpringXml配置使用Redis發佈訂閱
<bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="messageDelegateListener" />
<property name="serializer" ref="jdkSerializer" />
</bean>
<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">
<property name="connectionFactory" ref="jedisConnectFactory"/>
<property name="messageListeners">
<map>
<entry key-ref="messageListener">
<bean class="org.springframework.data.redis.listener.ChannelTopic">
<constructor-arg value="java" />
</bean>
</entry>
</map>
</property>
</bean>
ActiveMQ
ActiveMQ消息隊列有兩種模式,點對點隊列和一對多隊列即發佈訂閱,ActiveMQ是一種實現了標準的JMS協議的中間件。
- 消息訂閱
常用訂閱監聽容器類爲org.springframework.jms.listener.DefaultMessageListenerContainer
它實現的接口和繼承的類關係如下圖
DefaultMessageListenerContainer容器主要維護一些關於消息隊列的配置和處理線程,消息處理類和序列化主要在其超類org.springframework.jms.listener.AbstractMessageListenerContainer
中進行定義。主要有兩個參數destination和messageListener,前者是JMS目的地也可以理解爲信息的來源(隊列的實例),後者是處理消息的類,這兩個參數分別需要實現javax.jms.Topic/javax.jms.Queue
接口、javax.jms.MessageListener/org.springframework.jms.listener.SessionAwareMessageListener
接口。
- 在ActiveMQ中,隊列的JMS接口都已經有實現,所以大部分情況直接拿來就用,注入隊列的名字即可。
Topic的實現類爲org.apache.activemq.command.ActiveMQTopic
;
Queue的實現類爲org.apache.activemq.command.ActiveMQQueue
;
而javax.jms.MessageListener
或org.springframework.jms.listener.SessionAwareMessageListener
作爲回調接口,則需要自己根據具體的業務邏輯去實現。當然也可選擇其中的一個實現類org.springframework.jms.listener.adapter.MessageListenerAdapter
去實現自己的業務邏輯,這個類可參考上述Redis發佈訂閱模式,MessageListenerAdapter中有諸多默認的屬性如下,不過這個使用起來比較麻煩,需要設置destinationName或其它,不建議使用。
消息發佈
org.springframework.jms.core.JmsTemplate
的send方法。配置如下兩個方法即可連接到ActiveMQ註冊地址。
JmsTemplate的繼承實現關係
ActiveMQ的SpringXml配置使用
<!-- 連接工廠 -->
<bean id="activeMqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="${AvtiveMQ.brokerURL}"/>
</bean>
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="activeMqConnectionFactory"/>
<property name="sessionCacheSize" value="100"/>
</bean>
<!-- 一對多隊列,即發佈訂閱者模式 -->
<bean id="defaultTopicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="com.topic.default"/>
</bean>
<!-- 消費者 -->
<bean id="defaultMessageQueueListener" class="com.zheng.cms.job.jms.DefaultMessageQueueListener"/>
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="defaultTopicDestination"/>
<property name="messageListener" ref="defaultMessageQueueListener"/>
<property name="sessionTransacted" value="true"/>
</bean>