JMS(Java Massage Service)與MDB(Massage Driver Bean)

JMS(Java Massage Service)與MDB(Massage Driver Bean)
作者:嶽鄉成
一、 JMS簡介
1、 什麼是JMS
JMS(Java Massage Service,Java消息服務)是一組Java應用程序接口(Java API),它提供創建、發送、接受、讀取消息的服務。由Sun公司和它的合作伙伴設計的JMS API定義了一組公共的應用程序接口和相應的語法,使得Java程序能夠和其他消息組件進行通信。
JMS是一種與廠商無關的API,提供了獨立於特定廠商的企業消息系統訪問方式。
JMS由兩部分組成:JMS客戶端和JMS消息發送服務(有時也稱爲消息中介程序或路由器)(JMS Provide)。使用JMS消息的被稱爲JMS客戶端;處理消息路由與傳遞的消息系統被稱爲JMS Provide。JMS應用是由多個JMS客戶端和一個JMS Provide構成的業務系統。發送消息的JMS客戶端被稱爲生產者(producer),而接受消息的JMS客戶端則被稱爲消費者(consumer)。同一個JMS客戶端即可以是生產者,也可以是消費者。
JMS的編程過程很簡單,概括爲:應用程序A發送一條消息到消息服務器(JMS Provide),然後消息服務器把消息轉發給應用程序B。如下圖所示:

消息傳遞系統的中心是消息。一條Massage由3個部分組成:
(1)頭:每條JMS消息都必須具有消息頭。頭字段包含用於路由和識別消息的值。
(2)屬性:消息可以包含稱作屬性的可選頭字段。
(3)主體:包含要發送給接收應用程序的內容。
根據有效負載的類型來劃分,可以將消息分爲幾種類型,它們分別攜帶:簡單文本 (TextMessage)、可序列化的對象 (ObjectMessage)、屬性集合 (MapMessage)、字節流 (BytesMessage)、原始值流 (StreamMessage),還有無有效負載的消息 (Message)。
消息收發系統是異步的,也就是說,JMS客戶機可以發送消息而不必等待迴應。在JMS中,客戶機將消息發送給一個虛擬通道(主題或隊列),而其它JMS客戶機則預定或監聽這個虛擬通道。當JMS客戶機發送消息時,它並不等待迴應。它執行發送操作,然後繼續執行下一條指令。消息可能最終轉發到一個或許多個客戶機。這些客戶機都不需要立即做出迴應。
JMS支持兩種消息模型:Point-to-Point消息(PTP)和發佈訂閱消息(Publish Subscribe messaging,簡稱Pub/Sub)。JMS規範並不要求供應商同時支持這兩種消息模型,但開發者應該熟悉這兩種消息模型的優勢與缺點。
PTP消息模型是在點對點之間傳遞消息時使用。如果應用程序開發者希望每一條消息都能夠被處理,那麼應該使用PTP消息模型。與Pub/Sub消息模型不同,P2P消息總是能夠被傳送到指定的位置。
通過點對點(PTP)的消息傳遞模式,一個應用程序可以向另一個應用程序發送消息。在此傳遞模式中,目標類型是隊列。消息首先被傳遞至隊列目標,然後從該隊列將消息傳送至對此隊列進行監聽的某個消費者。如下圖所示:

Pub/Sub模型在一到多的消息廣播時使用。如果一定程度的消息傳遞的不可靠性可以被接受的話,那麼應用程序開發者也可以使用Pub/Sub消息模型。換句話說,它適用於所有的消息消費程序並不要求能夠收到所有的信息或者消息消費程序並不想接收到任何消息的情況。
通過發佈/訂閱(pub/sub)消息傳遞模式,應用程序能夠將一條消息發送到多個接受方。在此傳遞模式中,目標類型是主題。消息首先被傳遞至主題目標,然後傳送至所有已訂閱此主題的活動消費者。

