JMS 基本知識及與Spring結合

轉自http://www.blogjava.net/Unmi/archive/2010/04/10/317947.html
JMS(Java Message Service) 是 Java 爲面向消息中間件(MOM)定義的接口。JMS 的通信管道就是消息隊列,說到消息隊列,歷史就悠久,在 MS 系統中很早就有 MSMQ,譬如郵件、羣組就是些消息隊列。JMS 因其異步,所以可用來解決高併發的問題,分佈式可對負載進行均衡。

JMS 已成爲 J2EE 規範中的一部分,所以在 J2EE 應用服務器中都有 JMS 核心部分 MQ 的實現,MQ 也有獨立的產品,如 ActiveMQ、JBoss MQ(已更名爲 JBoss Messaging)、WebSphere MQ 等。

如果我們蒙着頭來理解,JMS 消息通信中的主要角色應該有:消息生產者(Producer)、消息消費者(Consumer)、它們間的消息隊列(Queue)、以及所傳送的消息(Message)。

由於 JMS 有兩種通信模式:端到端(P2P) 和主題/訂閱模式,所以還有個角色就是主題(Topic)。通信中還需要處理諸如連接(Connection),面向連接就會產生會話(Session),而連接一般都是通過連接工廠(Connection Factory) 來獲得。

而 MQ 中間件的存在,使得端與端之間不需要直接相連來建立隊列,且對於主題/訂閱模式更是不可能,所以消息生產者和消費者它們都是指向到 MQ 中間件上的,它們在 MQ 上所指向的隊列或主題被抽像爲目的地(Destination),對於消息生產者稱之爲目的地可以理解,但作爲消費者來說也叫做目的地中文描述上有些欠妥,謂之消息來源地(Source) 較好理解。還有,因爲是異步的消息通信,所以就要註冊消息監聽器(MessageListener)

通過上面的理解,我們把 JMS 中所有的角色都串聯起來了,我們在編程中要處理的基本就那些角色(對象),下面是 JMS 所定義的所有接口關係圖(異常接口類未列於其中)。


[img]http://dl.iteye.com/upload/attachment/262683/503ea563-b5c1-3db0-bed5-cc16cc800134.jpg[/img]


現在來說說 JMS 中那兩種通信模式的區別。首先來看最簡單的 P2P 或隊列模型。它就像我們郵件發送那樣的方式,P 用戶發往 C 用戶的郵件只有 C 能收到,P 可以在多個郵件客戶端發郵件給 C,C 也可以開多個郵件客戶端來接收,某一個接收端收取了郵件 M 的話,則另一個接收端就收不到郵件 M 了。騰訊的 Foxmail 默認行爲除外,它收完郵件後還會在服務器端保留,經常造成公司郵箱佔用過大。用個圖來描述:


[img]http://dl.iteye.com/upload/attachment/262685/5d4446cd-4c01-38e2-b8b1-043f34cc9546.jpg[/img]

這種 P2P 的通信息方式應該是數據傳輸在實際中可能不需要經過 MQ 服務器,而是直接在兩個客戶端間進行。
只有一個消費者將獲得消息
生產者不需要在接收者消費該消息期間處於運行狀態,接收者也同樣不需要在消息發送時處於運行狀態。
每一個成功處理的消息都由接收者簽收

另一種傳送模型是 主題發佈/訂閱模型。這種方式就類似於郵件列表,一般用戶會就某個主題加入郵件列表,即訂閱了該主題,當就該主題發佈的信息,其它訂閱者就都能收到,而且接收方之間是不受影響的。用個圖來揣測一下 MQ Server 對該種模型的實現方式:


[img]http://dl.iteye.com/upload/attachment/262687/e56f2aca-974e-3fa6-a2c1-278428d6c7e9.jpg[/img]

其實在 ActiveMQ 的管理控制檯的 Topics 標籤頁中,也還是可以看到存在 Queue 的概念,這取決於 JMS 產品針對 Pub/Sub 模型的實現行爲上。

MQ 除了完成上面消息轉發或分發的任務之外,有時候稱這個爲目的地管理器 (DestinationManager),可以認爲是 MQ 的的核心,除此之外還要負責消息緩存、狀態、持久化、事物、安全性的管理。消息的持久性還是一項很重要的服務,消費方未啓動時,假如有消息到來,消費方再次連接也可以接收到消息,即使 MQ 重啓後消息也不會丟失。

MQ 產品一般還支持集羣,以及與 MSMQ 或其他 MQ 產品橋接起來,也允許用其他語言編寫客戶端程序。

最後來看下以上兩種消息傳送模型中,消息生產者與消費者實現的主要步驟,兩種模型的步驟差不多,只是一個是創建隊列(Queue),一個是創建主題(Topic)。

消息生產者實現主要步驟:

  //1. 由ConnectionFactory 創建連接,一般 ConnectionFactory 從 JNDI 中獲得
Connection connection = connectionFactory.createConnection();

//2. 創建 Session,
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

//3. P2P 中創建 Destination, 這裏創建了一個 Queue,隊列名爲 "Hello.Unmi"
Destination destination = session.createQueue("Hello.Unmi"); //實際應用中隊列是從 JNDI 中獲得
//3. Sub/Pub 模型時,創建 Destination, 創建了一個 Topic,主題爲"Unmi.Learn.ActiveMQ"
Destination destination = session.createTopic("Unmi.Learn.ActiveMQ");

//4. 創建 Producer,
MessageProducer producer = session.createProducer(destination);

//5. 創建 Message,這裏創建的是一個文本消息,可創建多種類型的消息
Message message = session.createTextMessage("Hello JMS Sended.");

//6. 發送消息
producer.send(message);

