最近开发任务完成的早,有空闲时间,学习一下之前一直说学但又没时间学的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();
}
}
}
以上完成订阅者的代码
*需要注意的是,话题必须先订阅才能看到的,所以我们需要先运行订阅者,才发布话题,才能接收到消息
运行结果:
第一个订阅者:
第二个订阅者:
到此已经完成对订阅者的编写
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