active mq

一、简单例子

1.去官方网站下载:ActiveMQ

2.运行ActiveMQ:解压缩apache-activemq-5.9.1-bin.zip,然后双击apache-activemq-5.9.1\bin\activemq.bat运行ActiveMQ程序。启动ActiveMQ以后,登陆:http://localhost:8161/admin/,创建一个Queue,命名为FirstQueue。

注:部署需要jdk1.5及以上,编译需要jdk1.5(java5)及以上,Java的环境变量(JAVA_HOME)必须设置,即jdk安装的目录,比如C:\Program Files\jdk1.6

控制台监控用户名和密码设置:conf/jetty.xml <bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint"> 的属性authenticate修改为true,用户名和密码在conf/jetty-realm.properties设置。

Linux和Aix系统下的安装:解压:tar zxvf activemq-x.x.x.tar.gz,进入bin文件夹,运行:./activemq start &,也可以只运行:./activemq console。

3.创建Eclipse项目,所需jar包在apache-activemq-5.9.1\lib

4.新建发送类:

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;

public class Sender {
 public static void main(String[] args) {
  // ConnectionFactory :连接工厂,JMS 用它创建连接
  ConnectionFactory connectionFactory;
  // Connection :JMS 客户端到JMS Provider 的连接
  Connection connection = null;
  // Session: 一个发送或接收消息的线程
  Session session;
  // Destination :消息的目的地;消息发送给谁.
  Destination destination;
  // MessageProducer:消息发送者
  MessageProducer producer;
  // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar
  connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
  try {
   // 构造从工厂得到连接对象
   connection = connectionFactory.createConnection();
   // 启动
   connection.start();

   // 第一个参数是否使用事务:当消息发送者向消息提供者(即消息代理)发送消息时,消息发送者等待消息代理的确认,没有回应则抛出异常,消息发送程序负责处理这个错误。

   // 第二个参数消息的确认模式:AUTO_ACKNOWLEDGE :指定消息提供者在每次收到消息时自动发送确认。消息只向目标发送一次,但传输过程中可能因为错误而丢失消息。

   // CLIENT_ACKNOWLEDGE :由消息接收者确认收到消息,通过调用消息的acknowledge()方法(会通知消息提供者收到了消息)

   // DUPS_OK_ACKNOWLEDGE :指定消息提供者在消息接收者没有确认发送时重新发送消息(这种确认模式不在乎接收者收到重复的消息)。

   session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
   // 获取session注意参数值FirstQueue是一个服务器的queue,须在在ActiveMq的console配置
   destination = session.createQueue("FirstQueue");
   // 得到消息生成者【发送者】
   producer = session.createProducer(destination);
   // 设置不持久化,即MQ服务器重启消息就没了,实际根据项目决定(默认为DeliveryMode.PERSISTENT)
   producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
   // 构造消息内容
   TextMessage message = session.createTextMessage("ActiveMq 发送的消息");
   // 发送消息
   producer.send(message);
   session.commit();
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   try {
    if (null != connection)
     connection.close();
   } catch (Throwable ignore) {
    
   }
  }
 }
}

注:运行后MQ控制台查看FirstQueue是否接收到了一条消息

5.新建接收类(主要代码,其它与发送端相同):

 // ConnectionFactory :连接工厂,JMS 用它创建连接
 ConnectionFactory connectionFactory=new ActiveMQConnectionFactory("tcp://localhost:61616");
 // 构造从工厂得到连接对象
 connection = connectionFactory.createConnection();
 // 启动
 connection.start();
 // 获取操作连接
 Session session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);
 // 获取session注意参数值FirstQueue是一个服务器的queue,须在在ActiveMq的console配置
 Destination destination = session.createQueue("FirstQueue");
 // 消费者,消息接收者
 MessageConsumer consumer = session.createConsumer(destination);
 //设置接收者接收消息的等待时间
 TextMessage message = (TextMessage) consumer.receive(5000);
 if (null != message) {
     System.out.println("收到消息" + message.getText());
 }

