JMS(java訊息服務)學習筆記
JMS(java訊息服務)是java平臺關於面向訊息中介軟體的api介面,用於在應用程式和分散式系統中傳送訊息,進行非同步通訊。JMS提供了一套類似JDBC的技術規範,服務的實現由具體的實現提供商提供。
使用JMS,可以解決諸多的體系結構性問題,比如異構系統整合通訊,緩解系統瓶頸,提高系統的伸縮性(非同步、非點對點的模式使得處理訊息的應用可以水平擴充套件),增強系統使用者體驗,使得系統模組化和元件化變得可行並更加靈活。
使用JMS的集群系統有以下兩個角色:訊息傳送客戶端和訊息傳送伺服器。客戶端向伺服器端傳送訊息,伺服器隨後再將訊息傳送給一個或者多個訊息接收的客戶端。
JMS支援兩類訊息傳送模型:點對點模式和釋出/訂閱模式。點對點適用於一對一的訊息傳送,而釋出/訂閱模型則適用於訊息組播的場景。點對點模型通常是一個基於拉取或者輪詢的訊息傳送模型,這種模型從佇列中請求資訊,而不是將訊息推送到客戶端。這個模型的特點是傳送到佇列的訊息被一個且只有一個接收者接收處理,即使有多個訊息監聽者也是如此。基於這一點,JMS可以使用這種訊息傳送模型做負載均衡。點對點模型還可以允許接收者在接收訊息之前檢視訊息的內容,而釋出訂閱模型則不行。
釋出訂閱模型則是一個基於推送的訊息傳送模型。釋出訂閱模型可以用多種不同的訂閱者,臨時訂閱者只在主動監聽主題時才接收訊息,而持久訂閱者則監聽主題的所有訊息,即時當前訂閱者不可用,處於離線狀態。
JMS API中提供的公共介面ConnectionFactory需要從JMS提供者出用JNDI的方式獲取,有了ConnectionFactory,就可以獲取連線JMS伺服器的連結(Connection)、會話(Session)等。
這裡要講一下JNDI,JNDI是一組在java應用中訪問命名和目錄服務的api。它可以使應用程式使用者用api從網路上的 JNDI 提供者中獲取命名服務物件。它的一個可能實現是:將物件例項繫結到jnpserver後,當遠端端採用context.lookup()方式獲取遠端物件例項並開始呼叫時,jnp server獲取物件例項,將其序列化回本地,然後在本地進行反序列化,之後在本地進行類屬性訪問。通過這個機制,就可以知道了,本地其實是必須有繫結到jboss上的物件例項的class的,否則反序列化的時候肯定就失敗了。
一般的JMS伺服器也提供了JNDI的支援,會將ConnectionFactory物件以jndi的方式釋出出來,JMS客戶端通過這種方式獲取到連結之後,就可以建立會話來和JMS伺服器進行訊息傳遞了。
使用ActiveMQ並測試一個簡單的聊天室程式的步驟:
1.在activemq官網(http://activemq.apache.org/)上下載activemq應用包,解壓,在bin目錄下使用啟動指令碼啟動。
2.建立測試工程,測試工程要引入activemq的jar包,如:activemq-all-5.7.0.jar,然後編寫聊天室程式Chat.java:
packagecn.com.hanmfree;
import java.io.*;
import javax.jms.*;
import javax.naming.*;
public class Chat implementsjavax.jms.MessageListener{
private TopicSessionpubSession;
private TopicPublisherpublisher;
private TopicConnectionconnection;
private String username;
/* Constructor used toInitialize Chat */
public Chat(StringtopicFactory, String topicName, String username)
throwsException {
//Obtain a JNDI connection using the jndi.properties file
InitialContextctx = new InitialContext();
// Look up aJMS connection factory
TopicConnectionFactory conFactory =
(TopicConnectionFactory)ctx.lookup(topicFactory);
// Create aJMS connection
TopicConnection connection = conFactory.createTopicConnection();
// Create twoJMS session objects
TopicSessionpubSession = connection.createTopicSession(
false,Session.AUTO_ACKNOWLEDGE);
TopicSessionsubSession = connection.createTopicSession(
false,Session.AUTO_ACKNOWLEDGE);
// Look up aJMS topic
TopicchatTopic = (Topic)ctx.lookup(topicName);
// Create aJMS publisher and subscriber
TopicPublisherpublisher =
pubSession.createPublisher(chatTopic);
TopicSubscriber subscriber =
subSession.createSubscriber(chatTopic, null, true);
// Set a JMSmessage listener
subscriber.setMessageListener(this);
// Intializethe Chat application variables
this.connection = connection;
this.pubSession = pubSession;
this.publisher= publisher;
this.username= username;
// Start theJMS connection; allows messages to be delivered
connection.start( );
}
/* Receive Messages FromTopic Subscriber */
public voidonMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
String text = textMessage.getText( );
System.out.println(text);
} catch(JMSException jmse){ jmse.printStackTrace( ); }
}
/* Create and Send MessageUsing Publisher */
protected voidwriteMessage(String text) throws JMSException {
TextMessagemessage = pubSession.createTextMessage( );
message.setText(username+": "+text);
publisher.publish(message);
}
/* Close the JMS Connection*/
public void close( ) throwsJMSException {
connection.close( );
}
/* Run the Chat Client */
public static voidmain(String [] args){
try{
if (args.length!=3)
System.out.println("Factory, Topic, or usernamemissing");
// args[0]=topicFactory; args[1]=topicName; args[2]=username
Chat chat = new Chat(args[0],args[1],args[2]);
// Read from command line
BufferedReader commandLine = new
java.io.BufferedReader(new InputStreamReader(System.in));
// Loop until the word "exit" is typed
while(true){
String s = commandLine.readLine( );
if (s.equalsIgnoreCase("exit")){
chat.close( ); // close down connection
System.exit(0);// exit program
} else
chat.writeMessage(s);
}
} catch(Exception e){ e.printStackTrace( ); }
}
}
3.在測試工程的classpath中增加jndi配置檔案jndi.properties,具體內容如下:
java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory
java.naming.provider.url=tcp://localhost:61616
java.naming.security.principal=system
connectionFactoryNames=TopicCF
topic.topic1=jms.topic1
其中org.apache.activemq.jndi.ActiveMQInitialContextFactory是JMS介面TopicConnectionFactory的ActiveMQ實現,
tcp://localhost:61616 是activemq的jndi服務的預設地址,connectionFactoryNames是jndi物件的名字,topic.topic1是配置的topic,配置的具體值可以自行配置。
4.執行多個Chat程式,傳入TopicCF topic1<name>三個入參,可以實現在控制檯上和其他程式進行通訊,一個程式輸出的內容會被推送到其他程式中。
5.這個程式演示了使用activemq實現釋出/訂閱模型的訊息傳送方式。Chat類即是釋出者角色又是訂閱者角色,Chat類的構造方法中演示了從jndi從獲取TopicConnectionFactory物件,根據TopicConnectionFactory獲取connection,進而獲取session上的publisher和subscriber,publisher物件用來實現釋出的邏輯,subscriber則用來實現訂閱的邏輯。注意到,獲得subscriber之後還需要設定這個subscriber物件的監聽器,監聽器物件需要是一個 javax.jms.MessageListener 類的子類物件,實現了MessageListener 類上的onMessage方法,當訊息接受到時,會呼叫物件的onMessage。