1. 持久订阅时, 客户端需要首先向JMS提供者注册一个表面自己身份的id(clientId)。这样当咱们这个客户端处于离线时, JMS提供者会为这个客户端保存所有发送到主题的消息。当客户端再次连接到JMS提供者时, JMS提供者根据这个客户端id, 把消息发送给它。
2. 创建持久订阅必须设置一个客户端id, 不然会报如下错误
3. 设置客户端id
3.1. 设置客户端id要紧跟在创建连接之后
// 1. 创建一个连接工厂
TopicConnectionFactory cf = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);
// 2. 创建连接
TopicConnection conn = cf.createTopicConnection();
// 3. 设置客户端id
conn.setClientID(clientId);
3.2. 如果设置客户端id没有紧跟在创建连接之后回报如下错误
4. 多个客户端设置clientID, clientID不能重复。如果已有一个活动的被clientID标识的客户端, 再出现一个重复clientID标识的客户端连接, 会报如下错误
5. 持久订阅的实现机制
5.1. 生产者发送消息给提供者, 如果此时提供者发现没有任何的消费者(包括在线/离线), 那么就会认为该消息无用, 不需要存储, 会直接删除。
5.2. 如果有在线的消费者, 那么提供者会将消息直接传送给在线的消费者, 因为这个时候连接是通的, 消息有传输的通道。
5.3. 如果有离线的消费者, 那么提供者会把属于该消费者的消息存储下来, 等消费者在线的时候, 再将保存的离线消息推送给它。对于持久订阅者, 提供者会在该消费者第一次登录在线的时候, 将它的身份信息记录下来。记录身份的关键就是clientID和主题名称。当持久订阅者又重新在线的时候, 提供者会根据当前连接的clientID和主题名称, 去查询属于它的离线消息, 并进行推送。
6. 例子
6.1. 新建一个名为JMSDurableSubscriber的Java项目, 同时拷入相关jar包
6.2. 编写MyProducer.java
package com.jmsapp.persistent;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class MyProducer {
// 默认连接用户名
private static final String dftUsr = ActiveMQConnection.DEFAULT_USER;
// 默认用户密码
private static final String dftPwd = ActiveMQConnection.DEFAULT_PASSWORD;
// 默认连接地址
private static final String dftUrl = ActiveMQConnection.DEFAULT_BROKER_URL;
// 队列名称
private static final String topicName = "persistentSubscriber";
public static void main(String[] args) {
// 1. 创建一个连接工厂
TopicConnectionFactory cf = new ActiveMQConnectionFactory(dftUsr, dftPwd, dftUrl);
// 连接对象
TopicConnection conn = null;
// 会话对象
TopicSession session = null;
try {
// 2. 创建连接
conn = cf.createTopicConnection();
// 3. 启动连接
conn.start();
// 4. 创建会话
session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
// 5. 创建消息目的地。如果是点对点, 那么它的实现是Queue; 如果是订阅模式, 那它的实现是Topic。这里我们创建一个名为persistentSubscriber的主题。
Topic topic = session.createTopic(topicName);
// 6. 消息生产者
TopicPublisher publisher = session.createPublisher(topic);
// 7. 创建文本消息和发送消息
StreamMessage message = session.createStreamMessage();
message.writeString("JMS中的持久订阅");
publisher.publish(message);
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
if (session != null) {
session.close();
}
} catch (JMSException e1) {
e1.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
}
6.3. 编写MyConsumer.java
package com.jmsapp.persistent;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class MyConsumer {
// 默认连接用户名
private static final String dftUsr = ActiveMQConnection.DEFAULT_USER;
// 默认用户密码
private static final String dftPwd = ActiveMQConnection.DEFAULT_PASSWORD;
// 默认连接地址
private static final String dftUrl = ActiveMQConnection.DEFAULT_BROKER_URL;
// 队列名称
private static final String topicName = "persistentSubscriber";
// 客户端id
private static final String clientId = "rjbd";
public static void main(String[] args) {
// 1. 创建一个连接工厂
TopicConnectionFactory cf = new ActiveMQConnectionFactory(dftUsr, dftPwd, dftUrl);
// 连接对象
TopicConnection conn = null;
// 会话对象
TopicSession session = null;
try {
// 2. 创建连接
conn = cf.createTopicConnection();
// 3. 设置客户端id
conn.setClientID(clientId);
// 4. 创建会话
session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
// 5. 创建消息目的地。如果是点对点, 那么它的实现是Queue; 如果是订阅模式, 那它的实现是Topic。这里我们创建一个名为persistentSubscriber的主题。
Topic topic = session.createTopic(topicName);
// 6. 消息消费者
TopicSubscriber subscriber = session.createDurableSubscriber(topic, clientId);
// 7. 接收消息
subscriber.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message msg) {
try {
StreamMessage message = (StreamMessage) msg;
System.out.println("接收: " + message.readString());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
// 8. 启动连接, 准备开始接收消息
conn.start();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
6.4. 运行MyConsumer.java, 接收端是一直处于运行状态的
6.5. 终止运行MyConsumer.java
6.6. 运行MyProducer.java
6.7. 再次运行MyConsumer.java, 接收到消息(非持久化订阅, 这样操作就接收不到消息)