6.优化接收类,使用监听器

 consumer.setMessageListener(new MessageListener() {
  public void onMessage(Message arg0){
   TextMessage message = (TextMessage) arg0;
   if (null != message) {
    try {
     System.out.println("收到消息" + message.getText());
    } catch (JMSException e) {
     e.printStackTrace();
    }
   }
   System.out.println();
  }
 });

注:监听器中当次消息处理完成才会处理下一个消息。当有多个监听器监听同一队列时,消息平均分配给各个监听器,即使被分配的监听器正在处理上一个消息,本次消息也会等待此监听器处理完成,而不会分配给其它空闲的监听器,在等待过程中,如此监听器connection关闭,正在等待的消息会重新分配给其它监听器执行。

注:相对于Queue,Topic为一对多发送,发送端发送的消息,每个接收端都能收到,不过接收端启动监听前发送端发出的消息就收不到了。使用Topic,在MQ控制台新建一个Topic,发送端和接收端只修改一行代码:Destination destination = session.createTopic("topic名称");

二、使用SSL协议连接

1.在MQ目录/conf/activemq.xml文件中修改以下配置

  <sslContext>  
   <sslContext keyStore="file:${activemq.base}/conf/mybroker.ks" keyStorePassword="pwd123"/>  
  </sslContext>
 

 <transportConnectors>
  <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
  <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
  <transportConnector name="ssl" uri="ssl://0.0.0.0:61617?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
  <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
  <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
  <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
  <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
 </transportConnectors>

注:broker.ks是MQ的默认证书,默认密码为password。其它不使用的协议可以删除。

2.将broker.ks broker.ts client.ks client.ts 复制到项目src下

3.发送端修改代码

 ActiveMQSslConnectionFactory connectionFactory = new ActiveMQSslConnectionFactory(
   "ssl://localhost:61617");
 connectionFactory.setKeyAndTrustManagers(getKeyManagers(
   "broker.ks", "password"), getTrustManagers("broker.ts"),
   new SecureRandom());

 public static KeyManager[] getKeyManagers(String keyStore,String keyStorePassword) throws Exception {
  System.out.println("Initiating KeyManagers");
  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(ClassLoader.getSystemResourceAsStream(keyStore),keyStorePassword.toCharArray());
  KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  kmf.init(ks, keyStorePassword.toCharArray());
  System.out.println("Initiated KeyManagers");
  return kmf.getKeyManagers();
 }

 public static TrustManager[] getTrustManagers(String trustStore)throws Exception {
  System.out.println("Initiating TrustManagers");
  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(ClassLoader.getSystemResourceAsStream(trustStore), null);
  TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  tmf.init(ks);
  System.out.println("Initiated TrustManagers");
  return tmf.getTrustManagers();
 }

注:接收端类似,传入client.ks client.ts即可

4.上面用到的证书是MQ自带的,正式环境中不可能用默认证书,否则SSL将形同虚设。因此,我们必须生成自己的证书,以保证其安全性。打开cmd 切换到项目src目录,执行以下命令:

keytool -genkey -alias broker -keyalg RSA -keystore mybroker.ks

keytool -export -alias broker -keystore mybroker.ks -file mybroker.cert

keytool -import -alias broker -keystore mybroker.ts -file mybroker.cert

keytool -genkey -alias client -keyalg RSA -keystore myclient.ks

keytool -import -alias broker -keystore myclient.ts -file mybroker.cert

说明:根据提示执行完上面的命令,src下会产生mybroker.cert mybroker.ks mybroker.ts myclient.ks myclient.ts 五个文件,将mybroker.ks复制到MQ/conf目录,修改sslContext的配置,修改代码使用自己生成的证书。

三、消息持久化

在broker中设置属性persistent=”true”(默认是true),同时发送的消息也应该是persitent类型的。ActiveMQ消息持久化有三种方式:AMQ、KahaDB、JDBC。

