Spring整合JMS——基于ActiveMQ实现

来源:互联网 发布:windows电脑唯一标识 编辑:程序博客网 时间:2024/06/14 03:40

1.1 JMS简介

JMS的全称是Java Message Service,即Java消息服务。它主要用于在生产者和消费者之间进行消息传递,生产者负责产生消息,而消费者负责接收消息。把它应用到实际的业务需求中的话我们可以在特定的时候利用生产者生成一消息,并进行发送,对应的消费者在接收到对应的消息后去完成对应的业务逻辑。对于消息的传递有两种类型,一种是点对点的,即一个生产者和一个消费者一一对应;另一种是发布/订阅模式,即一个生产者产生消息并进行发送后,可以由多个消费者进行接收。

1.2 Spring整合JMS

对JMS做了一个简要介绍之后,接下来就讲一下Spring整合JMS的具体过程。JMS只是一个标准,真正在使用它的时候我们需要有它的具体实现,这里我们就使用Apache的activeMQ来作为它的实现。所使用的依赖利用Maven来进行管理,具体依赖如下:

    <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-jms</artifactId>            <version>4.3.3.RELEASE</version>        </dependency>        <dependency>            <groupId>org.apache.activemq</groupId>            <artifactId>activemq-core</artifactId>            <version>5.7.0</version>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-messaging</artifactId>            <version>4.3.3.RELEASE</version>        </dependency>

1.2.1 activeMQ准备

