我们使用jms一般是使用spring-jms和activemq相结合,通过spring的JmsTemplate发送消息到指定的Destination。
首先定义一个activemq的连接池:
<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"> <property name="connectionFactory"> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="failover:(tcp://192.168.20.23:61616?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=1000" /> </bean> </property> <property name="maxConnections" value="1"></property> </bean>
定义jmsTempalte的实例:
<bean id="oamTmpTopic" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="oamTmpTopic" /> </bean> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="connectionFactory" /> <property name="defaultDestination" ref="oamTmpTopic" /> <property name="explicitQosEnabled" value="true" /> <property name="deliveryMode" value="1" /> </bean>
定义生产者SendMessage.java:
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; public class SendMessage { private JmsTemplate jmsTemplate; private String topicName; private Topic topic; public void setJmsTemplate(JmsTemplate jmsTemplate) { this.jmsTemplate = jmsTemplate; } public void setTopicName(String topicName) { this.topicName = topicName; } public void sendMessage(final String message) { try { if (topic == null) { topic = jmsTemplate.getConnectionFactory().createConnection() .createSession(false, Session.AUTO_ACKNOWLEDGE) .createTopic(topicName); } jmsTemplate.send(topic,new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session .createTextMessage(message); return textMessage; } }); } catch (JMSException e) { e.printStackTrace(); } } }
定义消费者TestListener.java:
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.Session; import javax.jms.Topic; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.listener.DefaultMessageListenerContainer; public class TestListener implements MessageListener{ private JmsTemplate jmsTemplate; private String topicName; public TestListener(JmsTemplate jmsTemplate,String topicName){ this.jmsTemplate = jmsTemplate; this.topicName = topicName; Topic topic; try { topic = this.jmsTemplate.getConnectionFactory().createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createTopic(this.topicName); DefaultMessageListenerContainer dmc = new DefaultMessageListenerContainer(); dmc.setPubSubDomain(true); dmc.setDestination(topic); dmc.setConnectionFactory(this.jmsTemplate.getConnectionFactory()); dmc.setPubSubNoLocal(true); dmc.setMessageListener(this); dmc.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE); dmc.initialize(); dmc.start(); } catch (JMSException e) { e.printStackTrace(); } } @Override public void onMessage(Message message) { System.out.println(message); } }
然后在spring的配置文件中定义相关的bean:
<bean id="testListener" class="net.kentop.test.jms.TestListener"> <constructor-arg ref="jmsTemplate"></constructor-arg> <constructor-arg value="testTopic"></constructor-arg> </bean> <bean id="sendMessage" class="net.kentop.test.jms.SendMessage"> <property name="jmsTemplate" ref="jmsTemplate"></property> <property name="topicName" value="testTopic"></property> </bean>
编写测试代码BeanTest.java:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class BeanTest { public static ApplicationContext context = new ClassPathXmlApplicationContext("infrastructure-config.xml"); public static void main(String args[]){ SendMessage sendMessage = (SendMessage) context.getBean("sendMessage"); sendMessage.sendMessage("hahahha,我来测试了"); sendMessage.sendMessage("dfsdfsfsdfsdfsdf"); sendMessage.sendMessage("come on baby!"); sendMessage.sendMessage("hahahha,我来测试了2"); sendMessage.sendMessage("dfsdfsfsdfsdfsdf2"); sendMessage.sendMessage("come on baby!2"); sendMessage.sendMessage("hahahha,我来测试了3"); sendMessage.sendMessage("dfsdfsfsdfsdfsdf3"); sendMessage.sendMessage("come on baby!3"); sendMessage.sendMessage("hahahha,我来测试了4"); sendMessage.sendMessage("dfsdfsfsdfsdfsdf4"); sendMessage.sendMessage("come on baby!4"); } }
但是这个时候会发现,消费者是无法接收到消费者消息的。因为我们在定义消费者时,定义了以下的代码:
DefaultMessageListenerContainer dmc = new DefaultMessageListenerContainer(); dmc.setPubSubDomain(true); dmc.setDestination(topic); dmc.setConnectionFactory(this.jmsTemplate2.getConnectionFactory()); dmc.setPubSubNoLocal(true); dmc.setMessageListener(this); dmc.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE); dmc.initialize(); dmc.start();
上面的代码中的:
dmc.setPubSubNoLocal(true);
当设置pubSubNoLocal为true时,消费者不会接收来自同一个连接的消息。因为我们在上面的配置文件中定义了连接池的最大连接数为1,因此每次使用的连接都是同一个连接,所以就消费者就接收不到消息。只有当pubSubNoLocal为false时,消费者才能接收到来自同一个连接的消息。
当然,也可以设置连接池的最大连接数为多个,比如为10,这样就可能不会每次都是用同一个连接,消费者也可以接收到消息。但是这样的话,不是每个消息都可以接收到,因为这样的话不排除有时候消费者和生产者有使用同一个连接的可能。如果一定要设置pubSubNoLocal为true的话,那么就必须要使用不同的连接。
在这里也要注意的是:
dmc.setPubSubDomain(true);
当消费者要接收topic的消息时,pubSubDomain必须设置为true。当消费者要接收queue的消失时,pubSubDomain必须设置为false。
当然也可以使用两个不同的连接,一个连接被生产者使用,另外一个连接被消费者使用。这样的话,即使设置:
dmc.setPubSubNoLocal(true);
pubSubNoLocal为true,消费者也可以接收到消息。
比如,我们再增加一个activemq的连接池,这个连接池的最大连接数为1。
<bean id="connectionFactory2" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop"> <property name="connectionFactory"> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="failover:(tcp://192.168.20.23:61616?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=1000" /> </bean> </property> <property name="maxConnections" value="1"></property> </bean>
再定义一个使用该连接池的JmsTemplate:
<bean id="jmsTemplate2" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="connectionFactory2" /> <property name="defaultDestination" ref="oamTmpTopic" /> <property name="explicitQosEnabled" value="true" /> <property name="deliveryMode" value="1" /> </bean>
修改一下消费者,让消费者使用第二个连接池来接收消息:
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.Session; import javax.jms.Topic; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.listener.DefaultMessageListenerContainer; public class TestListener implements MessageListener{ private JmsTemplate jmsTemplate; private JmsTemplate jmsTemplate2; private String topicName; public TestListener(JmsTemplate jmsTemplate,String topicName,JmsTemplate jmsTemplate2){ this.jmsTemplate = jmsTemplate; this.topicName = topicName; this.jmsTemplate2 = jmsTemplate2; Topic topic; try { topic = this.jmsTemplate.getConnectionFactory().createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createTopic(this.topicName); DefaultMessageListenerContainer dmc = new DefaultMessageListenerContainer(); dmc.setPubSubDomain(true); dmc.setDestination(topic); dmc.setConnectionFactory(this.jmsTemplate2.getConnectionFactory()); dmc.setPubSubNoLocal(true); dmc.setMessageListener(this); dmc.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE); dmc.initialize(); dmc.start(); } catch (JMSException e) { e.printStackTrace(); } } @Override public void onMessage(Message message) { System.out.println(message); } }
修改相关的bean定义:
<bean id="testListener" class="net.kentop.test.jms.TestListener"> <constructor-arg ref="jmsTemplate"></constructor-arg> <constructor-arg value="testTopic"></constructor-arg> <constructor-arg ref="jmsTemplate2"></constructor-arg> </bean>
这样的话,启动测试程序,即使pubSubNoLocal为true,但是因为消费者和生产者使用的不是同一个连接,所以消费者可以接收到生产者的消息。