ActiveMQ In Action 第二章 理解面向对象的中间件(MOM)和JMS 2.4 JMS规范

来源:互联网 发布:流体热力学软件 编辑:程序博客网 时间:2024/04/30 13:21

2.4 JMS规范

正如上一节中提到的,JMS规范定义了两种类型的客户端——JMS客户端和非JMS客户端。让我们简短地探讨下他们的差异。

2.4.1 JMS 客户端

JMS客户端使用JMS API与JMS提供者进行交互。这概念与使用JDBC API来访问关系数据库中的数据类似,JMS客户端使用JMS API标准对消息服务器做标准化访问。许多JMS提供者(包括ActiveMQ)拥有超过JMS所需的功能。值得注意的是,100%的纯JMS客户端将只使用JMS API,避免使用这样的附加功能。但是选择使用一个特定的JMS提供程序通常是需要这些附加功能驱动的。如果一个JMS客户端使用这样的附加功能,在没有重构的情况下这个客户端可能无法移植到另一个JMS提供者上。

JMS客户端在某些方面利用MessageProducer和MessageConsumer接口。JMS提供者提供这两个接口的实现。发送消息的JMS客户端被称为生产者,接收消息的JMS客户端被称为消费者。一个JMS客户端也可能同时处理消息的发送和接收。

JMS 生产者

JMS客户端使用JMS MessageProducer类发送消息到目标。使用Session.createProducer()方法创建的生产者的时候,将会给定一个默认目标。通过使用MessageProducer.send()方法可以修改目标。MessageProducer接口如下所示。

Listing 2.1 The MessageProducer interface

public interface MessageProducer {
void setDisableMessageID(boolean value) throws JMSException;
boolean getDisableMessageID() throws JMSException;
void setDisableMessageTimestamp(boolean value) throws JMSException;
boolean getDisableMessageTimestamp() throws JMSException;
void setDeliveryMode(int deliveryMode) throws JMSException;
int getDeliveryMode() throws JMSException;
void setPriority(int defaultPriority) throws JMSException;
int getPriority() throws JMSException;
void setTimeToLive(long timeToLive) throws JMSException;
long getTimeToLive() throws JMSException;
Destination getDestination() throws JMSException;
void close() throws JMSException;
void send(Message message) throws JMSException;
void send(Message message, int deliveryMode, int priority,
long timeToLive)
throws JMSException;
void send(Destination destination, Message message)
throws JMSException;
void send(
Destination destination,
Message message,
int deliveryMode,
int priority,
long timeToLive)
throws JMSException;

}

MessageProducer不仅提供了发送消息的方法,还提供设置各种消息头包括JMS配送模式(JMSDeliveryMode) ,JMS优先级(JMSPriority),JMS失效(JMSExpiration)(通过get / setTimeToLive())的方法,以及可用send()方法立即设置所有这三种。消息头的相关内容会在2.4.5中讨论。

JMS 消费者

JMS客户端使用JMS MessageConsumer类从一个目标消费消息。MessageConsumer可以使用一个同步的receive()方法消费消息或通过实现MessageListener向消费者提供消息的异步处理方式。当消息到达目标后,MessageListener.onMessage()方法将被调用。MessageConsumer接口如下所示。

Listing 2.2 The JMS MessageConsumer interface

public interface MessageConsumer {
String getMessageSelector() throws JMSException;
MessageListener getMessageListener() throws JMSException;
void setMessageListener(MessageListener listener) throws JMSException;
Message receive() throws JMSException;
Message receive(long timeout) throws JMSException;
Message receiveNoWait() throws JMSException;
void close() throws JMSException;
}

MessageConsumer中没有设置目标的方法。目标的设置是在用Session.createConsumer()方法创建消费时进行的。

2.4.2 非JMS客户端

如前所述,非JMS客户端使用JMS提供程序的本地客户端API而不是JMS API。这是一个重要的区别,因为本地客户端API可能相对JMS API会有一些不同的特性。这样的非JMS API可能比起Java RMI跟多地利用CORBA IIOP协议或其他本地协议。消息传递提供程序如果出现在JMS规范之前,它会有一个本地客户端API。但许多JMS提供者也提供非JMS客户端API。

2.4.3 JMS提供者

JMS提供者是实现JMS API的供应商特定MOM。这样的实现提供了通过标准化JMS API访问MOM的能力(记得这个类似于JDBC)。

2.4.4 JMS消息