消息消費者實現主要步驟:

//1. 創建 Connection,//ActiveMQConnection 實現了 QueueConnection, TopicConnection
Connection connection = connectionFactory.createConnection();

//2. 創建 Session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

//3. 創建 Destination, 一個 Queue,隊列名與上同,這樣就能接收到前面生產者發來的消息
Destination destination = session.createQueue("Hello.Unmi");//實際應用中隊列是從 JNDI 中獲得
//3. Sub/Pub 模型時,創建 Destination, 一個 Topic,主題名同上,可接上前面發佈的消息
Destination destination = session.createTopic("Unmi.Learn.ActiveMQ"); //實際應用中隊列是從 JNDI 中獲得

//4. 創建 Consumer
MessageConsumer consumer = session.createConsumer(destination);

//5. 註冊消息監聽器,當消息到達時被觸發並處理消息,也可阻塞式監聽 consumer.receive()
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
//Do something with the message.
}
});



上面代碼全部是用最頂層的接口類型來引用的變量,實際應用中會用具體類型來引用變量,以使用更方便的方法。如直接用到 TopicPublisher、TopicSender、QueueSession、QueueConnection 等。而且還可能直接用某個 MQ 產品特定的實現類,如 ActiveMQConnection、等。如果爲了便於切換不同的 MQ 產品,當然用最上層的接口去引用類型,但不得不要用到某個 MQ 產品的有利特性的時候,程序代碼與該 MQ 產品存在這種高耦合也是不可避免的。


下面我麼藉助Mom4J來實現JMS
public final class Mom4jUtil {
public static void startJmsServer() throws Exception {
File f = new File("durable.dat.lck");
f.delete();
Mom4jFactory.start(new CustomConfig(), true);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.mom4j.jndi.InitialCtxFactory");
System.setProperty(Context.PROVIDER_URL, "xcp://localhost:8001");
}
}

class CustomConfig implements Mom4jConfig {

public int getPort() { return 4444; }
public int getAdminPort() { return 8888; }
public int getJndiPort() { return 8001; }
public int getThreadCount() { return 3; }
public int getSyncInterval() { return 1000; }
public int getAsyncInterval() { return 5000; }

public List getUsers() { return new ArrayList(); }
public List getContextHandlers() { return new ArrayList(); }

public File getMessageStore() {
File dir = new File("store/");
dir.mkdirs();
return dir;
}

public File getDurablesStore() {
return new File("durable.dat");
}

@SuppressWarnings("unchecked")
public List getDestinations() {
List list = new ArrayList(1);
list.add(new Mom4jDestination() {
public String getName() {
return "jms/queue";
}
public int getType() {
return Mom4jDestination.QUEUE;
}});
return list;
}
}

public class Sender extends Thread {
public void run() {
try {
Context ctx = new InitialContext();
ConnectionFactory factory = (ConnectionFactory) ctx.lookup("QueueConnectionFactory");
// Destination 即消息的目的地,消息發到何處
Destination destination = (Destination) ctx.lookup("jms/queue");
for (int i = 0;; i++) {
Connection connection = null;
try {
connection = factory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(destination);
String text = "Hello, it is " + new Date();
System.out.println(" Send: " + text);
Message message = session.createTextMessage(text);
producer.send(message);
producer.close();
session.close();
if(i%10 ==0)
TimeUnit.SECONDS.sleep(10);
} catch (JMSException e) {
throw new RuntimeException(e);
} finally {
if (connection != null)
connection.close();
}
TimeUnit.MILLISECONDS.sleep(200);
}
} catch (NamingException e) {
throw new RuntimeException(e);
} catch (JMSException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

public class Receiver extends Thread implements MessageListener {
public void run() {
try {
Context ctx = new InitialContext();
ConnectionFactory factory = (ConnectionFactory) ctx.lookup("QueueConnectionFactory");
// Destination 即消息的來源地,從何處接受消息
Destination destination = (Destination) ctx.lookup("jms/queue");
Connection connection = null;
try {
connection = factory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(destination);
consumer.setMessageListener(this);
connection.start();
Thread.sleep(20000);
} catch (JMSException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (connection != null)
connection.close();
}
} catch (NamingException e) {
throw new RuntimeException(e);
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
// MessageListener 的實現類是線程安全的
public void onMessage(Message message) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
if (message instanceof TextMessage) {
TextMessage text = (TextMessage) message;
try {
System.out.println("Receive: " + text.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}

public class Main {
public static void main(String[] args) throws Exception {
Mom4jUtil.startJmsServer();
new Sender().start();
new Receiver().start();
}
}



Spring提供JmsTemplate,簡化JMS操作
  <bean id="jmsConnectionFactory" 
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="QueueConnectionFactory" />
</bean>

<bean id="jmsQueue" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jms/queue" />
</bean>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="defaultDestination" ref="jmsQueue" />
</bean>

<bean id="sender" class="example.chapter9.Sender">
<property name="jmsTemplate" ref="jmsTemplate" />
</bean>

<bean id="receiver" class="example.chapter9.Receiver" />

<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="destination" ref="jmsQueue" />
<property name="messageListener" ref="receiver" />
</bean>


public class Sender {
private JmsTemplate jmsTemplate;
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void send(final String text) {
System.out.println(" Send: " + text);
jmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(text);
}
});
}
}

public class Receiver implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage text = (TextMessage) message;
try {
System.out.println("Receive: " + text.getText());
} catch (JMSException e) {
}
}
}
}


大多數時候,除了簡單的TextMessage外,需要發送的消息都應該被封裝到Java類中,Spring提供了一個MessageConverter接口,方便實現Java類和JMS消息的轉化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章