1.AMQ是一种文件存储形式,它具有写入速度快和容易恢复的特点。消息存储在一个个文件中,文件的默认大小为32兆,如果一条消息的大小超过了32 兆,那么这个值必须设置大点。当一个存储文件中的消息已经全部被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。默认配置如下(conf/activemq.xml中):

    <persistenceAdapter>
      <amqPersistenceAdapterdirectory="activemq-data" maxFileLength="32mb"/>
    </persistenceAdapter>

AMQ的属性:

属性名称默认值描述
directoryactivemq-data消息文件和日志的存储目录
useNIOtrue使用NIO协议存储消息
syncOnWritefalse同步写到磁盘,这个选项对性能影响非常大
maxFileLength32mb一个消息文件的大小
persistentIndextrue消息索引的持久化,如果为false,那么索引保存在内存中
maxCheckpointMessageAddSize4kb一个事务允许的最大消息量
cleanupInterval30000清除操作周期,单位ms
indexBinSize1024索引文件缓存页面数,缺省为1024,当amq扩充或者缩减存储时,会锁定整个broker,导致一定时间的阻塞,所以这个值应该调整到比较大,但是代码中实现会动态伸缩,调整效果并不理想。
indexKeySize96索引key的大小,key是消息ID
indexPageSize16kb索引的页大小
directoryArchivearchive存储被归档的消息文件目录
archiveDataLogsfalse当为true时,归档的消息文件被移到directoryArchive,而不是直接删除

2.KahaDB是基于文件的本地数据库储存形式,虽然没有AMQ的速度快,但是它具有强扩展性,恢复的时间比AMQ短,从5.4版本之后KahaDB做为默认的持久化方式。默认配置如下:

 <persistenceAdapter>
  <kahaDB directory="${activemq.data}/kahadb"/>
 </persistenceAdapter>

KahaDB的属性:

property namedefault value Comments
directoryactivemq-data 消息文件和日志的存储目录
indexWriteBatchSize1000 一批索引的大小,当要更新的索引量到达这个值时,更新到消息文件中
indexCacheSize10000 内存中,索引的页大小
enableIndexWriteAsyncfalse 索引是否异步写到消息文件中
journalMaxFileLength32mb 一个消息文件的大小
enableJournalDiskSyncstrue 是否讲非事务的消息同步写入到磁盘
cleanupInterval30000 清除操作周期,单位ms
checkpointInterval5000 索引写入到消息文件的周期,单位ms
ignoreMissingJournalfilesfalse 忽略丢失的消息文件,false,当丢失了消息文件,启动异常
checkForCorruptJournalFilesfalse 检查消息文件是否损坏,true,检查发现损坏会尝试修复
checksumJournalFilesfalse 产生一个checksum,以便能够检测journal文件是否损坏。
5.4版本之后有效的属性:
archiveDataLogsfalse 当为true时,归档的消息文件被移到directoryArchive,而不是直接删除
directoryArchivenull 存储被归档的消息文件目录
databaseLockedWaitDelay10000 在使用负载时,等待获得文件锁的延迟时间,单位ms
maxAsyncJobs10000 同个生产者产生等待写入的异步消息最大量
concurrentStoreAndDispatchTopicsfalse 当写入消息的时候,是否转发主题消息
concurrentStoreAndDispatchQueuestrue 当写入消息的时候,是否转发队列消息
5.6版本之后有效的属性:
archiveCorruptedIndexfalse 是否归档错误的索引

从5.6版本之后,有可能发布通过多个kahadb持久适配器来实现分布式目标队列存储。什么时候用呢?如果有一个快速的生产者和消费者,当某一个 时刻生产者发生了不规范的消费,那么有可能产生一条消息被存储在两个消息文件中,同时,有些目标队列是危险的并且要求访问磁盘。在这种情况下,你应该用通 配符来使用mKahaDB。如果目标队列是分布的,事务是可以跨越多个消息文件的。

每个KahaDB的实例都可以配置单独的适配器,如果没有目标队列提交给filteredKahaDB,那么意味着对所有的队列有效。如果一个队列没有对应的适配器,那么将会抛出一个异常。配置如下:

<persistenceAdapter>
 <mKahaDB directory="$/data/kahadb">
  <filteredPersistenceAdapters>      
   <!-- match all queues -->
   <filteredKahaDB queue=">">
    <persistenceAdapter>
     <kahaDB journalMaxFileLength="32mb" />
    </persistenceAdapter>
   </filteredKahaDB>             
   <!-- match all destinations -->
   <filteredKahaDB>
    <persistenceAdapter>
     <kahaDB enableJournalDiskSyncs="false" />
    </persistenceAdapter>
   </filteredKahaDB>
  </filteredPersistenceAdapters>
 </mKahaDB>
</persistenceAdapter>

如果filteredKahaDB的perDestination属性设置为true,那么匹配的目标队列将会得到自己对应的KahaDB实例。配置如下:

<persistenceAdapter>
 <mKahaDB directory="$/data/kahadb">
  <filteredPersistenceAdapters>      
   <!-- kahaDB per destinations -->
   <filteredKahaDB perDestination="true">
    <persistenceAdapter>
     <kahaDB journalMaxFileLength="32mb" />
    </persistenceAdapter>
   </filteredKahaDB>
  </filteredPersistenceAdapters>
 </mKahaDB>
</persistenceAdapter>

3.JDBC 配置JDBC适配器:

<persistenceAdapter>
 <jdbcPersistenceAdapter dataSource="#oracle-ds"
  createTablesOnStartup="false" />
</persistenceAdapter>

<bean id="oracle-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
 <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
 <property name="url" value="jdbc:oracle:thin:@10.53.132.47:1521:activemq" />
 <property name="username" value="activemq" />
 <property name="password" value="activemq" />
 <property name="maxActive" value="200" />
 <property name="poolPreparedStatements" value="true" />
</bean>

四、配置安全性

ActiveMQ也可以对各个主题和队列设置用户名和密码,在conf/activemq.xml中的broker内添加:

<plugins>  
 <!-- Configure authentication; Username, passwords and groups -->
 <simpleAuthenticationPlugin>
  <users>
   <authenticationUser username="system" password="manager"
    groups="users,admins" />
   <authenticationUser username="user" password="password"
    groups="users" />
   <authenticationUser username="guest" password="password"
    groups="guests" />
   <authenticationUser username="testUser" password="123456"
    groups="testGroup" />
  </users>
 </simpleAuthenticationPlugin>  
 <!--  Lets configure a destination based authorization mechanism -->
 <authorizationPlugin>
  <map>
   <authorizationMap>
    <authorizationEntries>
     <authorizationEntry queue="FirstQueue" read="testGroup" write="testGroup" />
     <authorizationEntry queue=">" read="admins" write="admins" admin="admins" />
     <authorizationEntry queue="USERS.>" read="users" write="users" admin="users" />
     <authorizationEntry queue="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
     <authorizationEntry topic=">" read="admins" write="admins" admin="admins" />
     <authorizationEntry topic="USERS.>" read="users" write="users" admin="users" />
     <authorizationEntry topic="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
     <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users ,testGroup"
      write="guests,users,testGroup"
      admin="guests,users,testGroup" />
    </authorizationEntries>
   </authorizationMap>
  </map>
 </authorizationPlugin>
</plugins>

注:simpleAuthenticationPlugin中设置用户名、密码和群组,authorizationPlugin设置主题和队列的访问群 组,“>”表示所有的主题或者队列。上面的配置中添加了一个testUser,属于群组testGroup,同时设置FirstQueue这个队列的访问读写权 限为testGroup,当然admins也可以访问的,因为admins是对所有的队列都有访问权限。将简单例子代码中的connectionFactory设置用户名和密码为用户testUser,如果密码不正确,将会抛出User name or password is invalid.异常,如果testUser所属的群组不能访问FirstQueue队列,那么会抛出User guest is not authorized to write to: queue://FirstQueue异常。需要注意的是所有的群组都需要对以ActiveMQ.Advisory为前缀的主题具有访问权限

五、与spring3整合

