JMS消息服务入门讲解,基于ActiveMQ的实现【基础篇】

最近开发任务完成的早,有空闲时间,学习一下之前一直说学但又没时间学的JMS,现在有时间赶紧开始学,不然懒癌又发作,又玩游戏看电影去了,好了,也不多说,让我们充满好奇心的开始吧。

一.JMS的基本概念

JMS全称为Java Message Service(java消息服务)。它只是定义了一套实现规范,主要包括2种消息模型,点对点模型和发布/订阅模型,2种模型各有各的特点和用途,下面会进行介绍。如今已经有多个厂商对其提供了实现,ActiveMQ就是其中一种实现。ActiveMQ是一个消息中间件。

大致的作用:A应用以一定的规则发消息给JMS,它先帮我们保存在服务里,B应用再以一定的规则去JMS里获取信息,这样不管B应用是否在线,信息都可以先保存再JMS里,B应用想什么时候去取都可以,已达到解耦、异步等作用。

其他详细的解释和介绍,我就不在这里说了,想了解就去百度百科吧。

二.2种消息模型

  • Point-to-Point(P2P):点对点
  • Publish/Subscribe(Pub/Sub):发布/订阅

1.Point-to-Point(P2P)模型

简单来说,主要由3部分组成:客户端A(生产者)+消息队列(JMS中间件的消息队列)+客户端B(消费者)。

大致运行流程:A发送消息到JMS中间件消息队列,JMS会先把消息保存到特殊的nosql中(具体的nosql类型数据库要看JMS实现商),等到B接收到信息并返回接收通知之后或者消息过期,消息才从JMS中移除。

这里给一张流程图



有几个比较重要的特点:

1.每条消息只能被消费一次,过期和被消费的消息会从队列中移除

2.队列的消息不存在时间依赖,不管消费者客户端是否运行,消息都会保存直至被消费

3.当消费者接收到消息后,需发送确定收到通知,消息才会被消费


2.Publish/Subscribe(Pub/Sub)

简单来说,也是主要由3部分组成,发布者、话题(topic)、订阅者。与P2P不同的是,发布者和订阅者都可以是多个。

大致运行流程:发布者发布一个话题,只要是在发布者发布之前进行订阅的客户端都能收到该话题。

这里给一张流程图


有几个比较重要的特点:

1.一个话题可以被多个订阅者消费

2.话题存在时间上的依赖,当发布者发布一个话题时,订阅者客户端必须处在运行状态,才能接收到该话题

3.为了解决时间上的依赖,订阅者可以设置成成持久化订阅的状态,这样的话,不管订阅者客户端是否在运行,都可以接收到该话题

三.在实践中学习和理解

理论我就不多说了,想要了解更多的理论知识,建议还是到百度百科呗。实践出真理,下面我们就来实验一下,当然在实践过程中,我还是会解释相关的一些知识,以便加强自己的理解。

1.搭建环境

基础的开发环境我就不在这里细说了(java开发环境、集成maven)。既然我们要用到JMS的实现提供商ActiveMQ,那我们就去官网下载呗。ActiveMQ下载地址


下个目前的最新版的5.15.3,下载完之后解压并打开,是不是跟Tomcat有点像,因为都是apache的嘛


这个就我们的消息中间件,现在我们先来运行起来,打开bin/win64/activemq.bat来运行,win64/32根据自己的情况来选择


双击打开activemq.bat


出现以上命令,说明运行成功,ActiveMQ有自带一个web工程,是给我们进行监测这个中间件的情况的(队列的数量、当前消息数、被消费消息数等等)

我们先来运行一下这个web工程瞧瞧看,去浏览器里输入:http://127.0.0.1:8161/admin/    ,中途需要输入账号密码,默认都是admin/admin


具体的先不介绍,后面会进行讲解。进行到这里,我们的消息服务已经搭建完成,现在我们需要集成到我们自己的项目工程里。

2.编码实现(简单的helloWorld)

进行到这里是不是已经有点小兴奋了,因为我们又可以开始写代码了。我这里直接用集成maven的工程里做测试。

首先,我们得有一些可以操作这个ActiveMQ消息中间件的API对吧,在pom.xml加上:

