基於上篇「點對點隊列」教程,僅需將 JNDI 資源類型從 Queue 改為 Topic,即可實現「一條消息、多個訂閱者同時收到」的廣播模式。
| 特性 | Point-to-Point (Queue) | Publish/Subscribe (Topic) |
|---|---|---|
| 消息模型 | 負載均衡:一條消息僅一個消費者 | 廣播:一條消息所有活躍訂閱者 |
| 持久化 | 天然持久,消費者離線也能收 | 默認非持久,需持久化訂閱離線接收 |
| JNDI 類型 | javax.jms.Queue | javax.jms.Topic |
| 連接工廠 | QueueConnectionFactory | TopicConnectionFactory |
| 會話/發送/接收 | QueueSession/QueueSender/QueueReceiver | TopicSession/TopicPublisher/TopicSubscriber |
編輯 conf/context.xml:
<Resource name="topic/connectionFactory"
auth="Container"
type="org.apache.activemq.ActiveMQConnectionFactory"
description="JMS Connection Factory"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
brokerURL="failover:(tcp://localhost:61616)?initialReconnectDelay=100&maxReconnectAttempts=5"
brokerName="LocalActiveMQBroker"
useEmbeddedBroker="false" />
<Resource name="topic/topic0"
auth="Container"
type="org.apache.activemq.command.ActiveMQTopic"
description="My Topic"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="TestTopic" />
關鍵點:
type 指定 ActiveMQTopicphysicalName 為 ActiveMQ 內部 Topic 名稱brokerURL 使用 failover: 協議支持自動重連package pubSub;
import javax.jms.*;
import javax.naming.InitialContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
@WebServlet("/Publish")
public class Publisher extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
PrintWriter out = resp.getWriter();
try {
InitialContext ctx = new InitialContext();
Topic topic = (Topic) ctx.lookup("java:comp/env/topic/topic0");
TopicConnectionFactory factory = (TopicConnectionFactory)
ctx.lookup("java:comp/env/topic/connectionFactory");
TopicConnection conn = factory.createTopicConnection();
TopicSession session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
TopicPublisher publisher = session.createPublisher(topic);
publisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
TextMessage msg = session.createTextMessage("Hello Topic World @ " + System.currentTimeMillis());
publisher.publish(msg);
out.write("Message Published: " + msg.getText());
conn.close();
} catch (Exception e) {
e.printStackTrace();
out.write("Error: " + e.getMessage());
}
}
}
package pubSub;
import javax.jms.*;
import javax.naming.InitialContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
@WebServlet("/Subscribe")
public class Subscriber extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
PrintWriter out = resp.getWriter();
try {
InitialContext ctx = new InitialContext();
Topic topic = (Topic) ctx.lookup("java:comp/env/topic/topic0");
TopicConnectionFactory factory = (TopicConnectionFactory)
ctx.lookup("java:comp/env/topic/connectionFactory");
TopicConnection conn = factory.createTopicConnection();
TopicSession session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
TopicSubscriber subscriber = session.createSubscriber(topic);
conn.start(); // 必須啟動!
TextMessage msg = (TextMessage) subscriber.receive(5000); // 5秒超時避免永久阻塞
if (msg != null)
out.write("Message Received: " + msg.getText());
else
out.write("Timeout: No message received");
conn.close();
} catch (Exception e) {
e.printStackTrace();
out.write("Error: " + e.getMessage());
}
}
}
/Subscribe (模擬多訂閱者)
/Publish 發送消息

conn.start() 必須在創建 Subscriber 後調用,否則 receive() 永久阻塞TopicSubscriber sub = session.createDurableSubscriber(topic, "clientId-unique");
需配置 clientId 於連接工廠或連接上
receive(long timeout) 防止 HTTP 請求掛死queue/ 前綴,Topic 用 topic/,互不干擾@Autowired JmsTemplate jmsTemplate;
public void publish(String msg) {
jmsTemplate.convertAndSend("TestTopic", msg);
}
@JmsListener(destination = "TestTopic", containerFactory = "topicListenerFactory")
public void receive(String msg) {
System.out.println("Received: " + msg);
}
Spring Boot 只需 spring-boot-starter-activemq 自動配置,極大簡化開發。