- 与其他框架一样,ActiveMQ也有一套Java的操作api。
1. JMS编码总体规范
- JMS,即Java消息服务,与MySQL一样,有着一套编码的规范。
- 消息(也就是图中的Msg)由生产者(也就是图中的Message Producer)发出,发送到队列(Queue)或者主题(Topic)中(也就是图中的Destination);消费者(也就是图中的Message Consumer)通过Destination接收发过来的Msg。
- 而无论是生产者还是消费者,都要通过ConnectionFactory获取Connection,再创建一个会话Session,在会话中实现自己想实现的功能。
1.1 JMS开发步骤
- 创建一个Connection Factory
- 通过Connection Factory创建JMS Connection
- 启动 JMS Connection
- 通过Connection 创建 Session
- 创建JMS destination
- 创建JSM producer 或者 创建JMS message并设置destination
- 创建JMS consumer 或者是注册一个JMS message listener
- 发送并接收JSM message
- 关闭所有JMS资源(connection、session、producer、consumer等)
1.2 Destination 简介
- 也就是目的地。就是存储消息的地方。而Destination分为两种,队列(queue)和主题(topic)。
- 也就是一对一和一对多的关系。
- 队列是发送给一个消费者的。只要有一个消费者接收到了消息,消息就会在队列中删除。
- 主题可以发送给多个订阅者。要订阅者提前预定自己想要的主题,当发布者发布消息的时候,所有订阅者都可以接收到消息。
2. 队列入门案例
- 导包
<dependencies> <!-- activemq 所需要的jar 包--> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-all</artifactId> <version>5.15.9</version> </dependency> <!-- activemq 和 spring 整合的基础包 --> <dependency> <groupId>org.apache.xbean</groupId> <artifactId>xbean-spring</artifactId> <version>3.16</version> </dependency> <!-- 测试包 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies>
2.1 实现生产者
- 创建
ActiveMQConnectionFactory
,通过工厂生产一个连接Connection
,通过连接获取一个会话Session
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); Connection connection = activeMQConnectionFactory.createConnection(); connection.start(); // 开启连接 Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);// 不使用事务,使用自动签收
ACTIVEMQ_URL 就是MQ服务器的那个IP加端口号,如
tcp://192.168.233.133:61616
- 通过
Session
将生产者、目的地(队列)、消息对象都生产出来Queue queue = session.createQueue(DESTINATION_NAME); // 创建一个消息队列出来,也就是目的地 MessageProducer producer = session.createProducer(queue); // 把目的地放进生产者里面 TextMessage message = session.createTextMessage("message:" + i); // 创建一个要发送的消息对象,该对象存储的是字符串 producer.send(message); // 生产者负责将消息对象发送出去
其中
DESTINATION_NAME
就是你目的地的名称,自己取就好。 - 至此,就完成了发送一条消息。如果不需要了,就可以关闭所有资源了。
producer.close(); session.close(); connection.close();
- 完整代码。示例通过junit完成,方便测试。
public class QueueProducer { private static final String ACTIVEMQ_URL = "tcp://192.168.233.133:61616"; // 服务器端口号,地址 private static final String DESTINATION_NAME = "queue01"; // 目的地名称 private ActiveMQConnectionFactory activeMQConnectionFactory; private Connection connection = null; private Session session = null; private MessageProducer producer = null; @Before public void before() throws JMSException { activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); connection = activeMQConnectionFactory.createConnection(); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);// 不使用事务,使用自动签收 } @Test public void test1() throws JMSException { // 通过session把角色都创建出来 Queue queue = session.createQueue(DESTINATION_NAME); // 创建一个消息队列出来,也就是目的地 producer = session.createProducer(queue); // 把目的地放进生产者里面 // 发送消息 for (int i = 0; i < 10; i++) { TextMessage message = session.createTextMessage("message:" + i);// 创建一个要发送的消息对象,该对象存储的是字符串 producer.send(message);// 生产者负责将消息对象发送出去 } System.out.println("发送消息完毕"); } @After public void after() throws JMSException { // 关闭资源 producer.close(); session.close(); connection.close(); System.out.println("资源关闭完毕"); } }
- 加入消息之后,可以在控制台页面查看状态。
- Number Of Pending Messages:等待消费的消息,这个是未出队列的数量,公式=总接收数-总出队列数。
- Number Of Consumers:消费者数量,消费者端的消费者数量。
- Messages Enqueued:进队消息数,进队列的总消息量,包括出队列的。这个数只增不减。
- Messages Dequeued:出队消息数,可以理解为是消费者消费掉的数量
2.2 实现消费者
- 消费者负责接收消息,这就牵扯到一个问题了,到底是消费者以同步的方式停下来等消息;还是以异步的方式,有消息过来了,监视器监视到了就执行指定的方法。
- 而ActiveMQ两种实现方式都有。
2.2.1 同步接收消息
- 创建
ActiveMQConnectionFactory
,通过工厂生产一个连接Connection
,通过连接获取一个会话Session
,操作与生产者一模一样,在此不再重复 - 通过
Session
将消费者、目的地(队列)都生产出来Queue queue = session.createQueue(DESTINATION_NAME); // 目的地 MessageConsumer consumer = session.createConsumer(queue); // 消费者
- 消费者等待。可以规定超时时间,如果没有,那就不超时。接收到消息之后,可以取出消息。
TextMessage message = (TextMessage) consumer.receive(); // 没有超时时间 TextMessage message = (TextMessage) consumer.receive(3000L); // 定制超时时间3s String text = message.getText();
- 可以使用一个死循环,里面一直监听,如果超过规定时间都没有消息,就退出循环
while (true) { // 接受一个消息,并将消息转换为Text类型的消息 TextMessage message = (TextMessage) consumer.receive(3000L); // 定制超时时间3s //TextMessage message = (TextMessage) consumer.receive(); // 没有超时时间 if (null != message) System.out.println(message.getText()); else break; }
2.2.2 异步接收消息
- 也可以在不使用
reveive()
方法,给Consumer内部设置一个监听器,触发之后就调用。consumer.setMessageListener((message) -> { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; try { System.out.println("接收到一个消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } });
- 监听器是一个很简单的接口,只有一个方法,因此可以使用lambda表达式
public interface MessageListener { void onMessage(Message var1); }
- 值得注意的是,由于现在是测试,如果让程序设置完监听器之后就执行完的话,是不能产生监听效果的。因此,要在结束之前增添一个阻塞进程结束的操作。
System.in.read();
- 完整代码
public class QueueConsumer { private static final String ACTIVEMQ_URL = "tcp://192.168.233.133:61616"; // 服务器端口号,地址 private static final String DESTINATION_NAME = "queue01"; // 目的地名称 private ActiveMQConnectionFactory activeMQConnectionFactory; private Connection connection = null; private Session session = null; private MessageConsumer consumer = null; @Before public void before() throws JMSException { activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL); connection = activeMQConnectionFactory.createConnection(); connection.start(); // 开启连接 session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);// 不使用事务,使用自动签收 } /** * 同步方法接收 * @throws JMSException */ @Test public void test1() throws JMSException { Queue queue = session.createQueue(DESTINATION_NAME); // 目的地 consumer = session.createConsumer(queue); // 消费者 // 监听,3s超时 while (true) { // 接受一个消息,并将消息转换为Text类型的消息 TextMessage message = (TextMessage) consumer.receive(3000L); // 定制超时时间3s //TextMessage message = (TextMessage) consumer.receive(); // 没有超时时间 if (null != message) System.out.println(message.getText()); else break; } } /** * 异步方法接收 * @throws JMSException * @throws IOException */ @Test public void test2() throws JMSException, IOException { consumer = session.createConsumer(session.createQueue(DESTINATION_NAME)); consumer.setMessageListener((message) -> { if (message instanceof TextMessage) { TextMessage textMessage = (TextMessage) message; try { System.out.println("接收到一个消息:" + textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }); System.in.read(); } @After public void after() throws JMSException { consumer.close(); session.close(); connection.close(); System.out.println("关闭"); } }
2.3 队列消息总结
2.3.1 两种消费方式
2.3.1.1 同步阻塞方式(receive)
- 订阅者或接收者抵用
MessageConsumer
的receive()
方法来接收消息,receive
方法在能接收到消息之前(或超时之前)将一直阻塞。
2.3.1.2 异步非阻塞方式(监听器onMessage())
- 订阅者或接收者通过
MessageConsumer
的setMessageListener(MessageListener listener)
注册一个消息监听器,当消息到达之后,系统会自动调用监听器MessageListener
的onMessage(Message message)
方法。
2.3.2 队列的特点
- 这是个点对点的消息传递方式
- 一个消息只能有一个消费者,被消费了就没了,不会再存储了
- 消息的生产者和消费者之间没有时间上的相关性,也就是说,生产者发送消息的时候,消费者不必一定要在线;消费者消费的时候,生产者不必一定在线。
- 值得一提的是,目前设置的都是不使用事务(自动提交)、自动签收的操作,后面还有更多的操作。
2.3.3 Something interesting
- 值得一提的是,当队列中存在消息的时候,如果分别开启两个消费者,第一个消费者会很快地把所有消息消费掉。
- 当有两个消费者同时在运行,如果有多个消息一起进来了,消息就会平均分摊到两个消费者中。
3. 实现主题的入门案例
3.1 实现发布者
- 操作和上面基本上一致,只是在创造生产者(发布者)的时候,放进去的对象不是队列
Queue
,而是主题Topic
。其他地方不再赘述// 不一样的只有这两句 Topic topic = session.createTopic(DESTINATION_NAME); MessageProducer producer = session.createProducer(topic);
3.2 实现订阅者
- 与上面也基本一致。仅下面的操作不一致。
Topic topic = session.createTopic(DESTINATION_NAME); MessageConsumer consumer = session.createConsumer(topic);
3.3 主题总结
3.3.1 topic特点
- 生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系
- 生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息。也就是说,要先开消费者在那等着生产者发布消息;否则,如果让生产者先发布了消息到目的地,再开消费者,消费者没办法接收到消息。
- 生产者生产时,topic不保存消息它是无状态的不落地,假如无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者再启动生产者。
- 这是一个发布者+两个订阅者情况下,订阅者均收到消息的状态。可见,消息出队的个数是入队的个数的两倍。