JMS通信模型:
1、 P2P:一个消息只能被一个接收者接收
2、 Publisher/Subscriber:一个消息允许有多个接收者
JMS重要概念:
概念 | 说明 |
Destination | 消息发送的目的地,即Queue或者Topic |
Message | 消息有以下类型 |
Stream | Java数据流消息,用标准流操作来顺序的填充和读取 |
MapStream | 一个Map类型的消息,名称为String类型,值为Java的基本类型 |
TextStream | 普通字符串消息 |
ObjectStream | 对象消息,包含一个科序列化的Java对象 |
BytesStream | 二进制数组消息,包含一个byte[] |
XMLStream | XML类型的消息 |
| 最常用的是TextStream和ObjectStream |
Session | 与JMS提供者建立的会话,通过Session才能建立一个Message |
Connection | 与JMS提供者建立的会话,通过其才能建立一个Session |
ConnectionFactory | 通过工厂类才能建立Connection |
Producer | 消息的生产者,要发送一个消息,必须通过这个生产者来发送 |
Consumer | 消费者,负责接收消息 |
它们之间的管理
ConnectionFactory---->Connection--->Session--->Message
Destination +Session------------------------------------>Producer
Destination +Session------------------------------------>Consumer
一个简单JMS例子
import javax.jms.Connection; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQQueue;
public class MessageSendAndReceive {
public static void main(String[] args) throws Exception { ConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");
Connection connection = factory.createConnection(); connection.start();
Queue queue = new ActiveMQQueue("testQueue");
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Message message = session.createTextMessage("Hello JMS!");
MessageProducer producer = session.createProducer(queue); producer.send(message);
System.out.println("Send Message Completed!");
MessageConsumer comsumer = session.createConsumer(queue); Message recvMessage = comsumer.receive(); System.out.println(((TextMessage)recvMessage).getText()); }
} |
MessageListener
消费者接收消息的两种方式:
1、 consumer.receive() 或 consumer.receive(int timeout);消费者会阻塞,直到有消息达到或者超时
2、 注册MessageListener;消费者无需等待,可以干自己的事情,当有消息到达时,会调用回调函数onMessage()方法;
注册方法:
MessageConsumer comsumer = session.createConsumer(queue); comsumer.setMessageListener(new MessageListener(){ @Override public void onMessage(Message m) { TextMessage textMsg = (TextMessage) m; try { System.out.println(textMsg.getText()); } catch (JMSException e) { e.printStackTrace(); } }
}); |
Queue
当有多个消费者在监听同一个Queue时,这些消息者按照先后顺序依次处理队列中的消息。如下例:
import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQQueue;
public class QueueTest {
public static void main(String[] args) throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");
Connection connection = factory.createConnection(); connection.start();
//创建一个Queue Queue queue = new ActiveMQQueue("testQueue");
//创建一个Session Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//注册消费者1 MessageConsumer comsumer1 = session.createConsumer(queue); comsumer1.setMessageListener(new MessageListener(){ public void onMessage(Message m) { try { System.out.println("Consumer1 get " + ((TextMessage)m).getText()); } catch (JMSException e) { e.printStackTrace(); } } });
//注册消费者2 MessageConsumer comsumer2 = session.createConsumer(queue); comsumer2.setMessageListener(new MessageListener(){ public void onMessage(Message m) { try { System.out.println("Consumer2 get " + ((TextMessage)m).getText()); } catch (JMSException e) { e.printStackTrace(); } }
});
//创建一个生产者,然后发送多个消息。 MessageProducer producer = session.createProducer(queue); for(int i=0; i<10; i++){ producer.send(session.createTextMessage("Message:" + i)); } }
}
运行这个例子会得到下面的输出结果: Consumer1 get Message:0 Consumer2 get Message:1 Consumer1 get Message:2 Consumer2 get Message:3 Consumer1 get Message:4 Consumer2 get Message:5 Consumer1 get Message:6 Consumer2 get Message:7 Consumer1 get Message:8 Consumer2 get Message:9
可以看出每个消息直被消费了一次,但是如果有多个消费者同时监听一个Queue的话,无法确定一个消息最终会被哪一个消费者消费。 |
Topic
与Queue不同的是,Topic实现的是发布/订阅模型,当发布一条消息时,所有已经订阅了该Topic的订阅者都将收到该信息,将上面例子修改成Topic。
import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQTopic;
public class TopicTest {
public static void main(String[] args) throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost"); Connection connection = factory.createConnection(); connection.start(); //创建一个Topic Topic topic= new ActiveMQTopic("testTopic"); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); //注册消费者1 MessageConsumer comsumer1 = session.createConsumer(topic); comsumer1.setMessageListener(new MessageListener(){ public void onMessage(Message m) { try { System.out.println("Consumer1 get " + ((TextMessage)m).getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //注册消费者2 MessageConsumer comsumer2 = session.createConsumer(topic); comsumer2.setMessageListener(new MessageListener(){ public void onMessage(Message m) { try { System.out.println("Consumer2 get " + ((TextMessage)m).getText()); } catch (JMSException e) { e.printStackTrace(); } } }); //创建一个生产者,然后发送多个消息。 MessageProducer producer = session.createProducer(topic); for(int i=0; i<10; i++){ producer.send(session.createTextMessage("Message:" + i)); } }
} 运行后得到下面的输出结果: Consumer1 get Message:0 Consumer2 get Message:0 Consumer1 get Message:1 Consumer2 get Message:1 Consumer1 get Message:2 Consumer2 get Message:2 Consumer1 get Message:3 Consumer2 get Message:3 Consumer1 get Message:4 Consumer2 get Message:4 Consumer1 get Message:5 Consumer2 get Message:5 Consumer1 get Message:6 Consumer2 get Message:6 Consumer1 get Message:7 Consumer2 get Message:7 Consumer1 get Message:8 Consumer2 get Message:8 Consumer1 get Message:9 Consumer2 get Message:9
说明每一个消息都会被所有的消费者消费。 |
一个消息对象由三个部分组成:消息头(Headers),属性(Properties),消息体(Payload)
消息头:
消息头 | 说明 |
JMSDestination | 消息的目的地,Topic或者是Queue |
JMSDeliveryMode | 消息的发送模式: persistent: JMS提供者会保持该信息直到该消息被消费者接收,即使其服务关闭,该信息也会保存 nonpersistent:不保存消息,如果服务关闭,消息会被丢失。 可以通过下面的方式设置: Producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT/ PERSISTENT); |
JMSTimestamp | 当调用send()方法的时候,JMSTimestamp会被自动设置为当前时间戳,可以通过下面方式得到这个值: long timestamp = message.getJMSTimestamp(); |
JMSExpiration | 表示一个消息的有效期。只有在这个有效期内,消息消费者才可以消费这个消息。默认值为0,表示消息永不过期。 可以通过下面的方式设置: producer.setTimeToLive(3600000); //有效期1小时 (1000毫秒 * 60秒 * 60分) |
JMSPriority | 消息的优先级:0-4为正常的优先级,5-9为高优先级。 可以通过下面方式设置:producer.setPriority(9); |
JMSMessageID | 一个字符串用来唯一标示一个消息。 |
JMSReplyTo | 有时消息生产者希望消费者回复一个消息,JMSReplyTo为一个Destination,表示需要回复的目的地。当然消费者可以不理会它。 message.setJMSReplyTo(Destination) |
JMSCorrelationID | 通常用来关联多个Message。例如需要回复一个消息,可以把JMSCorrelationID设置为所收到的消息的JMSMessageID。 |
JMSType | 表示消息体的结构,和JMS提供者有关。 |
JMSRedelivered | 如果这个值为true,表示消息是被重新发送了。因为有时消费者没有确认他已经收到消息或者JMS提供者不确定消费者是否已经收到。
|
除了这些Header,消息发送者可以自定义添加一些属性(Properties)。
方法:
TextMessage message = session.createTextMessage("Message payload"); message.setStringProperty("key", “value”); |
Selector
在JMS中默认创建一个消息消费者的method: sesssion.createConsumer(destination),该消费者将接收destination中的所有消息。
如果想创建一个只关注特定消息的消费者可以使用method:sesssion.createConsumer(destination,selector)
参数:selector是字符串,格式为“key='value'”,key和value可以用户自己定义。该字段用于匹配消息头中的属性。
通过selector可以让特定消费者consumer只接收包含该属性的消息。
例子:
注意:该例子在MyEclipse2015+JDK1.7+ActiveMQ5.11.1中验证通过。
package example; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage;
import org.apache.qpid.amqp_1_0.jms.impl.ConnectionFactoryImpl; import org.apache.qpid.amqp_1_0.jms.impl.QueueImpl; import org.apache.qpid.amqp_1_0.jms.impl.TopicImpl;
public class JMSSelectorTest {
public static void main(String[] args) throws Exception { String user = env("ACTIVEMQ_USER", "admin"); String password = env("ACTIVEMQ_PASSWORD", "password"); String host = env("ACTIVEMQ_HOST", "localhost"); int port = Integer.parseInt(env("ACTIVEMQ_PORT", "5672")); String destination = arg(args, 0, "queue://JMSSelectorTestQueue");
ConnectionFactoryImpl factory = new ConnectionFactoryImpl(host, port, user, password); Destination dest = null; if( destination.startsWith("topic://") ) { dest = new TopicImpl(destination); } else { dest = new QueueImpl(destination); }
Connection connection = factory.createConnection(user, password); connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer comsumerA = session.createConsumer(dest, "receiver = 'A'"); comsumerA.setMessageListener(new MessageListener() { public void onMessage(Message m) { try { System.out.println("ConsumerA get " + ((TextMessage) m).getText()); } catch (JMSException e1) { } } }); MessageConsumer comsumerB = session.createConsumer(dest, "receiver = 'B'"); comsumerB.setMessageListener(new MessageListener() { public void onMessage(Message m) { try { System.out.println("ConsumerB get " + ((TextMessage) m).getText()); } catch (JMSException e) { } } }); MessageProducer producer = session.createProducer(dest); for (int i = 0; i < 10; i++) { String receiver = (i % 3 == 0 ? "A" : "B"); TextMessage message = session.createTextMessage("Message" + i + ", receiver:" + receiver); message.setStringProperty("receiver", receiver); producer.send(message); } }
private static String arg(String[] args, int i, String string) { // TODO Auto-generated method stub return string; }
private static String env(String string, String string2) { // TODO Auto-generated method stub return string2; } }
|
运行程序,console中打印信息:
ConsumerA get Message0, receiver:A
ConsumerB get Message1, receiver:B
ConsumerB get Message2, receiver:B
ConsumerA get Message3, receiver:A
ConsumerB get Message4, receiver:B
ConsumerB get Message5, receiver:B
ConsumerA get Message6, receiver:A
ConsumerB get Message7, receiver:B
ConsumerB get Message8, receiver:B
ConsumerA get Message9, receiver:A
ConsumerA get Message0, receiver:A
ConsumerB get Message1, receiver:B
ConsumerB get Message4, receiver:B
ConsumerA get Message6, receiver:A
ConsumerB get Message7, receiver:B
--TemporaryQueue和TemporaryTopic
TemporaryQueue和TemporaryTopic,从字面上就可以看出它们是“临时”的目的地。可以通过Session来创建,例如:
TemporaryQueue replyQueue = session.createTemporaryQueue();
虽然它们是由Session来创建的,但是它们的生命周期确实整个Connection。如果在一个Connection上创建了两个Session,则一个Session创建的TemporaryQueue或TemporaryTopic也可以被另一个Session访问。那如果这两个Session是由不同的Connection创建,则一个Session创建的TemporaryQueue不可以被另一个Session访问。
另外,它们的主要作用就是用来指定回复目的地, 即作为JMSReplyTo。
在下面的例子中,先创建一个Connection,然后创建两个Session,其中一个Session创建了一个TemporaryQueue,另一个Session在这个TemporaryQueue上读取消息。
- import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
public class TemporaryQueueTest {
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("vm://localhost");
Connection connection = factory.createConnection();
connection.start();
Queue queue = new ActiveMQQueue("testQueue2");
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 使用session创建一个TemporaryQueue。
TemporaryQueue replyQueue = session.createTemporaryQueue();
TemporaryQueue replyQueue1 = session.createTemporaryQueue();
System.out.println("replyQueue="+replyQueue+", replyQueue1="+replyQueue1);
// 接收消息,并回复到指定的Queue中(即replyQueue)
MessageConsumer comsumer = session.createConsumer(queue);
comsumer.setMessageListener(new MessageListener() {
public void onMessage(Message m) {
try {
System.out.println("getJMSDeliveryMode: "+((TextMessage) m).getJMSDeliveryMode());
System.out.println("getJMSMessageID: "+((TextMessage) m).getJMSMessageID());
System.out.println("getJMSTimestamp: "+((TextMessage) m).getJMSTimestamp());
System.out.println("getJMSDestination: "+((TextMessage) m).getJMSDestination());
System.out.println("getJMSRedelivered: "+((TextMessage) m).getJMSRedelivered());
System.out.println("getJMSReplyTo: "+((TextMessage) m).getJMSReplyTo());
System.out.println("getJMSPriority: "+((TextMessage) m).getJMSPriority());
System.out.println("getJMSType: "+((TextMessage) m).getJMSType());
System.out.println("Body: " + ((TextMessage) m).getText());
System.out.println("Get Message: " + ((TextMessage) m).getText());
MessageProducer producer = session.createProducer(m.getJMSReplyTo());
producer.send(session.createTextMessage("ReplyMessage"));
} catch (JMSException e) {
}
}
});
// 使用同一个Connection创建另一个Session,来读取replyQueue上的消息。
// Session session2 = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
// Connection connection2 = factory.createConnection();
Session session2 = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
MessageConsumer replyComsumer = session2.createConsumer(replyQueue);
replyComsumer.setMessageListener(new MessageListener() {
public void onMessage(Message m) {
try {
System.out.println("Get reply: " + ((TextMessage) m).getText());
} catch (JMSException e) {
}
}
});
MessageProducer producer = session.createProducer(queue);
TextMessage message = session.createTextMessage("SimpleMessage");
message.setJMSReplyTo(replyQueue1);
producer.send(message);
}
}
运行结果为:
replyQueue=temp-queue://ID:NCNL0158-51325-1432865404618-3:1:1, replyQueue1=temp-queue://ID:NCNL0158-51325-1432865404618-3:1:2
getJMSDeliveryMode: 2
getJMSMessageID: ID:NCNL0158-51325-1432865404618-3:1:1:1:1
getJMSTimestamp: 1432865404910
getJMSDestination: queue://testQueue2
getJMSRedelivered: false
getJMSReplyTo: temp-queue://ID:NCNL0158-51325-1432865404618-3:1:2
getJMSPriority: 4
getJMSType: null
Body: SimpleMessage
Get Message: SimpleMessage
注:多次创建createTemporaryQueue,仅仅是最后一个数据有差异
如果将:
Session session2 = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
更改为:
Connection connection2 = factory.createConnection();
Session session2 = connection2.createSession(true, Session.AUTO_ACKNOWLEDGE);
就会得到下面的异常:
Exception in thread "main" javax.jms.InvalidDestinationException: Cannot use a Temporary destination from another Connection
at org.apache.activemq.ActiveMQMessageConsumer.<init>(ActiveMQMessageConsumer.java:196)
at org.apache.activemq.ActiveMQSession.createConsumer(ActiveMQSession.java:1239)
at org.apache.activemq.ActiveMQSession.createConsumer(ActiveMQSession.java:1183)
at org.apache.activemq.ActiveMQSession.createConsumer(ActiveMQSession.java:1095)
at org.apache.activemq.ActiveMQSession.createConsumer(ActiveMQSession.java:1067)
at org.com.activemq1.TemporaryQueueTest.main(TemporaryQueueTest.java:52)
注:上面例子在Myeclipse2015+JDK1.7+ActiveMQ5.11.1中验证通过。