既然是使用的apache的activeMQ作为JMS的实现,那么首先我们应该到apache官网上下载activeMQ(http://activemq.apache.org/download.html),进行解压后运行其bin目录下面的activemq.bat文件启动activeMQ。

1.2.2配置ConnectionFactory

ConnectionFactory是用于产生到JMS服务器的链接的,Spring为我们提供了多个ConnectionFactory,有SingleConnectionFactory和CachingConnectionFactory。

SingleConnectionFactory对于建立JMS服务器链接的请求会一直返回同一个链接,并且会忽略Connection的close方法调用。
CachingConnectionFactory继承了SingleConnectionFactory,所以它拥有SingleConnectionFactory的所有功能,同时它还新增了缓存功能,它可以缓存Session、MessageProducer和MessageConsumer。

这里我们使用SingleConnectionFactory来作为示例。

// ActiveMQ Connection    @Bean    public SingleConnectionFactory mqconnectionFactory() {        // 配置ActivityMQ提供的Connection接口        ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory();        activeMQConnection.setBrokerURL("tcp://127.0.0.1:61616");        // 配置Spring管理JMS        SingleConnectionFactory singleConnectionFactory = new SingleConnectionFactory();        singleConnectionFactory.setTargetConnectionFactory(activeMQConnection);        return singleConnectionFactory;    }

备注:Spring提供的ConnectionFactory只是Spring用于管理ConnectionFactory的,真正产生到JMS服务器链接的ConnectionFactory还得是由JMS服务厂商提供,并且需要把它注入到Spring提供的ConnectionFactory中。我们这里使用的是ActiveMQ实现的JMS,所以在我们这里真正的可以产生Connection的就应该是由ActiveMQ提供的ConnectionFactory。

1.2.3配置生产者

配置好ConnectionFactory之后我们就需要配置生产者。生产者负责产生消息并发送到JMS服务器,这通常对应的是我们的一个业务逻辑服务实现类。但是我们的服务实现类是怎么进行消息的发送的呢?这通常是利用Spring为我们提供的JmsTemplate类来实现的,所以配置生产者其实最核心的就是配置进行消息发送的JmsTemplate。对于消息发送者而言,它在发送消息的时候要知道自己该往哪里发,为此,我们在定义JmsTemplate的时候需要往里面注入一个Spring提供的ConnectionFactory对象。

    @Bean    public JmsTemplate springJmsTemplate() {        JmsTemplate jmsTemplate = new JmsTemplate();        //注入上面的mqconnectionFactory方法返回值        jmsTemplate.setConnectionFactory(mqconnectionFactory());        return jmsTemplate;    }

在真正利用JmsTemplate进行消息发送的时候,我们需要知道消息发送的目的地,即destination。在Jms中有一个用来表示目的地的Destination接口,它里面没有任何方法定义,只是用来做一个标识而已。当我们在使用JmsTemplate进行消息发送时没有指定destination的时候将使用默认的Destination。默认Destination可以通过在定义jmsTemplate bean对象时通过属性defaultDestination或defaultDestinationName来进行注入,defaultDestinationName对应的就是一个普通字符串。在ActiveMQ中实现了两种类型的Destination,一个是点对点的ActiveMQQueue,另一个就是支持订阅/发布模式的ActiveMQTopic。在定义这两种类型的Destination时我们都可以通过一个name属性来进行构造,如:

    // ActiveMQ Queue  点对点    @Bean(name = "originalBaseStation")    public ActiveMQQueue sendBaseStationQueue() {        ActiveMQQueue queue = new ActiveMQQueue("originalBaseStation");        return queue;    }   //一对多    @Bean    public ActiveMQTopic topDestination(){        ActiveMQTopic topic = new ActiveMQTopic();        return topic;    }

假设我们定义了一个ProducerService,里面有一个向Destination发送纯文本消息的方法sendMessage,那么我们的代码就大概是这个样子:

import javax.annotation.Resource;   import javax.jms.Destination;   import javax.jms.JMSException;   import javax.jms.Message;   import javax.jms.Session;   import org.springframework.jms.core.JmsTemplate;   import org.springframework.jms.core.MessageCreator;   import org.springframework.stereotype.Component;   import com.tiantian.springintejms.service.ProducerService;   @Component  public class ProducerServiceImpl implements ProducerService {       @Autowired    private JmsTemplate jmsTemplate;       public void sendMessage(Destination destination, final String message) {           System.out.println("---------------生产者发送消息-----------------");           System.out.println("---------------生产者发了一个消息:" + message);           jmsTemplate.send(destination, new MessageCreator() {               public Message createMessage(Session session) throws JMSException {                   return session.createTextMessage(message);               }           });       }    }

我们可以看到在sendMessage方法体里面我们是通过jmsTemplate来发送消息到对应的Destination的。到此,我们生成一个简单的文本消息并把它发送到指定目的地Destination的生产者就配置好了。

1.2.4配置消费者

生产者往指定目的地Destination发送消息后,接下来就是消费者对指定目的地的消息进行消费了。那么消费者是如何知道有生产者发送消息到指定目的地Destination了呢?这是通过Spring为我们封装的消息监听容器MessageListenerContainer实现的,它负责接收信息,并把接收到的信息分发给真正的MessageListener进行处理。每个消费者对应每个目的地都需要有对应的MessageListenerContainer。对于消息监听容器而言,除了要知道监听哪个目的地之外,还需要知道到哪里去监听,也就是说它还需要知道去监听哪个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环境。

定义处理消息的MessageListener
要定义处理消息的MessageListener我们只需要实现JMS规范中的MessageListener接口就可以了。MessageListener接口中只有一个方法onMessage方法,当接收到消息的时候会自动调用该方法。

import javax.jms.JMSException;   import javax.jms.Message;   import javax.jms.MessageListener;   import javax.jms.TextMessage;   public class ConsumerMessageListener implements MessageListener {       public void onMessage(Message message) {           //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage          TextMessage textMsg = (TextMessage) message;           System.out.println("接收到一个纯文本消息。");           try {               System.out.println("消息内容是:" + textMsg.getText());           } catch (JMSException e) {               e.printStackTrace();           }       }   }  

消息监听容器

    // 消息简体容器    // 发送最简单的文本而且,使用DefaultMessageListenerContainer即可    @Bean(name = "originalBaseStationListener")    public DefaultMessageListenerContainer defaultBaseStationListenerContainer() {        DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();        //设置connectionFactory        defaultMessageListenerContainer.setConnectionFactory(mqconnectionFactory());        //设置Destination        defaultMessageListenerContainer.setDestination(sendBaseStationQueue());        //设置上面定义的监听类        defaultMessageListenerContainer.setMessageListener(new ConsumerMessageListener());        return defaultMessageListenerContainer;    }

如上就是一个整合的Demo

Spring整合JMS(二)——消息监听器

3.1 消息监听器MessageListener

在spring整合JMS的应用中我们在定义消息监听器的时候一共可以定义三种类型的消息监听器,分别是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。下面就分别来介绍一下这几种类型的区别。

3.1.1 MessageListener

MessageListener是最原始的消息监听器,它是JMS规范中定义的一个接口。其中定义了一个用于处理接收到的消息的onMessage方法,该方法只接收一个Message参数。我们前面在讲配置消费者的时候用的消息监听器就是MessageListener,代码如下:

import javax.jms.JMSException;   import javax.jms.Message;   import javax.jms.MessageListener;   import javax.jms.TextMessage;   public class ConsumerMessageListener implements MessageListener {       public void onMessage(Message message) {           //这里我们知道生产者发送的就是一个纯文本消息,所以这里可以直接进行强制转换,或者直接把onMessage方法的参数改成Message的子类TextMessage          TextMessage textMsg = (TextMessage) message;           System.out.println("接收到一个纯文本消息。");           try {               System.out.println("消息内容是:" + textMsg.getText());           } catch (JMSException e) {               e.printStackTrace();           }       }   }  

3.1.2 SessionAwareMessageListener

SessionAwareMessageListener是Spring为我们提供的,它不是标准的JMS MessageListener。MessageListener的设计只是纯粹用来接收消息的,假如我们在使用MessageListener处理接收到的消息时我们需要发送一个消息通知对方我们已经收到这个消息了,那么这个时候我们就需要在代码里面去重新获取一个Connection或Session。SessionAwareMessageListener的设计就是为了方便我们在接收到消息后发送一个回复的消息,它同样为我们提供了一个处理接收到的消息的onMessage方法,但是这个方法可以同时接收两个参数,一个是表示当前接收到的消息Message,另一个就是可以用来发送消息的Session对象。先来看一段代码:

import javax.jms.Destination;   import javax.jms.JMSException;   import javax.jms.Message;   import javax.jms.MessageProducer;   import javax.jms.Session;   import javax.jms.TextMessage;   import org.springframework.jms.listener.SessionAwareMessageListener;   public class ConsumerSessionAwareMessageListener implements          SessionAwareMessageListener<TextMessage> {       @Autowired    private Destination destination;       public void onMessage(TextMessage message, Session session) throws JMSException {           System.out.println("收到一条消息");           System.out.println("消息内容是:" + message.getText());           MessageProducer producer = session.createProducer(destination);           Message textMessage = session.createTextMessage("ConsumerSessionAwareMessageListener。。。");           producer.send(textMessage);       }   }  

在上面代码中我们定义了一个SessionAwareMessageListener,在这个Listener中我们在接收到了一个消息之后,利用对应的Session创建了一个到destination的生产者和对应的消息,然后利用创建好的生产者发送对应的消息。

3.1.3 MessageListenerAdapter
MessageListenerAdapter类实现了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是将接收到的消息进行类型转换,然后通过反射的形式把它交给一个普通的Java类进行处理。
MessageListenerAdapter会把接收到的消息做如下转换:
TextMessage转换为String对象;
BytesMessage转换为byte数组;
MapMessage转换为Map对象;
ObjectMessage转换为对应的Serializable对象。
既然前面说了MessageListenerAdapter会把接收到的消息做一个类型转换,然后利用反射把它交给真正的目标处理器——一个普通的Java类进行处理(如果真正的目标处理器是一个MessageListener或者是一个SessionAwareMessageListener,那么Spring将直接使用接收到的Message对象作为参数调用它们的onMessage方法,而不会再利用反射去进行调用),那么我们在定义一个MessageListenerAdapter的时候就需要为它指定这样一个目标类。这个目标类我们可以通过MessageListenerAdapter的构造方法参数指定,如:

    @Bean    public MessageListenerAdapter getListener(){         MessageListenerAdapter msmq = new MessageListenerAdapter();        msmq.setDelegate(Object);        //Object的xxxx方法        msmq.defaultListenerMethod=xxxx;        return msmq;    }

前面说了如果我们指定的这个目标处理器是一个MessageListener或者是一个SessionAwareMessageListener的时候Spring将直接利用接收到的Message对象作为方法参数调用它们的onMessage方法。但是如果指定的目标处理器是一个普通的Java类时Spring将利用Message进行了类型转换之后的对象作为参数通过反射去调用真正的目标处理器的处理方法,那么Spring是如何知道该调用哪个方法呢?这是通过MessageListenerAdapter的defaultListenerMethod属性来决定的,当我们没有指定该属性时,Spring会默认调用目标处理器的handleMessage方法。

  public class Say {   public void sayA(String str){       System.out.println("testA"+str);   }   public void handleMessage(String str){       System.out.println("test HandleMessage"+str);   }}
@Bean(name="test")    public ActiveMQQueue testQueue(){        ActiveMQQueue queue = new ActiveMQQueue("queue");        return queue;    }    @Bean(name="messageListener")    public MessageListenerAdapter getMessageListenerAdapter(){        MessageListenerAdapter ms = new MessageListenerAdapter();        ms.setDelegate(new Say());        //如果不设置则标示为handleMessage方法        ms.setDefaultListenerMethod("sayA");        return ms;    }    @Bean(name = "testListener")    public DefaultMessageListenerContainer test() {        DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();        defaultMessageListenerContainer.setConnectionFactory(mqconnectionFactory());        defaultMessageListenerContainer.setDestination(testQueue());        defaultMessageListenerContainer.setMessageListener(getMessageListenerAdapter());        return defaultMessageListenerContainer;    }

然后发送TextMessage消息给queue Destination观察。

MessageListenerAdapter除了会自动的把一个普通Java类当做MessageListener来处理接收到的消息之外,其另外一个主要的功能是可以自动的发送返回消息
当我们用于处理接收到的消息的方法的返回值不为空的时候,Spring会自动将它封装为一个JMS Message,然后自动进行回复。那么这个时候这个回复消息将发送到哪里呢?这主要有两种方式可以指定。
第一,可以通过发送的Message的setJMSReplyTo方法指定该消息对应的回复消息的目的地。这里我们把我们的生产者发送消息的代码做一下修改,在发送消息之前先指定该消息对应的回复目的地为一个叫responseQueue的队列目的地,具体代码如下所示:

@Component  public class ProducerServiceImpl implements ProducerService {        @Autowired      private JmsTemplate jmsTemplate;       @Autowired      @Qualifier("responseQueue")       private Destination responseDestination;       public void sendMessage(Destination destination, final String message) {           System.out.println("---------------生产者发送消息-----------------");           System.out.println("---------------生产者发了一个消息:" + message);           jmsTemplate.send(destination, new MessageCreator() {               public Message createMessage(Session session) throws JMSException {                   TextMessage textMessage = session.createTextMessage(message);                   textMessage.setJMSReplyTo(responseDestination);                   return textMessage;               }           });       }   }  

用该发送端发送上述的监听器。同时定义一个responseDestination的监听器。

MessageListenerAdapter会自动把真正的消息处理器返回的非空内容封装成一个Message发送回复消息到通过responseDestination属性指定的默认消息回复目的地。
既然我们可以通过两种方式来指定MessageListenerAdapter自动发送回复消息的目的地,那么当我们两种方式都指定了而且它们的目的地还不一样的时候会怎么发送呢?是两个都发还是只发其中的一个呢?关于这部分的测试我这里就不赘述了,有兴趣的网友可以自己进行。这里我可以直接的告诉大家,当两种方式都指定了消息的回复目的地的时候使用发送消息的setJMSReplyTo方法指定的目的地将具有较高的优先级,MessageListenerAdapter将只往该方法指定的消息回复目的地发送回复消息。

0 0