JMS消息是JMS规范中最重要的概念。JMS规范中的每个概念都是围绕怎样处理一个JMS消息,因为这是业务数据和事件的传播方式。JMS消息允许发送任何东西,包括文本,二进制数据以及headers中的信息。如图2.5所示,JMS消息包含两个部分,包括headers和payload。headers提供客户端和JMS提供者使用的消息元数据。payload是消息的实际载体,通过不同的消息类型可以保存文本和二进制数据。


JMS消息被设计成易于理解和灵活的。所有的复杂性都被放置在headers中。

2.4.5 JMS消息内部

如前所述,JMS消息的复杂性在于headers所提供的细节。实际上有两种类型的消息头,基本上是相同的逻辑概念,但语义上不同。而JMS规范提供了一个标准列表的头和方法与之协作,properties旨在扩充基于Java基本类型的自定义消息头。两者一般称为消息头(headers)。

JMS消息头

如图2.5所示,JMS消息提供一个headers的标准列表而JMS API提供了相应的方法。许多headers是自动分配的。下面的列表描述了这些headers,以及它们如何被分配到消息。

被客户端的send()方法自动设置的头:

■JMSDestination——消息发送的目标。这对需要接受来自多个目标的消息的客户端非常有用。

■JMSDeliveryMode——JMS支持两种类型的消息传递模式:持久的和非持久的。默认发送模式是持久的。每个传递模式会有自己的开销,这意味着一个特定级别的可靠性。

                                 * Persistent——建议JMS提供者保存消息,如此当提供者出错时消息不会丢失。JMS提供者必须有且仅有一次地传递一条持久性消息。换句话说,如果                                       JMS提供者出错,消息不会被丢失,也不会被多次传递由于需要存储消息,消息持久化导致更多的开销。比起性能这种模式更看重可靠性。

                                 * Nonpersistent——指示JMS提供者不对消息做持久化。JMS提供者对非持久化消息最多进行一次传递。换句话说,如果JMS提供者出错,消息可能会丢                                    失,但它不会被传递两次。非持久化消息带来的开销更少,着重性能高于可靠性。

发送模式被设置在生产者上并应用于所有生产者发送的消息。但单个消息的传递模式可以被变更。

■JMSExpiration——一个消息过期的时点。这个头是用来防止过期的消息被传递。消息过期值可以使用MessageProducer.setTimeToLive()方法来为生产者发送的所有消息设置全局的生存时间(time-to-live),或者使用MessageProducer.send()方法来为每条被发送消息设置本地的生存时间(time-to-live)。调用这些方法会为消息设置一个默认以毫秒为单位的可用时间长度,但MessageProducer.send()的优先级更高。

■JMSExpiration消息头的计算方式是向格林尼治时间(GMT)当前时间追加一个生存时间。默认情况下生存时间为零,这意味着消息不会过期。如果生存时间没有指定,默认值将被使用并且消息不会过期。生存时间显式地指定为零与未指定效果相同。

这个头对时间敏感的消息很有用。但是注意,JMS提供者不应该传递已经过期的消息,以及JMS客户端应该被设置成不处理过期消息。这个头对时间敏感的消息很有用。

■JMSMessageID ——由JMS提供者分配的唯一地标识消息的string字符串,必须以ID开头。消息ID可用于消息处理或用于历史消息存储机制。由于消息ID可以对JMS提供者造成一定开销,生产者可以通过MessageProducer.setDisableMessageID()方法建议JMS提供者不依赖这个头的值。如果JMS提供者接受这个建议,消息ID必须被设置为null。请注意,一个JMS提供者可能忽略这个请求并强制为消息分配一个ID。

■JMSPriority——为消息分配一个重要等级。这个头也在消息生产者上设置。一旦生产者设置了优先级,它适用于所有从生产者发送的消息。单条消息的优先级可以变更。JMS定义了10个级别的消息优先级,从0(最低)到9(最高):

                                   * Priorities 0–4——普通等级。

                                   * Priorities 5–9——优先等级。
JMS提供者不需要实现消息排序,尽管大多数都这么做了。他们应该只是简单得按优先级传递消息。

■JMSTimestamp——指消息从生产者被发送到JMS提供者的时间。这个头的值使用标准Java毫秒时间值。类似于JMSMessageID头,生产者可以通过Producer.setDisableMessageTimestamp()方法建议JMS提供者无视JMSTimestamp头。如果JMS提供程序接受这个建议,必须将JMSTimestamp设置为零。

客户端可选设置头:

■JMSCorrelationID——用于关联当前消息与以前的消息。这个头是常用来关联请求消息和响应消息。JMSCorrelationID的值可以是下列之一:

                                  *一个供应商特定消息ID

                                  *一个程序特定string字符串

                                  * 一个本地供应商 byte[]值

供应商特定消息ID将以"ID:"为前缀,而应用程序特定字符串不能以"ID:"为前缀。如果一个JMS提供者支持本地关联ID的概念,一个JMS客户端可能需要分配一个特定JMSCorrelationID值以匹配非JMS客户端的预期,但这不是一个硬性要求。

JMSReplyTo——为响应指定一个目标。这个头常用于请求/应答的消息传递方式。一条消息如果附加了这个头,通常是期望得到响应的,但它实际上是可选的。客户端必须决定是否作出响应。

JMSType——用于消息类型语义识别。这个头很少有供应商会用,跟消息实体的Java类型也没有关系。

JMS提供者可选设置头:

JMSRedelivered——用于指示一条消息被投递过但可能没有被接收。这种情况可能发生在消费者接收消息失败或者一个异常被抛出以防止应答到达提供者时JMS提供者没有收到通知的时候。

JMS消息Properties

Properties是一个简单的附加头,可以被指定在一条消息上。JMS提供了使用泛型方法设置自定义头的能力。方法为头值提供了许多Java基本类型,包括boolean、byte、short、int、long、float、double和String字符串。下面的清单列出了Message接口中这些方法的例子。

Listing 2.3 The JMS Message interface


public interface Message {
...
boolean getBooleanProperty(String name) throws JMSException;
byte getByteProperty(String name) throws JMSException;
short getShortProperty(String name) throws JMSException;
int getIntProperty(String name) throws JMSException;
long getLongProperty(String name) throws JMSException;
float getFloatProperty(String name) throws JMSException;
double getDoubleProperty(String name) throws JMSException;
String getStringProperty(String name) throws JMSException;
Object getObjectProperty(String name) throws JMSException;
...
Enumeration getPropertyNames() throws JMSException;
boolean propertyExists(String name) throws JMSException;
...
void setBooleanProperty(String name, boolean value) throws JMSException;
void setByteProperty(String name, byte value) throws JMSException;
void setShortProperty(String name, short value) throws JMSException;
void setIntProperty(String name, int value) throws JMSException;
void setLongProperty(String name, long value) throws JMSException;
void setFloatProperty(String name, float value) throws JMSException;
void setDoubleProperty(String name, double value) throws JMSException;
void setStringProperty(String name, String value) throws JMSException;
void setObjectProperty(String name, Object value) throws JMSException;
....
}


还要注意处理消息通用属性的两个便利的方法——getPropertyNames()方法和propertyExists()方法。getPropertyNames()方法返回一条消息的所有属性枚举,用于轻松地遍历所有属性。propertyExists()方法用于测试一个给定的属性是否存在于一个消息中。注意JMS特定头不关注通用属性也不包含在getPropertyNames()方法返回的枚举里。
有三种类型的属性:自定义属性,JMS定义属性和提供者指定属性。
自定义属性:

自定义属性是任意的,由一个JMS应用程序定义。JMS应用程序的开发人员可以自由地使用任何Java类型定义的任何属性,通过前一节所示的泛型方法(getBooleanProperty()/ setBooleanProperty(),getStringProperty()/ setStringProperty()等等)。

JMS定义属性:

■JMSXAppID——标识发送消息的应用程序

■JMSXConsumerTXID——当前消息消费事务的事务标识符

■JMSXDeliveryCount——消息尝试传递的次数

■JMSXGroupID——当前消息所在消息组

■JMSXGroupSeq——当前消息的组内序列号

■JMSXProducerTXID——当前消息产生事务的事务标识符

■JMSXRcvTimestamp——JMS提供者发送消息给消费者的时间

■JMSXState——用于定义提供者特定状态

■JMSXUserID——识别发送消息的用户

规范唯一推荐使用的属性是JMSXGroupID和了JMSXGroupSeq,且这些属性应该在客户端分组信息 和/或 分组信息在一个特殊顺序的时候使用。

提供者指定属性:

JMS规范为提供者指定属性保留了JMS_<商家名称>这样的属性名前缀。提供者为<商家名称>占位符定义了各自的值。这些都是通常用于提供者指定的非JMS客户端,而不应该用于JMS-to-JMS的消息传递。

我们讨论了JMS消息头和属性,那它们到底是做什么的呢?当客户端想过滤从订阅目标接收到的消息时,头和属性就显得十分重要。