<!--ActiveMQ所需要的jar包 -->  
        <!-- 添加ActiveMQ的pool包 -->  
        <dependency>  
            <groupId>org.apache.activemq</groupId>  
            <artifactId>activemq-pool</artifactId>  
            <version>5.15.3</version>  
        </dependency>  
        <dependency>  
            <groupId>org.apache.activemq</groupId>  
            <artifactId>activemq-all</artifactId>  
            <version>5.15.3</version>  
        </dependency>  

这样我们就可以开始编码了。上一个工程的关键目录图吧(当然,这是我已经写好测试过的)


根据前面第二点的介绍,JMS有2种模式。

我们首先来写第一种P2P模式:

理一下思路,在这个P2P中,主要流程是:

①消息生产者给消息中间件(队列)发送消息

②消息消费者接收消息中间件(队列)的消息

1.实现第一个:消息生产者给消息中间件(队列)发送消息

package com.cheng.sbjm.jms.ptp;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * 
 * @author Ouyzc
 *jms就是一个服务器,发送者---jms----接收者
 */
public class Sender {

	/**
	 * 定义一个JMS发送者客户端(Queue方式)
	 * @param args
	 */
	public static void main(String[] args) {
		
			//定义连接工厂ConnectionFactoty获取连接对象
			ActiveMQConnectionFactory  connectionFactory;
			
			//定义一个私有连接
			Connection connection=null;
			
			//Session:定义 一个发送或接收消息的线程 (上下文管理容器)
			Session session=null;
			
			//Destination :定义消息目标
			//(无论生产者还是消费者,都需要发送或者接收的目标,实例化时会定义一个队列的名称,也就是目标的名称)
			//生产者往该目标里发送值,消费者从该目标里取值
			//Destination destinaction;
			//队列(目的地、生产者发送消息的目的地)  
			Queue queue=null;
			
			//messageProducer:定义消息发送者【生产者】
			MessageProducer messageProducer=null;
					
			//定义好以上需要使用的变量之后,可以实例化
			
			//1.实例化连接工程,这里使用jms的其中一个实现ActionMQ 
			connectionFactory=new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,ActiveMQConnection.DEFAULT_PASSWORD,"tcp://localhost:61616" );
			
			try {
				
			//2.获取连接对象
			connection=connectionFactory.createConnection();
			
			//3.启动连接,默认是关闭的
			connection.start();
			
			//4.创建一个可操作的连接对象(参数1-是否开启事物,参数2-是签收的模式)
			session=connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
			
			//5.获取消息目标,就是消息发送和接受的地点("test_queue1"是定义的一个队列名字)
			queue=session.createQueue("test_queue1");
		
			//6.消息主体,这里比喻成生产者。session进行创建
			messageProducer = session.createProducer(queue); 
			
			//消息过期设置  
			//messageProducer.setTimeToLive(1000);  
			
			//7.设置是否持久化方式
			messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);  
			
			//8.设置发送消息,定义JMS规范的消息类型,这里定义简单的TextMessage,session进行创建,并发送消息
			sendMessage(session,messageProducer);
                        session.commit();  
                        System.out.println("测试消息发送结束!");  				
			} catch (Exception e) {
				// TODO: handle exception
			}finally {  
	            try {  
	                if (null != connection)  
	                    connection.close();  
	                if(null !=session)
	                    session.close();
	            } catch (Throwable ignore) {  
	            } 
			}
			
		}
	
	public static void sendMessage(Session session, MessageProducer messageproducer)  
            throws Exception { 
		
		//连续发5条
        for (int i = 1; i <= 5; i++) {  
            TextMessage message = session.createTextMessage("ccww."
            		+ "ActiveMq 发送的消息"  
                    + i);  
            // 发送消息到目的地方  
  
            System.out.println("发送消息:" + "ActiveMq 发送的消息" + i);  
            messageproducer.send(message);  
        }  
        
    }  
}

在这个消息生产者中,涉及到概念还是比较多的,我在代码里尽量有解释了,这里我也还是解释一下大概的流程:

  • 创建ActiveMQ专门的连接工厂ActiveMQConnectionFactory
  • 获取专有的连接Connection,并开启连接
  • 从连接中创建一个上下文管理器Session
  • Session创建创建队列Queue(定义队列名称,消息消费者根据该名称取队列)
  • Session针对这个队列Queue创建消息生产者MessageProducer
  • Session创建消息发送体TextMessage
  • 生产者MessageProducer发送消息到队列

