OpenJMS消息
JMS 有五种消息类型。三种结构化或半结构化的消息类型(MapMessage、ObjectMessage 和StreamMessage)以及两种非结构化的或自由格式的消息类型(TextMessage 和 BytesMessage)。
OpenJMS消息通讯模式有两种:
点到点(point-to-point)(PTP)模型
PTP(Point-to-Point)模型是基于队列的,发送方发消息到队列,接收方从队列接收消息,队列的存在使得消息的异步传输成为可能。
消息的发布和订阅
JMS 的发布/订阅模型定义了如何向一个内容节点发布和订阅消息,内容节点也叫主题(topic),主题是为发布者(publisher)和订阅者(subscribe) 提供传输的中介。发布/订阅模型使发布者和订阅者之间不需要直接通讯(如RMI)就可保证消息的传送,有效解决系统间耦合问题(当然有这个需要才行),还有就是提供了一对一、一对多的通讯方式,比较灵活。
先介绍JMS里2个概念,持久订阅模式和非持久订阅模式,其实也是发布/订阅模型在可靠性上提供的2种方式:
非持久订阅模式:只有当客户端处于激活状态,也就是和JMS 服务器保持连接的状态下,才能接收到发送到某个Topic的消息,而当客户端处于离线状态时,则这个时间段发到Topic的消息将会永远接收不到。
持久订阅模式:客户端向JMS 注册一个识别自己身份的ID,当这个客户端处于离线时,JMS 服务器会为这个ID 保存所有发送到主题的消息,当客户再次连接到JMS 服务器时,会根据自己的ID 得到所有当自己处于离线时发送到主题的消息,即消息永远能接收到。
下面我们就通过代码来熟悉PTP和发布/订阅模式
PTP模式
发送者代码
import java.util.Hashtable; import javax.jms.JMSException; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueConnectionFactory; import javax.jms.QueueSender; import javax.jms.QueueSession; import javax.jms.Session; import javax.jms.TextMessage; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class QueueSend { public static void main(String[] args) throws NamingException, JMSException { // Hashtable<String, String> properties = new Hashtable<String, String>(); // // properties.put(Context.INITIAL_CONTEXT_FACTORY,"org.exolab.jms.jndi.InitialContextFactory"); // // properties.put(Context.PROVIDER_URL,"rmi://192.168.107.67:1099/"); // // Context context = new InitialContext(properties); Context context = new InitialContext(); QueueConnectionFactory queueConnFactory = (QueueConnectionFactory)context.lookup("JmsQueueConnectionFactory"); QueueConnection queueConn = queueConnFactory.createQueueConnection(); QueueSession queueSession = queueConn.createQueueSession(false,Session.AUTO_ACKNOWLEDGE); //Queue queue = (Queue)context.lookup("queue1"); Queue queue =queueSession.createQueue("test"); QueueSender queueSender = queueSession.createSender(queue); TextMessage message = queueSession.createTextMessage(); message.setText("hello, this is open JMS"); queueSender.send(message); context.close(); queueConn.close(); } }
消息接受者代码
import java.util.Hashtable; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueConnectionFactory; import javax.jms.QueueReceiver; import javax.jms.QueueSender; import javax.jms.QueueSession; import javax.jms.Session; import javax.jms.TextMessage; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class QueueRecive { public static void main(String[] args) throws NamingException, JMSException { // Hashtable properties = new Hashtable(); // // properties.put(Context.INITIAL_CONTEXT_FACTORY,"org.exolab.jms.jndi.InitialContextFactory"); // // properties.put(Context.PROVIDER_URL,"rmi://192.168.107.67:1099/"); // // Context context = new InitialContext(properties); Context context = new InitialContext(); QueueConnectionFactory queueConnFactory = (QueueConnectionFactory)context.lookup("JmsQueueConnectionFactory"); QueueConnection queueConn = queueConnFactory.createQueueConnection(); queueConn.start(); QueueSession queueSession = queueConn.createQueueSession(false,Session.AUTO_ACKNOWLEDGE); //Queue queue = (Queue)context.lookup("test"); Queue queue = (Queue)queueSession.createQueue("test"); QueueReceiver queueReceiver = queueSession.createReceiver(queue); Message message = queueReceiver.receive(); System.out.println(message.getJMSType()); if(message instanceof TextMessage){ System.out.println(((TextMessage)message).getText()); } context.close(); queueConn.close(); } }
说明
我们在获取QueueConnectionFactory时是使用JNDI方式,需要说明两点:
- 获取方式:我们通过设置property或者通过设置jndi.properties文件来初始化Context
如果使用jndi.properties,大家可以将%OPENJMS_HOME%/config目录下得jndi文件拷贝到自己程序的src下面,并将provider.url路径改掉。
- 路径设置:我们初始化Context时需要制定provider.url。这个URL从那边来。大家可以看java开源消息服务(OpenJMS&ActiveMQ)-OpenJMS学习笔记二中启动OpenJMS时,弹出来的启动窗口有如下信息。
我们JNDI service accepting connection on:****后面的url就是我们JNDI获取的PROVIDER_URL。比如我这变有两个分别是tcp和rmi开头的。大家随便选择一个使用。我在后面会具体描述有什么不一样。置于启动时,其他的Server acceptiing,Admin service我们也在后面研究。
置于代码下面发送和接受信息的代码都是标准的JMS操作。我们这边就不描述了。如果不清楚,大家自己去找资料。
发布订阅模式
发布信息
import java.util.Date;
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class TopicPublish {
/**
* @param args
* @throws NamingException
* @throws JMSException
*/
public static void main(String[] args) throws NamingException, JMSException {
// TODO Auto-generated method stub
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.exolab.jms.jndi.InitialContextFactory");
properties.put(Context.PROVIDER_URL, "rmi://192.168.107.67:1099/");
Context context = new InitialContext(properties);
TopicConnectionFactory factory = (TopicConnectionFactory) context
.lookup("JmsTopicConnectionFactory");
TopicConnection topicConn = factory.createTopicConnection();
topicConn.start();
TopicSession session = topicConn.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic1 = (Topic) context.lookup("topic1");
TopicPublisher publisher = session.createPublisher(topic1);
for(int i = 0 ;i<2;i++){
TextMessage message = session.createTextMessage("test topic:"+i+":"+new Date());
publisher.publish(message);
}
context.close();
topicConn.close();
}
}
订阅者代码(阻塞-同步模式)
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class TopicSubscribeSync {
/**
* @param args
* @throws NamingException
* @throws JMSException
*/
public static void main(String[] args) throws NamingException, JMSException {
// TODO Auto-generated method stub
System.out.println("订阅开始");
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.exolab.jms.jndi.InitialContextFactory");
properties.put(Context.PROVIDER_URL, "rmi://192.168.107.67:1099/");
Context context = new InitialContext(properties);
TopicConnectionFactory factory = (TopicConnectionFactory) context
.lookup("JmsTopicConnectionFactory");
TopicConnection topicConn = factory.createTopicConnection();
topicConn.setClientID("testDurable");
topicConn.start();
TopicSession session = topicConn.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic1 = (Topic) context.lookup("topic1");
//TopicSubscriber subscriber = session.createSubscriber(topic1);
TopicSubscriber subscriber = session.createDurableSubscriber(topic1,"sub1");
Message message = null;
while((message = subscriber.receive())!= null){
if(message instanceof TextMessage){
System.out.println(((TextMessage)message).getText());
}
}
subscriber.close();
context.close();
topicConn.close();
System.out.println("订阅结束");
}
}
订阅者代码(非阻塞-异步模式)
import java.util.Hashtable;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class TopicSubscribeAsync implements MessageListener{
/**
* @param args
* @throws NamingException
* @throws JMSException
*/
public static void main(String[] args) throws NamingException, JMSException {
// TODO Auto-generated method stub
new TopicSubscribeAsync().subscribe();
}
public void subscribe() throws NamingException, JMSException{
System.out.println("异步订阅开始");
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.exolab.jms.jndi.InitialContextFactory");
properties.put(Context.PROVIDER_URL, "rmi://192.168.107.67:1099/");
Context context = new InitialContext(properties);
TopicConnectionFactory factory = (TopicConnectionFactory) context
.lookup("JmsTopicConnectionFactory");
TopicConnection topicConn = factory.createTopicConnection();
topicConn.start();
TopicSession session = topicConn.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic1 = (Topic) context.lookup("topic1");
TopicSubscriber subscriber = session.createSubscriber(topic1);
subscriber.setMessageListener(this);
// context.close();
//
// topicConn.close();
System.out.println("订阅结束");
}
@Override
public void onMessage(Message message) {
// TODO Auto-generated method stub
System.out.println("receive a message");
if(message instanceof TextMessage){
try {
System.out.println(((TextMessage)message).getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
说明1
OpenJMS消息订阅者有两种方式:1.持久订阅 2.非持久订阅。概念上面已经描述。具体的代码时间就是上面的两个订阅代码。
第一个订阅代码,我通过createDurableSubscriber方法来指定了主题为:topic1,订户为:sub1。所以我们这个订阅者是一个持久订阅
第二个订阅代码,我通过createSubscriber方法指定了主题为:topci1。所以这个订阅者是非持久订阅。
说明2
消息接受处理分异步和同步两种方式。同步的话,直接subscriber.receive()代码,阻塞等待消息。异步的话,通过注册subscriber.setMessageListener(this);设置message监听器来处理消息。
说明3
Queue queue =queueSession.createQueue("test");上面queue1是openjms默认创建的一个队列,后面这个,是我们临时指定的对列名。
topicConn.setClientID("1");
topicConn.start();
TopicSession session = topicConn.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic1 = (Topic) session.createTopic("test");
//TopicSubscriber subscriber = session.createSubscriber(topic1);
TopicSubscriber subscriber = session.createDurableSubscriber(topic1,"1");
说明4