2.4.6 消息选择器

有时一个JMS客户端可能需要过滤来自订阅目标的消息类型。这正是头和属性的用武之地。例如,一个消费者想从队列接收特定股票代码的消息。这十分简单,只要每个消息包含一个属性标识股票代码。JMS客户端可以使用JMS消息选择器告诉JMS提供者,它只希望接收包含一个特定属性并且该属性有一个特定值的消息。

消息选择器允许JMS客户端基于消息头值指定哪些消息是它希望从一个目标收到的。选择器使用SQL92的一个子集定义的条件表达式。消息选择器根据消息头和属性作出布尔逻辑判定。如果消息不匹配这些表达式,将不会传递给客户端。消息选择器不能使用消息有效负载,只能用消息头和属性。

通过使用javax.jms.Session对象中的一些创建方法传递字符串参数作为选择器的条件表达式。

这些表达式的语法使用各种标识符,文字以及直接取自SQL92语法和表2.1中定义的操作符。


表2.1中所示项目是用来创建针对消息头和属性的查询。考虑下一个列表中定义的消息。这个消息定义了两个属性,将用于过滤接下来的示例消息。

现在我们通过消息选择器做之前的消息做个过滤。

列表2.5定义了一个选择器,以匹配苹果公司。这个消费者之接收匹配选择器中定义的查询的消息。

本例中为为苹果公司指定一个选择器,匹配价格大于前一个价格的消息。这个选择器将显示股票价格上涨的消息。除此之外,如果你还想为账户增加股票消息的及时性,请看下面的例子。

最后一个示例列表2.7的消息选择器定义了一个更复杂的选择器,以匹配消息为苹果公司和思科公司,其中谁的价格增加以及市盈率低于当前接受阈值。

这些例子应该足够你开始使用消息选择器。如果你想要更深入的信息,请参见JMS消息类型的Javadoc。


消息实体:

JMS为消息实体(有效负载)定义了6个Java类型。通过使用这些对象,数据和信息可以通过消息有效负载发送。

■Message——基础消息类型。用于发送一条没有负载的消息,只有头和属性。通常用于简单的事件通知。

■TextMessage——消息的有效负载是一个字符串。通常用于发送简单文本和XML数据。

■MapMessage——使用一组键/值对作为其有效负载。键是String类型而值是一个Java基本类型。

■BytesMessage——包含一个连续的字节数组作为有效负载。

■StreamMessage——消息负载包含一个Java基本类型的被有序地填充和读取的流。

■ObjectMessage——用来保存一个序列化的Java对象作为它的负载。通常用于复杂的Java对象。还支持Java集合。


如前所述,JMS的创建是一群人的努力,这个团体里包含消息传递机制实现的提供者。由于现有消息传递机制实现的影响,导致JMS拥有两种风格的消息传递机制(或规范里称做的域)——点对点 和 发布/订阅。大多MOM已经支持这两种消息传递风格,所以JMS对两种风格都支持是有道理的。让我们看看这些消息传递风格来更好地理解它们。

点对点消息传递

点对点(PTP)消息传递域使用队列(queues)作为目标。通过使用队列,消息或同步或异步进行发送和接收。每个到队列的消息只能被传递一次且只传递给一个消费者。这类似于通过邮件服务器做人到人的邮件发送。消费者从队列中接收消息,同步则使用MessageConsumer.receive()方法,异步则通过注册一个MessageListener实现使用MessageConsumer.setMessage()——侦听器方法。队列中将存储所有消息直到他们被传递或过期。

多个消费者可以注册在一个队列上如图2.6所示,但只有一个消费者将获得一个给定的消息,然后由这个消费者确认消息。注意,图2.6中的消息来自一个生产者,被传递给一个消费者。并不是所有的消费者。正如前面提到的,JMS提供者保证将消息一次且仅有一次地交付给下一个认可的注册消费者。就此来看,JMS提供者会以一种轮询的方式将消息发送个所有的注册消费者之一。

发布/订阅消息传递

发布/订阅(pub/sub)消息传递域使用的目标称为主题。发布者发送消息到主题然后订阅者从主题接收消息。任何发送到主题的消息将自动传递给主题的所有订阅者。这个消息传递域类似于订阅邮件列表,以一对多模式将发送给邮件列表的消息发送给所有订阅者。发布/订阅域如图2.7所示。

