文章目录
1.JMS介绍
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持(百度百科给出的概述)。我们可以简单的理解:两个应用程序之间需要进行通信,我们使用一个JMS服务,进行中间的转发,通过JMS 的使用,我们可以解除两个程序之间的耦合。
JMS(JAVA Message Service,java消息服务)API是一个消息服务的标准或者说是规范
,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
Java消息队列–JMS概述
2.ActiveMQ介绍
JMS是一个用于提供消息服务的技术规范,它制定了在整个消息服务提供过程中的所有数据结构和交互流程。而MQ则是消息队列服务,是面向消息中间件(MOM)的最终实现,是真正的服务提供者;MQ的实现可以基于JMS,也可以基于其他规范或标准。
支持JMS的开源MQ:
目前选择的最多的是ActiveMQ(是对JMS的一种实现)。
MQ常用的有ActiveMQ,RabbitMQ,kafka。
消息中间件
active MQ介绍
2.1 名词解释
Destinationo
目的地,JMSProvider(消息中间件)负责维护,用于对Message进行管理的对象。
MessageProducer需要指定Destination才能发送消息,MessageConsumer要指定Destination才能接收消息;
Producere
消息生成者(客户端、生成消息),负责发送Message到目的地;应用接口为MessageProducere在JMS规范中,所有的标准定义都在javax.jms包中。
Consumer〖Receiver〗
消息消费者(处理消息),负责从目的地中消费〖处理|监听|订阅〗Messagee应用接口为MessageConsumer
Message
消息,消息封装一次通信的内容。常见类型有:
TextMessage、ObjectMessage、MapMessage、StreamMessage、BytesMessage
ConnectionFactory
链接工厂,用于创建链接的工厂类型。注意,不能和JDBC中的ConnecnonFactory混淆
Connection
链接.用于建立访问ActiveMQ连接的类型,由链接工厂创建.注意,不能和JDBC中的Connection混淆
Session
会话,一次持久有效状态的访问.由链接创建.是具体操作消息的基础支撑
Queue&Topico
Queue是队列目的地,Topic是主题目的地:都是Destination的子接口。
Queue特点:队列中的消息,默认只能由唯一的一个消费者处理。一旦处理消息就删除
Topic特点:主题中的消息,会发送给所有的消费者同时处理:只有在消息可以重复处
理的业务场景中可使用。
2.2 常用配置
2.2.1 端口查看
1.jetty.xml
<bean id="jettyPort" class="org.apache.activemq.web.WebConsolePort" init-method="start">
<!-- the default port number for the web console -->
<property name="host" value="0.0.0.0"/>
<property name="port" value="8161"/>
</bean>
2.2.2 账户查看
1.jetty-realm.properties
# username: password [,rolename ...]
#账户名:密码,角色
admin: admin, admin
user: user, user
2.2.3 安全认证配置
2.2.4 持久化策略
- ActiveMQ中,持久化是指对消息数据的持久化。在ActiveMQ中,默认的消息是保存在内存中的。当内存容量不足的时候,或ActiveMQ正常关闭的时候,会将内存中的未处理的消息持久化到磁盘中。具体的持久化策略由配置文件中的具体配置决定.
- ActiveMQ的默认存储策略是kahadb。如果使用JDBC作为持久化策略,则会将所有的需要持久化的消息保存到数据库中。
- 所有的持久化配置都在conf/activemq.xml中配置,配置信息都在broker标签内部定义。
Activemq.xml
在activemq.xml中配置如下:注意:若缺包,则把相关包上传到Lib目录去
3.下载
4.启动
前提:必须按照JDK并配置环境变量
在下面目录bin里找到activemq.bat脚本启动
从它的目录来说,还是很简单的:
- bin 存放的是脚本文件
- conf 存放的是基本配置文件
- data 存放的是日志文件
- docs 存放的是说明文档
- examples 存放的是简单的实例
- lib 存放的是activemq所需jar包
- webapps 用于存放项目的目录
5.登陆
启动后,访问:http://127.0.0.1:8161/admin/
默认用户与密码为:admin admin
登陆后,管理界面如图:
6.编程使用
前提:导入必要包
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.11.1</version>
</dependency>
</dependencies>
OR
6.1 PTP模型(基于Queue)
- 消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息
- 消息被消费以后,queue中不再有存储,所消息消费者不可能消费到已经被消费的消息
- Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费、其它
的则不能消费此消息了 - 当消费者不存在时,消息会一直保存,直到有消费消费
- 比如生产者生产了M1消息,而目前有两个消费则C1,C2,那么M1放到服务器队列Queue中后,C1消费了消息M1,那么C2就无法再获取消息了,因为队列Queue中已经没有消息M1了
生产者
public class JMSProducer {
private static final String USERNAME=ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
private static final int SENDNUM=10; // 发送的消息数量
public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session = null; // 会话 接受或者发送消息的线程
Destination destination = null ; // 消息的目的地
MessageProducer messageProducer = null; // 消息生产者
// 1.实例化连接工厂
//创建连接工厂,连接ActiveMQ服务的连接工厂。
//创建工厂,构造方法有三个参数,分别是用户名,密码,连接地址
//无参构造,有默认的链接地址。本地链接。localhost
//单参数构造,无验证模式的。没有用户的认证。
//三参数构造,有认证+指定地址。默认端口是61616。从ActiveMQ的conf/activemq.xml配置文件中查看。
//factory =new ActiveMQConnectionFactory("guest","guest","tcp://192.168.159.129:61616");
connectionFactory=new ActiveMQConnectionFactory(JMSProducer.USERNAME, JMSProducer.PASSWORD, JMSProducer.BROKEURL);
try {
// 2.通过连接工厂获取连接
//创建连接的方法有重载,其中有createConnection(String username,String password)
//可以在创建连接工厂时,只传递连接地址,不传递用户信息.
connection=connectionFactory.createConnection();
// 3.启动连接
//建议启动连接,消息的发送者不是必须启动连接。消息的消费者必须启动连接。
//producer在发送消息的时候,会检查是否启动了链接。如果未启动,自动启动。
//如果有特殊的配置,建议配置完毕后再启动链接。
connection.start();
// 4.创建Session
/*
创建会话的时候,必须传递两个参数,分别代表是否支持事务和如何确认消息处理。
transacted-是否支持事务,数据类型是boolean.true-支持,false-不支持
true-支持事务,第二个参数默认无效,建议传递的数据是Session.SESSION TRANSACTED
false-不支持事务,常用参数。第二个参数必须传递,且必须有效。
acknowledgeMode-如何确认消息的处理。使用确认机制实现的。
AUTO ACKNOWLEDGE-自动确认消息。消息的消费者处理消息后,自动确认。常用
CLIENT ACKNOWLEDGE-客户端手动确认,消息的消费者处理后,必须手工确认!
DUPS OK ACKNOWLEDGE-有副本的客户端手动确认。
一个消息可以多次处理。
可以降低Session的消耗,在可以容忍重复消息时使用。(不推荐使用)
*/
session=connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 5.创建消息队列
//创建目的地。参数是目的地名称。是目的地的唯一标记。会在MQ服务器创建一个名字为FirstQueue1目的地类
//用于接收该条目的地接收和发送消息
destination=session.createQueue("FirstQueue1");
// 6.创建消息生产者
//创建的消息发送者,发送的消息一定到指定的目的地中。
//创建producer的时候,可以不提供目的地。在发送消息的时候制定目的地。比如createProducer(nul)
messageProducer=session.createProducer(destination);
// 7.发送消息
sendMessage(session, messageProducer);
// 8.提交事务
//由于session是开启了事务,所以必须执行提交操作,不然缓存中的数据是不会上传到服务器中去的
session.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
// 9 关闭资源
if(messageProducer !=null){
try {
messageProducer.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(session!=null){
try {
session.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
/**
* 发送消息
* @param session
* @param messageProducer
* @throws Exception
*/
public static void sendMessage(Session session,MessageProducer messageProducer)throws Exception{
for(int i=0;i<JMSProducer.SENDNUM;i++){
TextMessage message=session.createTextMessage("ActiveMQ 发送的消息"+i);
System.out.println("发送消息:"+"ActiveMQ 发送的消息"+i);
messageProducer.send(message);
}
}
}
Number Of Consumers :表示消费者数量;
Number Of Pending Messages :等待消费的消息,这个是当前未出队列的数量;
Messages Enqueued :进入队列的消息;( 这个数量只增不减,重启acmq后会清零)
Messages Dequeued :出了队列的消息 可以理解为是消费这消费掉的数量 (重启acmq后会清零)
6.1.1 主动消费(receive调用一次,获取一次消息)
消费者(循环获取)
public class JMSConsumer {
private static final String USERNAME= ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageConsumer messageConsumer; // 消息的消费者
// 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSConsumer.USERNAME, JMSConsumer.PASSWORD, JMSConsumer.BROKEURL);
try {
connection=connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建Session
session=connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 创建连接的消息队列
destination=session.createQueue("MyQueueNO.1");
// 创建消息消费者
messageConsumer=session.createConsumer(destination);
// 每10秒查询一次
while(true){
//receive主动获取消息,每执行一次,就从消息中心消费一条消息
TextMessage textMessage=(TextMessage)messageConsumer.receive(10000);
if(textMessage!=null){
System.out.println("收到的消息:"+textMessage.getText());
}else{
break;
}
}
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
若队列里没有生产者产生的消息,那么启动消费者就会一直阻塞,知道队列里有消息了,消费者才算执行下去
6.1.2 观察者消费
消费者(监听获取)
public class JMSConsumer2 {
private static final String USERNAME= ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageConsumer messageConsumer; // 消息的消费者
// 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSConsumer2.USERNAME, JMSConsumer2.PASSWORD, JMSConsumer2.BROKEURL);
try {
connection=connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建Session
session=connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 创建连接的消息队列
destination=session.createQueue("MyQueueNO.1");
// 创建消息消费者
messageConsumer=session.createConsumer(destination);
// 注册一个监听器
// 当启动消费者的时候,就会监听MyQueueNO.1这个队列里的消息,有则获取过来
messageConsumer.setMessageListener(new Listener());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这里需要增加一个监听器,如下:
监听器
public class Listener implements MessageListener{
public void onMessage(Message message) {
try {
System.out.println("监听器收到消息"+((TextMessage)message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
6.2 PUB/SUB模型(基于Topic)
- 消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息
- 和点对点方式不同,发布到topic的消息会被所有订阅者消费
- 当生产者发布消息,不管是否有消费者。都不会保存消息
- 一定要先有消息的消费者,后有消息的生产者
主题生产者
public class JMSProducer {
private static final String USERNAME=ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
private static final int SENDNUM=5; // 发送的消息数量
public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageProducer messageProducer; // 消息生产者
// 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSProducer.USERNAME, JMSProducer.PASSWORD, JMSProducer.BROKEURL);
try {
connection=connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建Session
session=connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建消息主题名
destination=session.createTopic("MyTopicNO.1");
// 创建消息生产者
messageProducer=session.createProducer(destination);
// 发送消息
for(int i = 0; i< JMSProducer.SENDNUM; i++){
TextMessage message=session.createTextMessage("ActiveMQ 发送的消息"+i);
System.out.println("发送消息:"+"ActiveMQ 发送的消息"+i);
messageProducer.send(message);//通过生产者发送消息
}
// 提交事务
session.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
if(connection!=null){
try {
connection.close();
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
上图名词解释
主题消费者
public class JMSConsumer {
private static final String USERNAME= ActiveMQConnection.DEFAULT_USER; // 默认的连接用户名
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD; // 默认的连接密码
private static final String BROKEURL=ActiveMQConnection.DEFAULT_BROKER_URL; // 默认的连接地址
public static void main(String[] args) {
ConnectionFactory connectionFactory; // 连接工厂
Connection connection = null; // 连接
Session session; // 会话 接受或者发送消息的线程
Destination destination; // 消息的目的地
MessageConsumer messageConsumer; // 消息的消费者
// 实例化连接工厂
connectionFactory=new ActiveMQConnectionFactory(JMSConsumer.USERNAME, JMSConsumer.PASSWORD, JMSConsumer.BROKEURL);
try {
connection=connectionFactory.createConnection();
// 启动连接
connection.start();
// 创建Session
session=connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 创建主题名
destination=session.createTopic("MyTopicNO.1");
// 创建消息消费者
messageConsumer=session.createConsumer(destination);
// 注册消息监听
messageConsumer.setMessageListener(new Listener());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
监听器
public class Listener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
System.out.println("订阅者一收到的消息:"+((TextMessage)message).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
6.3 ptp和pub/sub 对比
6.4 在线发送
注意:
主题消费者一定要比主题生产者先启动起来,不然主题生产者前面发的消息,消费者是不会接受到的,只有消费者先启动监听,后续发过来的消息才能收到
队列不存在这种情况,队列生产者先产生消息,队列消费者后启动也能收到这个消息
与Spring集成
后续更新…
参考资料
一头扎进 JMS 之 ActiveMQ
浅析jms之ActiveMQ
ActiveMQ简介