PTP消息傳遞模式是傳統意義上的拉模式,在此模式中,消息不是自動推送給客戶端的,而是要由客戶端從隊列中請求獲得。Pub/Sub消息傳遞模型基本上是一個推模式,在該模式中,消息會自動廣播,消費者無需通過主動請求或輪詢主題的方式來獲得新的消息。
上面兩種消息傳遞模式裏,我們都需要定義消息生產者和消費者,生產者把消息發送到JMS Provider的某個目標地址(Destination),消息從該目標地址傳送至消費者。消費者可以同步或異步接收消息,一般而言,異步消息消費者的執行和伸縮性都優於同步消息的接收者。體現在如下幾個方面:
(1)異步消息的接收者創建的網絡流量比較小。
(2)異步消息接收者使用的線程比較小。
(3)使用異步消息接受者可以防止應用程序代碼在服務器上執行阻塞操作。
消息驅動Bean是異步消息消費者,它由EJB容器進行管理,具有一般的JMS消費者所不具有的優點。例如:容器可以創建多個消息驅動Bean實例來處理大量的併發消息,而一般的JMS消費者開發時則必須對此進行處理才能獲得類似的功能。同時消息驅動Bean可取得EJB所能提供的標準服務,如容器管理事務等服務。
2、什麼是消息驅動Bean
消息驅動Bean是設計用來專門處理基於消息請求的組件。它能夠收發異步的JMS消息,並能夠輕易地與其他EJB交互,特別適用於當一個業務執行的時間很長,而執行結果無需實時向用戶反饋的場合。
消息驅動Bean像一個沒有local和remote接口的無狀態Session Bean,它和無狀態的Session Bean一樣也使用了實例池機制,容器可以爲它創建大量的實例,用來併發處理成千上萬個JMS消息。
消息驅動Bean通常要實現MassageListerner接口,該接口定義了onMassage()方法,消息驅動Bean通過它來處理收到的JMS消息。
Package javax.jms;
Public interface MassageListener{
Public void onMassage(Massage message);
}
消息驅動Bean通過註釋來指定監聽哪一個消息發送者發生的JMS消息,當監聽到有消息到達時,容器調用onMassage()方法,將消息作爲參數傳入消息驅動Bean中,消息驅動Bean在onMassage()中決定如何處理該消息。
當一個業務執行的時間很長,而執行結果無需實時向用戶反饋時,非常適合MDB,如訂單成功後給用戶發送一份電子郵件或發送一條短信等。

二、 PTP消息傳遞模型
1、配置消息到達的目的地址(Destination)
在開始JMS編程前,需要先配置消息到達的目的地址(Destination),正如發送電子郵件一樣,需要先知道對方的E-mail地址。以Jboss爲例。
Jboss使用一個XML文件配置隊列地址,其文件的命名格式應遵守*-service.xml。
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:service=Queue,name=foshanshop">
<attribute name="JNDIName">queue/foshanshop</attribute>
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
</server>
配置文件編寫完成後,將它複製到“jboss的安裝目錄server/default/deploy”目錄下,這樣會引發隊列的熱部署,可以通過http://localhost:8080/jmx-console進入jboss管理臺,查看剛纔部署的隊列。
其實在Jboss中使用MDB,可以不配置上述文件,Jboss會根據MDB裏的信息自動創建隊列地址。
2、當隊列部署成功後,就可以針對該隊列編寫消息生產者。它發送了5種類型的消息。
package com.foshanshop.ejb3.app;
import java.util.Properties;

import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;