消息生产者到此已经完成了,我们来运行一下(发现消息发送成功了):


这时,我们前面不是有提到过ActiveMQ有提供一个web工程给我们来监测吗?来我们打开一下


往下拉


发现确实有存在5条消息,并且未被消费,点击右边橙色的res小按钮,可以看到我们的消息内容


以上,就完成了最简单的消费生产者


2.实现第二个:消息消费者接收消息中间件(队列)的消息

package com.cheng.sbjm.jms.ptp;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class Receiver {

	/**
	 * 定义一个JMS消费者客户端
	 * @param args
	 */
	public static void main(String[] args) {
			
			//定义连接工厂ConnectionFactoty获取连接对象
			ActiveMQConnectionFactory connectionFactory=null;
			
			//定义一个私有连接
			Connection connection=null;
			
			//Session:定义 一个发送或接收消息的线程 (上下文管理容器)
			Session session=null;
			
			//Queue :定义队列消息目标;
			Queue queue=null;
			
			//messageProducer:定义消息接收者【消费者】
			MessageConsumer messageConsumer=null;  
		
			
			//定义好以上需要使用的变量之后,可以实例化
			
			//1.实例化连接工程,这里使用jms的其中一个实现ActionMQ 
			connectionFactory=new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,ActiveMQConnection.DEFAULT_PASSWORD,"tcp://localhost:61616" );
			
			try {
				
			//2.获取连接对象
			connection=connectionFactory.createConnection();
			
			//3.启动连接,默认是关闭的
			connection.start();
			
			//4.创建一个可操作的连接对象(参数1-是否开启事物,参数2-是签收的模式)
			/**
			 * 参数1-是否开启事物:
			 * 开启事务时,参数2就没有意义了,因为当事务被提交时,会自动提交确认收到反馈消息(异步接收不存在事务)。
			 * 不开启事务时,参数2就有意义了(参数2就是决定你采用什么方式确定签收)
			 * 1.Session.AUTO_ACKNOWLEDGE(当客户端成功的从receive方法或从onMessage(Message message) 方法返回的时候,会话自动确认client收到消息)
			 * 2.Session.CLIENT_ACKNOWLEDGE(客户单通过调用acknowledge方法来确认客户端收到消息)
			 * 3.Session.DUPS_ACKNOWLEDGE(不是必须签收,消息可能会重复发送)
			 */
			session=connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			
			//5.获取消息目标,就是消息发送和接受的地点,要么queue,要么topic。session进行创建
			queue=session.createQueue("test_queue1");
			
			//6.消息主体,这里比喻成消费者,session进行创建
			messageConsumer = session.createConsumer(queue);
			
			//7.同步接收消息,直到接收到消息为止,都会阻塞程序
			//TextMessage message = (TextMessage) messageConsumer.receive(100000);  
			
			//7.异步接收消息,不会阻塞程序(创建一个监听器)
			messageConsumer.setMessageListener(new MessageListener() {  
                @Override  
                public void onMessage(Message message) {  
                    TextMessage textMessage = (TextMessage) message;  
                    try {  
                        String value = textMessage.getText();  
                        System.out.println("value: " + value);  
                        //反馈确认收到,消息被消费
                        textMessage.acknowledge();
                    } catch (JMSException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            });  
			
			
			} catch (Exception e) {
				// TODO: handle exception
			}
			
		}
}

在这个消息消费者中,涉及到概念也是比较多的,我在代码里尽量有解释了,大多数跟消息生产者类似,这里我也还是解释一下大概的流程:

  • 创建ActiveMQ专门的连接工厂ActiveMQConnectionFactory
  • 获取专有的连接Connection,并开启连接
  • 从连接中创建一个上下文管理器Session
  • Session创建创建队列Queue(定义队列名称与生产者发送的队列名称一致)
  • Session针对这个队列Queue创建消息消费者MessageConsumer
  • 消息消费者通过同步/异步接收队列信息

解释一下同步接收与异步接收的区别

同步接收:

//7.同步接收消息,直到接收到消息或者超时为止,都会阻塞程序
TextMessage message = (TextMessage) messageConsumer.receive(5000);  

receive()为同步接收的方法,参数为超时时间,以毫秒为单位,不传的话,会一直阻塞,直到接收的消息,同步一般不常用,除非有特殊的业务要求。

异步接收:

//7.异步接收消息,不会阻塞程序(创建一个监听器)
	messageConsumer.setMessageListener(new MessageListener() {  
                @Override  
                public void onMessage(Message message) {  
                    TextMessage textMessage = (TextMessage) message;  
                    try {  
                        String value = textMessage.getText();  
                        System.out.println("value: " + value);  
                        //textMessage.acknowledge();
                    } catch (JMSException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            });  

异步接收需要先创建一个消息监听器,并重写onMessage方法,异步接收不会阻塞,常用。

消息消费者已经完成,我们来运行一下:


接收到刚才消息生产者发送的消息,再来看一下ActiveMQ的监测:


消息队列里有5条,被消费的有5条!

以上就完成最简单的消息消费者


接下来编写第二种Pub/Sub模式

理一下思路,在这个Pub/Sub模式中,主要流程是:

①消息生产者(发布者)给消息中间件发送话题(topic)

②消息消费者(订阅者)接收消息中间件的发送话题

消息生产者(发布者)代码与第一种P2P模式的消息生产者代码几乎一样,只是在创建生产者时,创建的是话题并不是队列

package com.cheng.sbjm.jms.topic;

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

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class Publisher {
	
	public static void main(String[] args) {
		
	       System.out.println("TopicSender启动了...");  
	        // ConnectionFactory :连接工厂,JMS 用它创建连接  
	        ConnectionFactory connectionFactory;  
	        // Connection :JMS 客户端到JMS Provider 的连接  
	        Connection connection = null;  
	        // Session: 一个发送或接收消息的线程  
	        Session session;  
	        // Destination :消息的目的地;消息发送给谁
	    	//Destination destinaction;
	        //topic 话题  
	        Topic topic;  
	        // MessageProducer:消息发送者  
	        MessageProducer producer;  
	        // TextMessage message;  
	        // 构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar  
	        connectionFactory = new ActiveMQConnectionFactory(  
	                ActiveMQConnection.DEFAULT_USER,  
	                ActiveMQConnection.DEFAULT_PASSWORD, "tcp://127.0.0.1:61616");  
	        try {  
	            // 构造从工厂得到连接对象  
	            connection = connectionFactory.createConnection();  
	            // 启动  
	            connection.start();  
	            // 获取操作连接  
	            session = connection.createSession(Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);  
	            // 获取session注意参数值mytopic是一个服务器的topic,须在在ActiveMq的console配置  
	            topic = session.createTopic("mytopic");  
	            // 得到消息生成者【发送者】  
	            producer = session.createProducer(topic);  
	            // 设置不持久化
	            producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);  
	            // 消息体
	            sendMessage(session, producer);  
	            session.commit();  
	        } catch (Exception e) {  
	            e.printStackTrace();  
	        } finally {  
	            try {  
	                if (null != connection)  
	                    connection.close();  
	            } catch (Throwable ignore) {  
	            }  
	        }  
	    }  
	  
	    public static void sendMessage(Session session, MessageProducer producer)  
	            throws Exception {  
	    	
	        for (int i = 1; i <= 5; i++) {  
	            TextMessage message = session.createTextMessage("我给你发话题");  
	            System.out.println("Sender发送消息:" + "topic:" + i);  
	            producer.send(message);  
	        }
	        
	    	
	}

}

流程跟P2P的消息生产者类似,这里就不多描述了,上一些运行结果图


ActiveMQ监听


选择“topic”查看话题检监测


有2个订阅者,有5条消息未被消费,被消费10条(这是我之前测试数据)

到此已经完成发布者的代码


接下来是消息消费者(订阅者)代码,也是跟P2P的消息消费者代码类似(这里为了测试,我2个订阅者,同时接收话题,这里只给一个订阅者的代码。另一个是一样的)

package com.cheng.sbjm.jms.topic;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

public class Subscriber1 {
	
	public static void main(String[] args) {
		
		 // ConnectionFactory :连接工厂,JMS 用它创建连接  
        ConnectionFactory connectionFactory;  
        // Connection :JMS 客户端到JMS Provider 的连接  
        Connection connection = null;  
        // Session: 一个发送或接收消息的线程  
        Session session;  
        // Destination :消息的目的地;消息发送给谁.  
        Destination destination;  
        // 消费者,消息接收者  
        MessageConsumer consumer;  
        connectionFactory = new ActiveMQConnectionFactory(  
                ActiveMQConnection.DEFAULT_USER,  
                ActiveMQConnection.DEFAULT_PASSWORD, "tcp://localhost:61616");  
        try {  
            // 构造从工厂得到连接对象  
            connection = connectionFactory.createConnection();  
            //当生产者采用持久化策略时,ClientID用以别区哪个客户端已经接收过
            //connection.setClientID("hh");
            // 启动  
            connection.start();  
            // 获取操作连接  
            session = connection.createSession(Boolean.FALSE,Session.AUTO_ACKNOWLEDGE);  
            // 获取session注意参数值mytopic是一个服务器的topic,须在在ActiveMq的console配置  
            destination = session.createTopic("mytopic");  
            consumer = session.createConsumer(destination);  
            System.out.println("我是订阅者1");
            consumer.setMessageListener(new MessageListener() {  
                @Override  
                public void onMessage(Message message) {  
                    if(message instanceof TextMessage){  
                        try {  
                            TextMessage text=(TextMessage)message;  
                            System.out.println("subscriber1收到消息" + text.getText());  
                        } catch (JMSException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                }  
            });  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
	}
}

以上完成订阅者的代码

*需要注意的是,话题必须先订阅才能看到的,所以我们需要先运行订阅者,才发布话题,才能接收到消息

运行结果:

第一个订阅者:


第二个订阅者:


到此已经完成对订阅者的编写




完成了JMS基本的2种模式的解释和演示后,还有几个概率是必须要知道的

1.针对消息生产者的:持久化与非持久化

当在编写消息生产者的时候,我有给消费者设置一个值

//7.设置是否持久化方式
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);  

DeliveryMode.PERSISTENT为持久化

当设置该消息为持久化的时,JMS会把消息保存到磁盘,不会因为ActiveMQ停止而消息丢失

DeliveryMode.NON_PERSISTENT为非持久化

当设置该消息为非持久化的时,消息会因为ActiveMQ停止而消息丢失

是否设置持久化看需求而定


2.是否开启事务(对消息生产者和消息消费者都会有影响)

当在消息生产者设置为开启事务时:

//4.创建一个可操作的连接对象(参数1-是否开启事物,参数2-是签收的模式)
session=connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

需要手动提交事务(只有提交了事务,消息才会发送)

//9.提交事务
session.commit();  

否则,则自动提交事务


当在消息消费者设置为开启事务时

session=connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

参数1-是否开启事物:

开启事务时,参数2就没有意义了,因为当事务被提交时,会自动提交确认收到反馈消息(异步接收不存在事务)。

不开启事务时,参数2就有意义了(参数2就是决定你采用什么方式确定签收)

参数二可选的值:
1.Session.AUTO_ACKNOWLEDGE(当客户端成功的从receive方法或从onMessage(Message message) 方法返回的时候,会话自动确认client收到消息)

2.Session.CLIENT_ACKNOWLEDGE(客户单通过调用acknowledge方法来确认客户端收到消息)

//7.异步接收消息,不会阻塞程序(创建一个监听器)
		messageConsumer.setMessageListener(new MessageListener() {  
                @Override  
                public void onMessage(Message message) {  
                    TextMessage textMessage = (TextMessage) message;  
                    try {  
                        String value = textMessage.getText();  
                        System.out.println("value: " + value);  
                        //反馈确认收到,消息被消费
                        textMessage.acknowledge();
                    } catch (JMSException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            });  

3.Session.DUPS_ACKNOWLEDGE(不是必须签收,消息可能会重复发送)

以上完成了对JMS的基础介绍与演示

 参考博客:https://blog.csdn.net/qh_java/article/category/6727730


猜你喜欢

转载自blog.csdn.net/ouyzc/article/details/79643387