1.加入jar包
commons-logging.jar
spring-aop-3.2.8.RELEASE.jar
spring-beans-3.2.8.RELEASE.jar
spring-context-3.2.8.RELEASE.jar
spring-core-3.2.8.RELEASE.jar
spring-expression-3.2.8.RELEASE.jar
spring-jms-3.2.8.RELEASE.jar
spring-tx-3.2.8.RELEASE.jar
2.src下applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="Index of /schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="Index of /schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL">
   <value>tcp://localhost:61616</value>
  </property>
 </bean>
 <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory">
   <ref bean="connectionFactory" />
  </property>
 </bean>
 <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
  <constructor-arg index="0">
   <value>FirstQueue</value>
  </constructor-arg>
 </bean>
</beans>
3.发送端:
public class Sender2 {
 public static void main(String[] args) throws Exception {
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  JmsTemplate jmsTemplate = (JmsTemplate) context.getBean("jmsTemplate");
  Destination destination = (Destination) context.getBean("destination");
  for (int i = 1; i < 10; i++) {
   MyMessageCreator mc = new MyMessageCreator();// 生成消息
   mc.n = i;
   jmsTemplate.send(destination, mc);
   Thread.sleep(2000);// 1秒后发送下一条消息
  }
 }
}
class MyMessageCreator implements MessageCreator {
 public int n = 0;
 public Message createMessage(Session paramSession) throws JMSException {
  return paramSession.createTextMessage("这个是第 " + n + " 个测试消息!");
 }
}
注:jmsTemplate.convertAndSend(destination, "这个是第 1 个测试消息"); 方法可能根据发送消息的类型自动产生对应类型的Message。
4.接收端:
 public static void main(String args[]) throws Exception {
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  JmsTemplate jmsTemplate = (JmsTemplate) context.getBean("jmsTemplate");
  Destination destination = (Destination) context.getBean("destination");
  boolean isContinue = true;// 是否继续接收消息
  while (isContinue) {
   TextMessage msg = (TextMessage) jmsTemplate.receive(destination);
   if (msg!=null) {
    System.out.println("收到消息 :" + msg.getText());
   }else{
    isContinue = false;
   }
  }
  System.out.println("程序退出了!");
 }
5.使用Spring的ConnectionFactory:有SingleConnectionFactory和CachingConnectionFactory。SingleConnectionFactory对于建立JMS服务器链接的请求会一直返回同一个链接,并且会忽略Connection的close方法调用。CachingConnectionFactory继承了SingleConnectionFactory,所以它拥有SingleConnectionFactory的所有功能,同时它还新增了缓存功能,它可以缓存Session、MessageProducer和MessageConsumer。这里我们使用SingleConnectionFactory来作为示例。Spring提供的ConnectionFactory只是Spring用于管理ConnectionFactory的,真正产生到JMS服务器链接的ConnectionFactory还得是由JMS服务厂商提供,并且需要把它注入到Spring提供的ConnectionFactory中。我们这里使用的是ActiveMQ实现的JMS,所以在我们这里真正的可以产生Connection的就应该是由ActiveMQ提供的ConnectionFactory。将org.apache.activemq.ActiveMQConnectionFactory的beanId修改为targetConnectionFactory
 <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
 <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
  <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
  <property name="targetConnectionFactory" ref="targetConnectionFactory" />
 </bean>
ActiveMQ为我们提供了一个PooledConnectionFactory,通过往里面注入一个ActiveMQConnectionFactory可以用来将Connection、Session和MessageProducer池化,这样可以大大的减少我们的资源消耗。
 <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
  <property name="connectionFactory" ref="targetConnectionFactory" />
  <property name="maxConnections" value="10" />
 </bean> 
 
 <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
 <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
  <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
  <property name="targetConnectionFactory" ref="pooledConnectionFactory" />
 </bean>
6.spring监听容器: 对于消息监听容器而言,除了要知道监听哪个目的地之外,还需要知道到哪里去监听,也就是说它还需要知道去监听哪个JMS服务器,这是通过在配置MessageConnectionFactory的时候往里面注入一个ConnectionFactory来实现的。所以我们在配置一个MessageListenerContainer的时候有三个属性必须指定,一个是表示从哪里监听的ConnectionFactory;一个是表示监听什么的Destination;一个是接收到消息以后进行消息处理的MessageListener。Spring一共为我们提供了两种类型的MessageListenerContainer,SimpleMessageListenerContainer和DefaultMessageListenerContainer。