import com.foshanshop.ejb3.bean.Man;
/**
* 發送Queue消息
* @author yuexiangcheng
*
*/
public class QueueSender {
public static void main(String[] args) {
QueueConnection conn = null;
QueueSession session = null;
try {
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.setProperty(Context.PROVIDER_URL, "localhost:1099");
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
InitialContext ctx = new InitialContext(props);

QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("QueueConnectionFactory");
conn = factory.createQueueConnection();
session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
Destination destination = (Queue) ctx.lookup("queue/foshanshop");
MessageProducer producer = session.createProducer(destination);

//發送文本
TextMessage msg = session.createTextMessage("山西人您好,這是我的第一個消息驅動Bean");
producer.send(msg);

//發送Ojbect(對象必須實現序列化,否則等着出錯吧)
producer.send(session.createObjectMessage(new Man("大美女", "北京朝陽區和平里一號")));

//發送MapMessage
MapMessage mapmsg = session.createMapMessage();
mapmsg.setObject("no1", "北京和平里一號");
producer.send(mapmsg);

//發送BytesMessage
BytesMessage bmsg = session.createBytesMessage();
bmsg.writeBytes("我是一個兵,來自老百姓".getBytes());
producer.send(bmsg);

//發送StreamMessage
StreamMessage smsg = session.createStreamMessage();
smsg.writeString("巴巴運動網,http://www.babasport.com");
producer.send(smsg);

} catch (Exception e) {
e.printStackTrace();
}finally{
try {
session.close ();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
3、下面是Queue消息的接受方,它是一個MDB。

package com.foshanshop.ejb3.impl;

import java.io.ByteArrayOutputStream;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.BytesMessage;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;

import com.foshanshop.ejb3.bean.Man;

@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="queue/foshanshop"),
@ActivationConfigProperty(propertyName="acknowledgeMode",
propertyValue="Auto-acknowledge")
})
public class PrintBean implements MessageListener {

public void onMessage(Message msg) {
try {
if (msg instanceof TextMessage) {
TextMessage tmsg = (TextMessage) msg;
String content = tmsg.getText();
System.out.println(content);
}else if(msg instanceof ObjectMessage){
ObjectMessage omsg = (ObjectMessage) msg;
Man man = (Man) omsg.getObject();
String content = man.getName()+ " 家住"+ man.getAddress();
System.out.println(content);
}else if(msg instanceof MapMessage){
MapMessage map = (MapMessage) msg;
String content = map.getString("no1");
System.out.println(content);
}else if(msg instanceof BytesMessage){
BytesMessage bmsg = (BytesMessage) msg;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[256];
int length = 0;
while ((length = bmsg.readBytes(buffer)) != -1) {
byteStream.write(buffer, 0, length);
}
String content = new String(byteStream.toByteArray());
byteStream.close();
System.out.println(content);
}else if(msg instanceof StreamMessage){
StreamMessage smsg = (StreamMessage) msg;
String content = smsg.readString();
System.out.println(content);
}

} catch (Exception e){
e.printStackTrace();
}
}
}
三、 Pub/Sub消息傳遞模式
Topic消息允許很多個主題訂閱者接受同一個消息,所以下面的例子定義了兩個消息接收者,當一條消息到達時,這兩個接收者都可以收到。
1、配置消息到達的目標地址
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.mq.server.jmx.Topic"
name="jboss.mq.destination:service=Topic,name=chatTopic">
<attribute name="JNDIName">topic/chatTopic</attribute>
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
</mbean>
</server>
2、針對該目標地址編寫消息的生產者如下:
package com.foshanshop.ejb3.app;
import java.util.Properties;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;
/**
* 發送Topic消息
* @author lihuoming
*
*/
public class TopicSender {

public static void main(String[] args) {
TopicConnection conn = null;
TopicSession session = null;
try {
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.setProperty(Context.PROVIDER_URL, "localhost:1099");
props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
InitialContext ctx = new InitialContext(props);

TopicConnectionFactory factory = (TopicConnectionFactory) ctx.lookup("TopicConnectionFactory");
conn = factory.createTopicConnection();
session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE);
Destination destination = (Topic) ctx.lookup("topic/chatTopic");
MessageProducer producer = session.createProducer(destination);
//發送文本
TextMessage msg = session.createTextMessage("您好,這是我的第一個消息驅動Bean");
producer.send(msg);
} catch (Exception e) {
System.out.println(e.getMessage());
}finally{
try {
session.close ();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
3、消息的消費者MDB
第一個MDB:TopicPrintBeanOne.java
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/chatTopic")
})
public class TopicPrintBeanOne implements MessageListener{

public void onMessage(Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
String content = tmsg.getText();
System.out.println(this.getClass().getName()+"=="+ content);
} catch (Exception e){
e.printStackTrace();
}
}
}
第二個MDB:TopicPrintBeanTwo.java
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/chatTopic")
})
public class TopicPrintBeanTwo implements MessageListener{

public void onMessage(Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
String content = tmsg.getText();
System.out.println(this.getClass().getName()+"=="+ content);
} catch (Exception e){
e.printStackTrace();
}
}
}
四、 在Session bean中發送消息
1、在Session bean中發送Queue消息實例如下:
package com.foshanshop.ejb3.impl;
import javax.annotation.Resource;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;

