消息是不同应用程序之间,或同一个应用程序的不同组件之间的通信方法,当一个应用程序或一个组件将消息发送到指定的消息目的之后,该消息可以被一个或多个组件读取并处理。
消息生产者将消息发送到消息服务器,消息服务器使用消息队列来保存消息,而消息消费者则通过消息队列来依次读取每条消息,这就是典型的PTP(Peer To Peer,点对点)模型。
对Pub-Sub(Publish/Subscribe,发布/订阅)模型而言,消息生产者将消息发送消息服务器的指定主题,而消息服务器则将该消息转发到订阅该主题的每个消息消费者。
JMS系统中大致包含如下基本对象
1)连接工厂:有服务器管理员创建,并绑定到JNDI树上,JMS客户端使用JNDI查找、定位连接工厂,然后利用连接工厂创建JMS连接。
2)JMS连接:表示客户机与服务器之间的活动连接。通过连接工厂创建连接。通常,每个客户机使用单独的连接,而每个连接则可以连接多个JMS目的。
3)JMS会话:表示客户机与JMS服务器之间的通信状态。JMS会话建立在连接之上,表示JMS客户机与服务器之间的通信线程。会话定义了消息的顺序。JMS使用会话进行事务性的消息处理。
4)JMS消息目的:即消息生产者发送消息的目的地,也是消息消费者获取消息的消息源。
5)消息生产者:负责创建消息并将消息发送到消息目的。
6)消息消费者:负责接收消息并读取消息内容。
JMS主要有两个版本1.0.2和1.1。JMS1.0.2的API为每个消息模型提供了两种不同的类体系,JMS1.1则使用统一的域模型,从而减少两种模型之间的差别,避免客户端代码的差别。
不管是使用PTP还是Pub-Sub,发送的步骤可以归纳如下
1)通过JNDI查找来获取JMS连接工厂
2)JMS连接工厂创建JMS连接
3)JMS连接创建JMS消息会话
4)JMS消息会话创建消息生产者
5)JMS消息会话创建空的JMS消息
6)JMS消息调用自身的方法填充内容
7)通过JNDI查找获取JMS消息目的
8)消息生产者将消息发送到指定的JMS消息目的
9)关闭JMS资源
public void sendMessage() throws NamingException, JMSException {
final String CONNECTION_FACTORY_JNDI = "ConnectionFactory";
Context ctx = getInitialContext();
ConnectionFactory connFactory = (ConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI);
Destination dest = (Destination) ctx.lookup("MessageQueue");
Connection conn = connFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer sender = session.createProducer(dest);
sender.setDeliveryMode(DeliveryMode.PERSISTENT);
sender.setTimeToLive(20000);
TextMessage msg = session.createTextMessage();
msg.setText("Hello");
sender.send(msg);
msg.setText("Welcome to JMS");
sender.send(msg);
session.close();
conn.close();
}
private Context getInitialContext() {
final String INIT_FACTORY = "org.jnp.interfaces.NamingContextFactory";
final String SERVER_URL = "jnp://localhost:1099";
Context ctx = null;
try {
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY, INIT_FACTORY);
prop.put(Context.PROVIDER_URL, SERVER_URL);
ctx = new InitialContext(prop);
} catch (NamingException e) {
e.printStackTrace();
}
return ctx;
}
public static void main(String[] args) throws Exception {
MessageSender mp = new MessageSender();
mp.sendMessage();
}
上面的代码中创建Session时调用了Connection的如下方法
Session createSession(boolean transacted,int acknowledgeMode):该方法的前一个参数表明创建的会话是否具有事务性,后一个参数是消息的确认方式。
消息的确认是指当消息接受者收到消息,并做出了对应处理之后,他将回送一个确认消息。对于非事务性会话,创建会话时应该指定确认方式。JMS定义了3中确认方式
AUTO_ACKNOWLEDGE:对于同步消费者,Receive方法调用返回且没有异常发生时,将自动对收到的消息予以确认。对于异步消息,当onMessage方法放回且没有异常发生时,即收到的消息自动确认。
CLIENT_ACKNOWLEDGE:要求客户端使用javax.jms.Message.acknowledge()方法完成确认。
DUPS_OK_ACKNOWLEDGE:允许JMS不必急于确认收到的消息,允许在收到多个消息之后一次完成确认。与AUTO_ACKNOWLEDGE相比,这种确认方式在某些情况下可能更有效,因为没有确认,当系统崩溃或网络出现故障时,消息可以被重新传递。
PTP消息的同步接收,步骤如下
1)通过JNDI查找来获取JMS连接工厂
2)通过JMS连接工厂创建JMS连接工厂
3)JMS连接创建JMS会话
4)通过JNDI查找获取JMS消息目的
5)JMS会话根据指定的JMS消息目的来创建一个JMS消息消费者
6)JMS消费者接收消息
7)关闭JMS资源
在同步接收策略中,JMS消费者调用receive()方法从消息目的视图获取消息,有两个重载版本
Message receive():读取下一条JMS消息,该方法将会一直阻塞线程
Message receive(long timeout):读取下一条JMS消息,如果没有读到消息,该方法将会阻塞timeout毫秒,如果经过timeout毫秒依然没有读到,将会返回null
Message receiveNoWait():该方法不会阻塞线程,他尝试从消息队列读取消息,如果消息队列中有可用的消息,该方法返回读到的消息,否则返回null
public void receiveMessage() throws JMSException, NamingException {
final String CONNECTION_FACTORY_JNDI = "";
Context ctx = getInitialContext();
ConnectionFactory connFactory = (ConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI);
Destination dest = (Destination) ctx.lookup("MessageQueue");
Connection conn = connFactory.createConnection();
conn.start();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer receiver = session.createConsumer(dest);
TextMessage msg = (TextMessage) receiver.receive();
System.out.println(msg);
session.close();
conn.close();
}
PTP 消息的异步接收
类似于AWT的事件编程,JMS消息消费者并不主动调用receive()方法去接收消息,而是采用一种监听器的机制来监听消息目的,当有消息抵达消息目的时,JMS消费者将自动触发他锁对应的监听器的监听方法。
JMS为消息的异步接收提供了MessageListener接口,这是一个标准的事件监听接口,实现该接口的类必须实现如下方法
public void onMessage(Message m):当JMS消息目的有消息送达时,JMS消息监听器的该监听方法将被触发。
public AsyncConsumer() throws JMSException, NamingException, InterruptedException {
final String CONNECTION_FACTORY_JNDI = "";
Context ctx = getInitialContext();
ConnectionFactory connFactory = (ConnectionFactory) ctx.lookup(CONNECTION_FACTORY_JNDI);
Destination dest = (Destination) ctx.lookup("MessageQueue");
Connection conn = connFactory.createConnection();
conn.start();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer receiver = session.createConsumer(dest);
receiver.setMessageListener(this);
Thread.sleep(20000);
session.close();
conn.close();
}
@Override
public void onMessage(Message m) {
TextMessage msg = (TextMessage) m;
System.out.println(msg);
try {
System.out.println(msg.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
AsyncConsumer consumer = new AsyncConsumer();
}
对于Pub-Sub消息模型而言,当多个消息消费者同时订阅某个主题时,只要有一个消息生产者向该主题发布一条消息,每个消息消费者都可以收到一个消息的副本;而对于PTP消息模型来说,当消息生产者向消息队列发送一条消息之后,只有一个消息消费者可以接收到该消息。
对于Pub-Sub消息模型而言,当消息生产者将某个消息发布到指定主题时,即使某个消息消费者订阅了该主题,但如果该消息抵达消息主题时该消息消费者处于离线状态,他将无法收到该消息;而对于PTP消息模型来说,当消息生产者向消息队列发送一条消息时,即使消息消费者处于离线状态,只要该消息还处于有效期内,该消息消费者总可以从消息队列中提取到该消息。
可靠的JMS订阅
使用这种可靠的订阅模式,客户端必须提供一个唯一的标识符,可以保证离线的消费者也能接收到消息。
Connection conn=connFactory.createConnection();
//设置客户端ID
conn.setClientID(“kingdz”);
//创建可靠的消息订阅者
TopicSubscriber receiver=session.createDurableSubscriber(dest,”kingdz”);
当一个可靠的消息订阅者处于离线状态时,JMS服务器必须保存需要送达该主题的所有消息,才能保证可靠的消息订阅者不会错过任何消息。这将强制JMS服务器必须保存这些消息的副本,知道所有的可靠的消息订阅者都收到这些消息为止。