SimpleMessageListenerContainer会在一开始的时候就创建一个会话session和消费者Consumer,并且会使用标准的JMS MessageConsumer.setMessageListener()方法注册监听器让JMS提供者调用监听器的回调函数。它不会动态的适应运行时需要和参与外部的事务管理。兼容性方面,它非常接近于独立的JMS规范,但一般不兼容Java EE的JMS限制。

大多数情况下我们还是使用的DefaultMessageListenerContainer,跟SimpleMessageListenerContainer相比,DefaultMessageListenerContainer会动态的适应运行时需要,并且能够参与外部的事务管理。它很好的平衡了对JMS提供者要求低、先进功能如事务参与和兼容Java EE环境。

<!-- 实现了javax.jms.MessageListener的消息监听器 --> 
 <bean id="consumerMessageListener" class="com.hanjun.ConsumerMessageListener"/>
 <!-- 消息监听容器 -->
 <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="destination" ref="destination" />
  <property name="messageListener" ref="consumerMessageListener" />
 </bean>
注:<property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE"></property> 接收端在处理成功并message.acknowledge()后MQ上的消息才会被删除
注:<property name="messageSelector" value="aa='bb'"></property> 可设置消息过滤,属性符合的消息才会被接收。
7.在Spring整合JMS的应用中我们在定义消息监听器的时候一共可以定义三种类型的消息监听器,分别是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。MessageListener是最原始的消息监听器,它是JMS规范中定义的一个接口。其中定义了一个用于处理接收到的消息的onMessage方法,该方法只接收一个Message参数。SessionAwareMessageListener是Spring为我们提供的,它不是标准的JMS MessageListener。MessageListener的设计只是纯粹用来接收消息的,假如我们在使用MessageListener处理接收到的消息时我们需要发送一个消息通知对方我们已经收到这个消息了,那么这个时候我们就需要在代码里面去重新获取一个Connection或Session。SessionAwareMessageListener的设计就是为了方便我们在接收到消息后发送一个回复的消息,它同样为我们提供了一个处理接收到的消息的onMessage方法,但是这个方法可以同时接收两个参数,一个是表示当前接收到的消息Message,另一个就是可以用来发送消息的Session对象。
MessageListenerAdapter类实现了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是将接收到的消息进行类型转换,然后通过反射的形式把它交给一个普通的Java类进行处理。
 <bean id="textMessageListener" class="com.hanjun.TextMessageListener"/>
 <!-- 消息监听适配器 -->
 <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
     <property name="delegate"  ref="textMessageListener"></property>
 </bean>
public class TextMessageListener{
 public void handleMessage(String message) {
  System.out.println("接收到一个纯文本消息。");
  System.out.println("消息内容是:" + message);
 }
}
注:MessageListenerAdapter的属性defaultListenerMethod可指定目标类要执行的方法名,不指定默认为handleMessage
注:如果我们指定的这个目标处理器是一个MessageListener或者是一个SessionAwareMessageListener的时候Spring将直接利用接收到的Message对象作为方法参数调用它们的onMessage方法。
MessageListenerAdapter除了会自动的把一个普通Java类当做MessageListener来处理接收到的消息之外, 其另外一个主要的功能是可以自动的发送返回消息。如上,当handleMessage的返回值不为空为void时,返回值将被自动封装成message发送到指定队列。该队列可以在发送端发送消息时通过message.setJMSReplyTo(destination)设置,也可通过接收端MessageListenerAdapter的属性defaultResponseDestination设置。
    <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate"  ref="textMessageListener"></property>
        <property name="defaultResponseDestination" ref="destination"/>
    </bean>
注:messageListenerAdapter中defaultResponseDestination的设置优化级低于发送端的message.setJMSReplyTo,同时设置时以setJMSReplyTo为准。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值