import com.foshanshop.ejb3.QSender;
import com.foshanshop.ejb3.bean.Man;

/**
* 發送Queue消息
* @author yuexiangcheng
*
*/
@Stateless
@Remote (QSender.class)
public class QSenderBean implements QSender {
@Resource(mappedName="QueueConnectionFactory") private QueueConnectionFactory factory;
@Resource(mappedName="queue/foshanshop") private Queue destination;

public void send() {
QueueConnection conn = null;
QueueSession session = null;
try {
conn = factory.createQueueConnection();
session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(destination);

//發送文本
TextMessage msg = session.createTextMessage("佛山人您好,這是我的第一個消息驅動Bean");
producer.send(msg);

//發送Ojbect(對象必須實現序列化,否則等着出錯吧)
producer.send(session.createObjectMessage(new Man("大美女", "北京朝陽區和平里一號")));

//發送MapMessage
MapMessage mapmsg = session.createMapMessage();
mapmsg.setObject("no1", "北京和平里一號");
producer.send(mapmsg);

//發送BytesMessage
BytesMessage bmsg = session.createBytesMessage();
bmsg.writeBytes("我是一個兵,來自老百姓".getBytes());
producer.send(bmsg);

//發送StreamMessage
StreamMessage smsg = session.createStreamMessage();
smsg.writeString("巴巴運動網,http://www.babasport.com");
producer.send(smsg);
}catch (Exception e){
e.printStackTrace();
}finally{
try {
session.close ();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}

}
2、在Session bean中發送Topic消息實例如下:
package com.foshanshop.ejb3.impl;

import javax.annotation.Resource;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;

import com.foshanshop.ejb3.TSender;
/**
* 發送Topic消息
* @author yuexiangcheng
*
*/
@Stateless
@Remote (TSender.class)
public class TSenderBean implements TSender {
@Resource(mappedName="TopicConnectionFactory") private TopicConnectionFactory factory;
@Resource(mappedName="topic/chatTopic") private Topic destination;

public void send(String msg) {
TopicConnection conn = null;
TopicSession session = null;
try {
conn = factory.createTopicConnection();
session = conn.createTopicSession(false, TopicSession.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(destination);
TextMessage text = session.createTextMessage(msg);
producer.send(text);
}catch (Exception e){
e.printStackTrace();
}finally{
try {
session.close ();
conn.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
五、 消息驅動bean消費遠程的JMS消息
以上的例子都是處理本地JMS消息,那麼如何實現Jboss下的消息驅動bean消費遠程的JMS消息,可以通過以下兩步來實現。
1、創建目標地址文件,該文件命名規則爲*-service.xml(*爲自定義的一個或多個字符),該文件編寫好後放在jboss的deploy目錄下,這樣就可以在本地得到一個從遠程獲取的JMS provider。代碼如下:
<mbean code="org.jboss.jms.jndi.JMSProviderLoader" name="jboss.mq:service=JMSProviderLoader,name=RemoteJMSProvider,server=remotehost">
<attribute name="ProviderName">RemoteJMSProvider</attribute>
<attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JNDIProviderAdapter</attribute>
<!-- The connection factory -->
<attribute name="FactoryRef">UIL2XAConnectionFactory</attribute>
<!-- The queue connection factory -->
<attribute name="QueueFactoryRef">UIL2XAConnectionFactory</attribute>
<!-- The topic factory -->
<attribute name="TopicFactoryRef">UIL2XAConnectionFactory</attribute>
<!-- Connect to JNDI on the host "the-remote-host-name" port 1099-->
<attribute name="Properties">
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jnp.interfaces
java.naming.provider.url=192.168.1.101:1099
</attribute>
</mbean>
把這行java.naming.provider.url=192.168.1.101:1099代碼中的IP地址(192.168.1.101)該成你要獲取消息的遠程服務器IP地址。
2、編寫MDB類,代碼如下:
@MessageDriven(activateConfig = {
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/testQueue"),
@ActivationConfigProperty(propertyName="providerAdapterJNDI", propertyValue="java:/RemoteJMSProvider")
})
public class MDB implements MessageListener {
...
}
發佈了14 篇原創文章 · 獲贊 0 · 訪問量 1514
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章