1 JMS概念
JMS是Java Message Service Java消息服务的简称,是Sun公司制定的J2EE规范之一,JMS是一个规范,各个厂商提供了不同的实现,类似于JPA,Hibernate和TopLink等提供了JPA实现,很多产品都实现了JMS规范,如IBM的MQSeries、Weblogic、JBoss还有开源的activemq等等。查看JMS源码您就会发现,里面基本都是接口,只有少数几个实现类,也就是定义了API,这样只要基于JMS API编写代码,就可以不修改代码在不同的JMS实现之间移植。
JMS规范定义了如何描述信息(即消息的格式,后面介绍),以及如何传递信息(即点对点和发布订阅两种模型,后面介绍)。
2 应用场景
1)消息收发。分为3种角色,消息生产者、JMS服务器、消息消费者。消息生产者将消息发送到JMS服务器,消息消费者到JMS服务器中接收消息。消息生产者和消息的消费者是解耦的,两者互相不知道对方的存在,是松散耦合的。另外同多线程的生产者和消费者模式一样,JMS服务器相当于其中的内存缓冲区,有助于缓解生产者和消费者之间的速度差异,可以消除系统峰值的压力。JMS一般都不是用来整合一个系统,而是多个参与消息驱动的系统。
2)异步处理。如果业务应用场景允许异步(比如购买后发送一个确认的email),消息消费者可能会执行较长时间,则可以采用异步方式,减少系统阻塞,提高系统的吞吐量。
3 JMS类图
图中可以看到基本都是接口,核心接口是ConnectionFactory,Connection,Session,Message,Destination, MessageProducer和MessageConsumer,另外
Queue 和 Topic 继承自 Destination ,QueueReceiver 继承自MessageConsumer, QueueSender继承自MessageProducer ,QueueSession继承自Session,Session提供了方法 createProducer(Destination)和createConsumer(Destination) ,QueueSession增加了方法createSender(Queue)和createReceiver(Queue)。
4 消息格式
(消息头 属性 消息体的类型)
JMS消息分为3部分:
1)消息头(header) 。消息的元数据。例如JMSMessageID(消息的唯一标识),JMSPriority(优先级), JMSExpiration(期限)等,可通过下图设置。
2)属性(properties) 。自定义的附加信息。包括 :a. 应用需要用到的属性; b. 消息头中原有的一些可选属性; c. JMS Provider 需要用到的属性。 例如下图中设置应用用到的属性。
可从消息消费者的输出中看到生产者传递的properties。
3)消息体(body)。具体要传递的消息内容。
Message支持的类型有:
BytesMessage --字节流
MapMessage --键值对集合
ObjectMessage --可序列化的对象
TextMessage --文本对象,String类型的串
StreamMessage --Java输入输出流
类图中能看到这些Message。
5 发布订阅模型 vs.点对点模型
5.1 点对点模型(P2P)
每个消息只发给一个消息消费者,例如A给B发送QQ消息,对应的类是Queue。
1)一个消息对应一个消息消费者。
2)消息消费者必须确认对消息的接收,否则认为消息没有被接收。
5.2 发布订阅模型(Pub/Sub)
消息发送给所有的订阅者,类似于QQ中的群,群中所有的人都可以收到消息,对应的类是Topic。
1)一个消息对应多个消息消费者,只要订阅者都可以接收消息。 类似于观察者模式,只要是注册了的观察者都可以收到Subject的通知。
2)只能接收订阅后的消息。
6实例
6.1 简单示例
1) 准备工作:下载activiemq-5.4.1。启动activemq。浏览器中输入http://localhost:8161/admin/,
创建一个Queue。
2)代码:
Producer,生产者。
public class Producer {
public static void main(String[] args) {
Connection connection = null;
try {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
"tcp://localhost:61616");
connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.TRUE,Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("myQueue");
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage("activemq 发送的消息");
// message.setBooleanProperty("booleanddd",true);
producer.send(message);
session.commit();
session.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != connection)
connection.close();
} catch (Throwable ignore) {
}
}
}
}
Consumer,消费者。
public class Consumer {
public static void main(String[] args) {
Connection connection = null;
try {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD,
"tcp://localhost:61616");
connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.FALSE,
Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("myQueue");
MessageConsumer consumer = session.createConsumer(destination);
while (true) {//如果没有这行,收到消息后就执行完了,只能接收一条消息
TextMessage message = (TextMessage) consumer.receive();//接收到消息之前,一直被阻塞,等待啊等待.. ,等你一万年
if (null != message) {
System.out.println("message:" + message);
System.out.println("收到的消息内容:" + message.getText());
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != connection)
connection.close();
} catch (Throwable ignore) {
}
}
}
}
3)JMS处理主线流程。
所有的流程基本上都一样,无需区分topic还是queue,JMS服务器自动处理差异性。
a.获取连接工厂ConnectionFactory。
b.从连接工厂中获取Connection,并启动。
c.从Connection中获取Session
d.从Session中创建Destination
e.从Session中根据Destination创建MessageProducer
f.从Session中创建Message
g.由MessageProducer发送Message
h.关闭资源
6.2 异步消息
上面代码中的consumer.receive()会一直阻塞,如果希望异步接收消息,需要类实现MessageListener,然后实现onMessage(Message message)方法。
代码片段如下,详细参见附件中的AsynchronousReceiver.java类。
public void onMessage(Message message){
try {
if(message instanceof TextMessage){
TextMessage text = (TextMessage) message;
System.out.println("message:" + text);
System.out.println("收到的消息内容:" + text.getText());
}
if(message instanceof ObjectMessage){
ObjectMessage objectMessage = (ObjectMessage)message;
System.out.println("message:" + objectMessage);
JMSObject obj = (JMSObject)objectMessage.getObject();
System.out.println("收到的对象消息:" + obj);
}
} catch (JMSException e) {
e.printStackTrace();
}
}
6.3 处理返回值代码
如果生产者需要处理消费者的返回值,则需要使用QueueRequestor,
消费者端代码片段。
if(message instanceof ObjectMessage){
ObjectMessage obj = (ObjectMessage) message;
System.out.println("message:" + obj);
System.out.println("收到的消息内容:" + obj.getObject());
Queue replyQueue = (Queue) message.getJMSReplyTo();
MapMessage replyMsg = session.createMapMessage();
replyMsg.setBoolean("result",true);
//设置业务编号
// replyMsg.setJMSCorrelationID(message.getJMSCorrelationID());
QueueSender sender = session.createSender(replyQueue);
sender.send(replyMsg);
}
完整代码参见附件中的Requestor和Replyor类。
7 持久化
如果生产者发送消息的时候,或者消费者接收消息的时候JMS服务器down掉了,而应用又不希望消息被搞丢了怎么办?这就需要用到持久化设置了。
在P2P模型中,如果是非持久化的,消息最多被发送1次,如果是持久化的则消息发且仅发送一次。
Queue默认是持久化的,Topic默认是非持久化的,因为持久化要消耗一定的性能。
代码示例:
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
8 事务
如果需要控制在同一个事务中,则需要:
设置为TRUE,然后通过commit()统一发送,要么全部成功,要么全部失败。
QueueSession session = connection.createQueueSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
其他代码。。。。
session.commit();
如果其中既有发送消息,又有数据库操作,则需要使用JTA事务。
9 Selector
如果多个生产者和多个消费者,消费者想有选择的处理消息,则可以使用JMS的selector机制。
生产者端代码:
objMsg.setStringProperty("classname",classname);
消费者端代码:
String selector = "classname"+"='classname'";
QueueReceiver receiver = session.createReceiver(msgQueue,selector);
或者我只处理紧急消息,则可以:
QueueReceiver receiver = session.createReceiver(msgQueue," JMSPriority > 4");
10 MDB
MDB是Message Driven Bean,它适用于服务器端执行时间长,而且客户端不需要处理服务器端返回值的场景。它非常类似于JMS 消费端中的异步消息方式,也需要实现MessageListener,然后实现onMessage(Message message)方法。主要区别是它是容器启动时负责创建的实例,执行完毕后实例回收到容器管理的对象池中。