我们使用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>
- 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();
- }
- }
- }
<wbr> </wbr>
定义消费者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>
- 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);
- <SPAN style="FONT-SIZE: small">dmc.setPubSubDomain(true);</SPAN>
<span style="font-size: 12px;"><span style="font-family: Courier New; font-size: 12px;">dmc.setPubSubDomain(true);</span></span>
当消费者要接收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>
- lt;/bean>
- <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" />
- lt;/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>
(From:http://newleague.iteye.com/blog/1162579)
附:
不要使用JmsTemplate开发高性能JMS (http://blog.csdn.net/haitingr/article/details/4377120)
JMS在J2EE中占有举足轻重的位置,随着ESB系统越来越受到重视,JMS更是倍受推崇,Spring对与JMS的解决方案JmsTemplate完全将Jms中间件的异构屏蔽,使得架构师、设计者份外兴奋,可是Spring的JmsTemplate远远没有JdbcTemplate那么成功,在高并发、高性能环境中,JmsTemplate是不适用的
以下是我在项目开发过程中几点原因,仅供参考
1、JmsTemplate采用的是”短连接“。JmsTemplate在消费/生产一条消息时,均创建一个全新Consumer/Producer,工作完毕即可关闭,此工作方式的好处是不产生死连接,但是同时带来了性能的大幅下降,Consumer/Producer的创建,是一个非常耗时的过程,需要连接JMS中间件,注册监听器,确认,关闭过程需要通知中间件卸载监听器。
2、频繁的”短连接“将对JMS中间件稳定性的影响。很多JMS中间件经受不住高并发”短连接“操作,会造成队列假死,消息丢失、消息脏读等问题,特别是在进行select操作过程中更为明显。
3、JmsTemplate限制了对特定JMS中间件的调整。虽然jms和jdbc一样,由商家提供具体的实现,但是现在的商家远远没有像对待jdbc一样对待jms(此事以ibm为最,ibm一直推荐我们废弃jms标准,使用他们提供的特殊api进行开发),开发调试过程中必然会根据不用的jms中间件进行一些参数的调整,但是规范就是规范,封装的太好了也未必是件很好的事情。
在此声明,不是说JmsTemplate设计的有问题,而是它不适合高并发、高性能环境中的生产应用系统,唯一办法就是开发一套完整的JMS操作组件代替JmsTemplate。