JMS概述
JMS为Java程序创建、发送、接收和读取企业消息传递系统提供了一种通用的方式。JMS最初是为已经存在的消息传递产品提供标准的Java API而开发的。从那以后,开发了更多的消息传递产品。JMS为Java客户机应用程序和Java中间层服务提供了使用这些消息传递产品的通用方法。它定义了一些消息传递语义和一组相应的Java接口。
JMS支持的两种消息传递模式
-
点到点(PTP)消息传递允许客户端通过称为队列的中间抽象向另一个客户端发送消息。发送消息的客户机将消息发送到特定的队列。接收消息的客户机从该队列中提取消息。
消息传递允许客户端通过称为主题的中间抽象向多个客户端发送消息。发送消息的客户机将消息发布到特定的主题。然后将消息传递给订阅该主题的所有客户端。
JMS API
JMS API 发展历史
由于历史原因,JMS提供了四套用于发送和接收消息的接口。
- JMS 1.0定义了两个特定于域的api,一个用于点到点消息传递(队列),另一个用于发布/订阅(主题)。尽管由于向后兼容的原因,这些仍然是JMS的一部分,但是应该认为它们已经完全被后面的api所取代。
- JMS 1.1引入了一个新的统一API,它提供了一组单独的接口,可以用于点到点消息传递和发布/订阅消息传递。这在这里被称为经典API。
- JMS 2.0引入了一个简化的API,它提供了经典API的所有特性,但是需要的接口更少,使用起来也更简单。
- 每个API都提供了一组不同的接口,用于连接到JMS提供者以及发送和接收消息。但是,它们都共享一组公共接口,用于表示消息和消息目的地,并提供各种实用程序特性。
各套API共用接口
多个api共有的主要接口如下:
- Message、BytesMessage、MapMessage、ObjectMessage、StreamMessage和TextMessage——从JMS提供者发送或接收的消息。
- Queue ——封装点对点消息传递的消息目的地标识的受管理对象
- Topic -----用于封装发布/订阅消息的消息目的地标识的受管理对象。
- Destination ------- 队列和主题的公共子类型
经典API接口
class API 主要提供的接口如下
- ConnectionFactory——客户端用来创建连接的受管理对象。简化的API也使用这个接口。
- Connection ——到JMS提供者的活动连接
- Session——用于发送和接收消息的单线程上下文
- MessageProducer—由会话创建的对象,用于将消息发送到队列或主题
- MessageConsumer——由会话创建的对象,用于接收发送到队列或主题的消息
简版API接口
简化的API提供了与经典API相同的消息传递功能,但是需要更少的接口,并且使用起来更简单。简化后的API提供的主要接口如下:
- ConnectionFactory——客户端用来创建连接的受管理对象。简化的API也使用这个接口。
- JMSContext - 到JMS提供者的活动连接和用于发送和接收消息的单线程上下文
- JMSProducer 一个由JMSContext创建的对象,用于向队列或主题发送消息
-
JMSConsumer 由JMSContext创建的对象,用于接收发送到队列或主题的消息
在简化的API中,单个JMSContext对象包含经典API中由两个独立对象(一个连接和一个会话)提供的行为。尽管该规范将JMSContext称为具有底层“连接”和“会话”,但简化的API不使用连接和会话接口。
如何使用API
使用classic API的典型JMS客户机执行以下JMS设置过程
- 使用JNDI发现ConnectFactory对象
- 使用JNDI发现一个或者多个Destination 对象
- 使用ConnectFactory 来创建一个JMS 带有被抑制消息传递的连接对象
- 使用Connection 创建一个或者多个连接会话对象。
- 使用Session和Destination 来创建消息生产者和需要的消息消费者
- 告诉连接开启消息传递
与之相对的是 一个典型的JMS使用简化的API做了如下的事情:
- 使用JNDI发现ConnectFactory对象
- 使用JNDI发现一个或者多个Destination 对象
- 使用ConnectFactory对象创建JMSContext 对象
- 使用JMSContext 创建需要的消息生产者和消息消费者
- 传递消息是被自动开始的。
此时,客户机已经具备了生成和使用消息所需的基本JMS设置
多线程并发支持
-
Class API
-
simplified API
消息发送
1、同步发送
在Class API中,MessageProducer上可以使用以下方法来同步发送消息:
- send(Message message)
- send(Message message, int deliveryMode, int priority, long timeToLive)
- send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive)
- send(Destination destination, Message message)
这些方法将阻塞,直到消息被发送。如果需要,调用将阻塞,直到从JMS服务器接收到确认消息为止。
异步发送
在Class API中,可以使用MessageProducer上的以下方法来异步发送消息
- send(Message message, CompletionListener completionListener)
- send(Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener)
- send(Destination destination, Message message, CompletionListener completionListener)
- send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener)
package javax.jms;
/**
* A CompletionListener is implemented by the application and may be specified when a message is sent asynchronously.
* When the sending of the message is complete, the JMS provider notifies the application by calling the onCompletion(Message) method of the specified completion listener. If the sending if the message fails for any reason, and an exception cannot be thrown by the send method, then the JMS provider calls the onException(Exception) method of the specified completion listener.
*/
public interface CompletionListener {
void onCompletion(Message message);
void onException(Message message, Exception exception);
}
异步时消息的顺序:不需担心
如果使用同一个生产者发送多条消息,则必须满足JMS消息顺序要求。这适用于同步和异步发送的组合已经执行。在发送下一条消息之前,应用程序不需要等待异步发送完成。
关闭 提交 或者回滚
CompletionListener回调方法不能在它自己的构造器、session(包括JMSContext)或connect上调用close,也不能在它自己的session上调用commit或rollback。这样做将导致Close、submmit或rollback抛出IllegalStateException或IllegalStateRuntimeException(取决于方法签名)。
发送有过期时间的消息
客户端可以为它发送的每个消息指定一个生存时间值(以毫秒为单位)。这用于确定消息的过期时间,该过期时间是通过将send方法上指定的生存时间值添加到发送消息的时间计算出来的(对于事务发送,这是客户端发送消息的时间,而不是事务提交的时间)。
发送延时消息
客户端可以为它发送的每个消息指定一个以毫秒为单位的传递延迟值。这用于确定消息的传递时间,该时间是通过将send方法上指定的传递延迟值添加到发送消息的时间来计算的(对于已处理的发送,这是客户端发送消息的时间,而不是事务提交的时间)。
发送更优先的消息
priority 指定
设置消息的传递格式
JMS支持两种消息传递模式。
NON_PERSISTENT模式是开销最低的传递模式,因为它不需要将消息记录到稳定的存储中。JMS提供者故障会导致NO_persistent消息丢失。
持久模式指示JMS提供程序格外小心,以确保消息不会在传输过程中由于JMS提供程序故障而丢失。
JMS提供者必须最多一次交付一个NO_persistent消息。这意味着它可能会丢失消息,但一定不能发送两次。
JMS提供者必须一次且仅一次地交付持久消息。这意味着JMS提供者失败一定不能导致它丢失,并且它一定不能两次交付它。持久(一次且仅一次)和非持久(最多一次)消息交付是JMS客户机在交付技术(如果JMS提供者死亡,可能会丢失消息)和交付技术(确保消息能够在这样的失败中存活下来)之间进行选择的一种方式。这种选择通常意味着性能/可靠性的权衡。
当客户端选择非持久交付模式,表示它重视性能而不是可靠性;选择持久性反转请求的权衡。
消息顺序
了解消息顺序。默认是时间先后顺序,明白哪些情况将对顺序产生影响。
- 高优先级的消息可能比先前的低优先级消息快。
- 具有较晚延时时间的消息可以在具有较早延时时间的消息之后传递。
- 由于JMS提供程序故障,客户端可能无法接收到NO_persistent消息。
- 如果将持久消息和非持久消息都发送到目的地,则仅在一致模式下保证顺序。也就是说,较晚的非持久消息可能先于较早的持久消息到达;但是,它永远不会在具有相同优先级的较早的非持久消息之前到达。
- 客户端可以使用事务会话将其发送的消息分组为原子单元(JMS事务的生产者组件)。事务发送到特定目的地的消息的顺序是重要的。跨目的地发送消息的顺序不重要。有关更多信息。
消息接收
- queue 消息接收
- Topic 消息接收
非共享非持久化订阅
非共享非持久订阅是消费来自主题的消息的最简单方法
创建非共享非持久订阅,并在该订阅上创建使用者对象,使用以下方法之一:
- 在Class API 中 调用Session的createConsumer 方法 将会返回一个MessageConsumer 对象
- 在 简化版API 中 调用JMSContext的createConsumer方法 将会返回一个JMSConsumer 对象
每个非共享非持久订阅都有一个消费者。如果应用程序需要在同一订阅上创建多个消费者,则应该使用共享的非持久订阅。
nolocal参数被用来指定提供给Topic的消息的连接不能给消费者
非共享的持久订阅
持久订阅由一个应用程序使用,该应用程序需要接收在某个主题上发布的所有消息,包括在没有与之关联的使用者时发布的消息。JMS提供者保留此持久订阅的记录,并确保来自主题发布者的所有消息都被保留,直到它们被交付到持久订阅上的使用者并被其确认,或者直到它们过期。
一个非共享的持久订阅会同时只有一个活动的消费者。
创建非共享持久订阅,并在该订阅上创建使用者,使用以下方法之一:
- 在Class API中 调用Session的createDurableConsumer 方法 将返回一个MessageConsumer 对象
在简化版API中 调用JMSContext的createDurableConsumer 方法 将返回一个JMSConsumer 对象
必须指定订阅名
MessageConsumer createDurableConsumer(Topic topic, String name) throws JMSException;
未共享的持久订阅由客户端指定的名称和客户端标识符标识,必须对客户端标识符进行设置。客户端随后希望在该非共享持久订阅上创建使用者,必须使用相同的客户端标识符。
取消持久订阅的方式:
未共享的持久订阅将被持久化,并将继续累积消息,直到使用会话、JMSContext或TopicSession上的unsubscribe方法将其删除为止。当客户端有一个活动消费者时,或者当从客户端接收到的消息是当前事务的一部分或在会话中没有得到确认时,删除持久订阅是错误的。
void unsubscribe(String name) throws JMSException;
共享的非持久化订阅
非持久共享订阅由客户端使用,该客户端需要能够在多个使用者之间共享从非持久主题订阅接收消息的工作。因此,非持久共享订阅可能有多个使用者。来自订阅的每个消息将仅交付给该订阅上的一个使用者。
创建一个共享的非持久订阅,并在该订阅上创建一个使用者,使用以下方法之一:
- 在Class API 中 Session的createSharedConsumer 方法将会被调用 这将会返回一个MessageConsumer
- 在简化版的API中JMSContext的createSharedConsumer 方法,会返回一个JMSConsumer 对象
必须指定订阅名(自定义)
MessageConsumer createSharedConsumer(Topic topic, String sharedSubscriptionName) throws JMSException;
共享的持久化订阅
持久订阅由一个应用程序使用,该应用程序需要接收在某个主题上发布的所有消息,包括在没有与之关联的使用者时发布的消息。JMS提供者保留此持久订阅的记录,并确保来自主题发布者的所有消息都被保留,直到它们被交付到持久订阅上的使用者并被其确认,或者直到它们过期。
共享非持久订阅由客户机使用,客户机需要能够在多个使用者之间共享从持久订阅接收消息的工作。因此,共享持久订阅可能有多个使用者。来自订阅的每个消息将仅交付给该订阅上的一个使用者。
创建共享持久订阅,并在该订阅上创建使用者,使用以下方法之一:
在Class API中 使用Session的createSharedDurableConsumer 方法 将会返回一个MessageConsumer 对象
在简化版的API中 使用JMSContext的createSharedDurableConsumer 方法 将会返回一个JMSConsumer 对象。
MessageConsumer createSharedDurableConsumer(Topic topic, String name) throws JMSException;
同步方式接受消息
异步方式接受消息
客户端可以向使用者注册实现JMS MessageListener接口的对象。当消息到达使用者时,提供者通过调用侦听器的onMessage方法来传递消息。侦听器可能抛出一个RuntimeException;但是,这被认为是客户端编程错误。行为良好的侦听器应该捕获此类异常,并尝试将导致这些异常的消息转移到特定于应用程序的“不可处理消息”目的地的某种形式。
侦听器抛出RuntimeException的结果取决于会话的确认模式。
- AUTO_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE - 消息将立即重新发送。JMS提供者在放弃之前重新交付相同消息的次数依赖于提供者。对于在这种情况下重新交付的消息,将设置jmsredeliverycount消息头字段,并增加JMSXDeliveryCount消息属性。
- CLIENT_ACKNOWLEDGE 侦听器的下一条消息被传递。如果客户端希望重新发送之前未确认的消息,则必须手动恢复会话。
- Transacted Session 侦听器的下一条消息被传递。客户机可以提交或回滚会话(换句话说,RuntimeException不会自动回滚会话)。
JMS程序应该使用消息侦听器将抛出RuntimeException的客户端标记为可能发生故障。
一个会话使用一个线程来运行它的所有消息监听器。当线程忙于执行一个侦听器时,所有其他异步传递到会话的消息必须等待。
消息确认
如果会话是事务的,则通过提交自动处理消息确认,通过回滚自动处理恢复。
如果不是事务会话,将会有三种应答选项:
- DUPS_OK_ACKNOWLEDGE 懒得确认收到消息,适用于能容忍重复消息的情况,它 带来的好处是减轻session判断重复消息的工作。
- AUTO_ACKNOWLEDGE Session自动确认
- CLIENT_ACKNOWLEDGE 客户端自己 来回复确认(我们自己来控制回复确认)
会话的恢复方法用于停止会话并使用其第一个未确认的消息重新启动它。实际上,会话传递的一系列消息被重置到最后一条确认消息之后的位置。它现在传递的消息可能与最初传递的消息不同,这些消息可能是由于消息过期、高优先级消息的到来或者由于消息没有达到指定的传递时间而无法传递的消息。
事务处理
事务将会话的输入消息流和输出消息流组织成一系列原子单元。当事务提交时,确认其原子输入单元,并发送其关联的原子输出单元。如果执行了事务回滚,则销毁其生成的消息,并自动恢复其使用的消息。
事务可以使用其会话的commit()或rollback()方法来完成。会话当前事务的完成将自动开始下一个事务。结果是,事务处理的会话始终有一个当前事务,在该事务中完成其工作
JMS消息模型
消息由三部分构成,Header、Body 必需,Properties 非必需
Header 所有消息都支持同一组头字段。头字段包含客户端和提供者用于标识和路由消息的值。
Properties 除了标准的标头字段外,消息还提供了一个内置功能,用于将可选的标头字段添加到消息中。
特定于应用程序的属性——实际上,这为向消息添加特定于应用程序的头字段提供了一种机制
标准属性——JMS定义了一些标准属性,它们实际上是可选的头字段。
特定于提供程序的属性——一些JMS提供程序可能需要使用特定于提供程序的属性。JMS为它们定义了一个命名约定。Body JMS定义了几种类型的消息体,它们涵盖了当前使用的大多数消息传递样式。
消息头字段
设置消息头
在发送消息之前,应用程序可以使用消息对象上的方法来设置JMSCorrelationID、JMSReplyTo和JMSType消息头。
JMSProperties
- properties name
属性名必须遵守消息选择器标识符的规则 - properties value
消息值只支持基本类型
在发送消息之前,客户端应用程序可以使用消息对象上的方法来设置消息属性。
Message Selection
Selection 是消费端选择性消费消息的功能,通过使用消息头、消息属性中的字段来编写一个SQL条件 表达式来说明要消息提供者如何选择性递送消息给消费者。
MessageConsumer createConsumer(Destination destination, String messageSelector)
Message Body
Spring JMS ApI详解
简化同步JMS访问代码的助手类
如果希望使用动态Destination创建,则必须使用“pubSubDomain”属性指定要创建的JMsDestination的类型。对于其他操作,这是不必要的。Point-toPoint(队列)是默认域。
JMS会话的默认设置是“非事务”和“自动确认”。根据Java EE规范的定义,当在活动事务中创建JMS会话时,事务和确认参数将被忽略,无论JTA事务还是spring管理的事务。要为本机JMS使用配置它们,请为“sessionTransacted”和“sessionemode”bean属性指定适当的值。
该模板使用一个org.springframe . js .support.destin . dynamicdestinationresolver和一个org.springframe . js .support.converter。SimpleMessageConverter分别作为解析目标名称或转换消息的默认策略。可以通过“destinationResolver”和“messageConverter”bean属性覆盖这些默认值。
与此模板一起使用的ConnectionFactory应该返回池化的连接(或单个共享连接)以及池化的会话和消息生成器。否则,特别JMS操作的性能将受到影响。
最简单的选择是使用spring提供的org.springframe .jms.connection。SingleConnectionFactory作为目标ConnectionFactory的装饰器,以线程安全的方式重用单个JMS连接;对于通过此模板发送消息来说,这通常足够好了。在Java EE环境中,确保ConnectionFactory是通过JNDI从应用程序的环境命名上下文获得的;应用服务器通常在那里公开池状的、事务敏感的工厂。
@Bean
public MessageConverter jacksonJmsMessageConverter(){
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
@JmsListener(destination = "Message")
public void receive(User user){
System.out.println(user.getId()+":"+user.getName());
}