与前一节中提到的PTP域差不多,订阅者注册到接收消息的主题,同步使用MessageConsumer.receive()方法,异步则通过注册MessageListener的实现然后使用MessageConsumer.setMessageListener()方法。主题不保存消息,除非被明确要求这样做。这可以通过使用持久订阅来实现。当使用一个持久订阅,如果一个订阅者断开与JMS提供者的连接,它的JMS提供者会为订阅者存储消息。重新连接后,持久订阅者将从JMS提供者收到所有未过期的消息。持久订阅能使订阅者断开时而不丢失任何信息。


区分消息的持久性和消息的持久化:

消息持久性和消息的持久化是JMS中经常混淆的两个概念。尽管他们很相似,但他们有语义及目的上的差别。消息持久性只能在pub/sub域实现。当客户端连接到一个话题,他们可以使用一个持久或非持久订阅。考虑这两者的差别:

■持久订阅——持久订阅是无限的。主题订阅者告诉JMS提供者当该订阅者断开时保存这个订阅者的状态。如果持久订阅者断开,JMS提供者将保存所有消息直到该订阅者重新连接或该订阅者显式地取消订阅。

■非持久订阅——非持久订阅是有限的。主题订阅者告诉JMS提供者不保存订阅者断开时的订阅者状态。如果订阅者断开,JMS提供者将不会为订阅者保存任何消息。

消息持久化独立于消息域。消息持久化是一个服务质量属性用于指示JMS应用程序在JMS提供者出错事件中处理失踪消息的能力。正如前面讨论的一样,这个服务质量属性可以通过在生产者的setDeliveryMode方法中使用 JMSDeliveryMode类的PERSISTENT或NON-PERSISTENT常量作为参数进行定义。

JMS应用程序中的请求/响应消息传递

尽管JMS规范没有定义请求/应答消息传递作为一个正式的消息传递域,但它提供了一些消息头和两个简便的类来处理基本请求/应答消息传递。请求/应答消息传递是一个异步的反复对话模式。利用PTP域或pub/sub域之一以及 JMSReplyTo和JMSCorrelationID消息头和临时目标达成。JMSReplyTo指定响应发出的目标,应答消息中的JMSCorrelationID指定请求消息的JMSMessageID。这些消息头文件用于链接应答消息到原始请求消息。临时目标只为一个连接的这个时期创建,也只能被创建他们的连接消费。这些限制使得临时目标能有应用于请求/应答消息传递。

QueueRequestor和TopicRequestor类用于处理基本请求/响应。这两个类提供了一个request()方法发送一个请求消息并通过创建一个临时目标等待回复消息,一个请求只期待一个响应。这些类只在请求/应答的最基本形式中起作用,如图2.8所示——一个请求一个响应。

图2.8描述了基本请求/应答方式两个端点之间的消息传递。这通常通过使用JMSReplyTo消息头和一个临时队列(接收方发送应答消息并被请求者消费)。如前所述,QueueRequestor和TopicRequestor可以处理基本请求/应答但不是设计来处理更复杂的请求/应答的情况,如单个请求和多个回答来自多个接收方。应付这样一个复杂的用例需要你开发一个自定义的JMS应用程序。

受管理对象

受管理对象包含供应商指定的JMS配置信息并建议由JMS管理员创建;因此,有了这个名字(受管理对象)。受管理对象被JMS客户端使用。他们用来隐藏来自客户端的供应商指定细节以及抽象JMS提供者的管理任务。通常通过JNDI查找管理对象,但不是必需的。当JMS提供者驻留在一个Java EE容器上时最常见。JMS规范定义了两种类型的受管理对象:ConnectionFactory和Destination。

CONNECTIONFACTORY

JMS客户端使用ConnectionFactory对象创建到一个JMS提供者的连接。连接通常是一个客户端和JMS提供程序间的开放的TCP套接字连接,所以连接的开销很大。如果可能的话,实现连接池是一个好主意。连接到一个JMS提供者类似于JDBC连接到一个关系数据库,客户端用来与数据库交互。JMS客户端使用连接创建javax.jms.Session对象,它表示与JMS提供程序的交互。

DESTINATION

Destination对象封装了供应商指定地址(消息发出的地方和被消耗消息的来源)。虽然destinations使用Session对象创建,但它的生命周期和创建session的connection一样长。

临时目标对于创建它们的连接是独一无二的。它们的生命周期与创建它们的连接一样长,并且只有创建它们的连接才能为它们创建消费者。如前所述,临时目标常用于请求/应答消息传递。

0 0