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。