消息过滤的背景
从一个特定的队列或主题接收消息时,您可能会希望由更多的选择。如果没有消息过滤技术,主题订阅者就会接收发布到该主题的每一条消息,而队列接收者也会继续接收下一条消息,并不考虑这些消息的内容或类型。
就主题订阅者来说,它可能要强制处理很多不必要和不想要的消息,这通常导致:编写定制Java代码来人工过滤不想要的消息。
针对队列的消息过滤更有趣,因为它和主题不同:一旦一条消息被一个队列接收者消费以后,对其他所有接收者来说,该队列就不再可用。这意味着,如果一个队列接收者消费了一条消息,并决定它不应该处理这条消息,此时已经为时太晚了:该消息已经被接收并从该队列中删除。
消息过滤器
一旦使用了消息选择器,消费者就只会接收能够通过过滤器的那些消息。消息选择器使用消息属性和消息头作为条件表达式的准则。请注意:消息选择器无法参考消息体内的数据;它只能使用消息头和消息属性。这就意味着,MessageProducer必须向消息属性域中添加合适的数据,使得那些消息可以进行逻辑过滤。
消息选择器是基于SQL-92条件表达式语法的一个子集。消息选择器由三个元素组成:标识符、常量和比较运算符。
标识符
标识符是表达式中被比较的那一部分。标识符必须来自消息头,或者是来自消息属性。例如,下面表达式中的标识符是Symbol、Side、Shares和JMSPriority:
Symbol = ‘ABC‘ AND Side = ‘BUY‘ AND Shares <=1000.0 AND JMSPriority > 4
标识符可以使任何应用程序定义的属性、JMS定义的属性或提供者定义的属性,也可以是若干JMS消息头之一。在刚才的例子中,Symbol、Side和Shares来自消息中的应用程序属性。而JMSPriority则来自于消息头。标识符是区分大小写的,而且它必须和属性或JMS消息头名称精确匹配。
可以用作标识符的JMS消息头包括下列几种:
- JMSDeliveryMode
- JMSPriority
- JMSMessageID
- JMSTimestamp
- JMSCorrelationID
- JMSType
JMSDestination和JMSReplyTo消息头无法用作标识符,因为它们对应的值是Destination对象,这些对象的实际值是私有的,因而也是未定义的(undefined)。
JMSRedelivered值可以在传送期间修改,因此不许子啊选择器中使用。
常量
常量是使用硬编码方式写入消息选择器的表达式值。在下面的消息选择器中,所有的常量有‘ABC‘、‘SELL‘和1000:
Symbol = ‘ABC‘ AND Side = ‘BUY‘ AND Shares <=1000.0
String常量使用单引号括起来。如果String常量中含有撇号或单引号,则使用两个单引号表示(例如,‘Smith‘‘s‘)。
数值常量使用精确数值表示法(+22、30、-54121)、近似数值表示法(-33.22、100.00、+7.0)或科学计数法(-9E4、3.4E6)来表示。
Boolean常量用true或false来表示
比较运算符
比较运算符对标识符和boolean表达式中的常量进行比较,这个boolean表达式的值为true或false。使用逻辑运算符AND和OR,可以将比较运算符组合成更复杂的表达式。消息选择器使用的比较运算符包括:
- 算术比较运算符
- LIKE运算符
- BETWEEN运算符
- IN运算符
- NOT运算符
- IS NULL运算符
消息选择器表达式从左到右赋值:
Symbol = ‘ABC‘ AND Side = ‘BUY‘ AND Shares <=1000.0
在这个例子中,表达式在求值时,就好像加了下面的圆括号一样:
(Symbol = ‘ABC‘ AND Side = ‘BUY‘) OR Shares <=1000.0
消息选择器支持6个算术比较运算符,也就是=、>、>=、<、<=和<>(不等于)。这些算术比较运算符,可以使用在除boolean以外的任何基本属性类型上。Boolean和String属性类型限定使用=或<>这两种算术运算符。
String类型可以使用LIKE比较运算符进行比较。例如:
Shares > 1000.0 AND Symbol LIKE ‘A%C‘
LIKE运算符试图将常量中的每个字符和属性字符相匹配。LIKE比较还可以使用两个特殊的通配符:_和%。下划线代表所有的单个字符。百分号代表所有的字符串。
BETWEEN运算符可以用于指定一个范围(包括端点在内)。例如:
Shares BETWEEN 1000 and 2000
这个表达式等价于:
(Shares >= 1000) AND (Shares <= 2000)
IN运算符可以用于指定一个组的成语:
Symbol IN (‘ABC‘,‘AQC‘,BCD‘)
这个表达式等价于:
(Symbol = ‘ABC‘) OR (Symbol = ‘AQC‘) OR (Symbol = ‘BCD‘)
声明一个消息选择器
创建使用消息选择器的消费者时,JMS提供者必须确认该选择器语句句法正确。如果选择器句法错误,该运算会抛出javax.jms.InvalidSelectorException。示例如下:
String selector = "InventoryID = ‘s93740232-02‘ ADN Quantity BETWEEN 1000 AND 13000"; TopicSubscriber subscriber = session.createSubscriber(topic,selector,false); QueueReceiver receiver = session.createReceiver(queue,selector);
请注意:发布/订阅模型中,当创建订阅者时,要指定消息选择器,还必须为noLocal参数指定一个boolean值。noLocal参数仅仅适用于主题,并指明了消息生产者发布的消息是否应该传送给这个生产者自身。true表示禁止将消息传送给发布这条消息的同一连接。
设计注意事项
在设计基于消息的解决方案时,主要有两种消息过滤方式。您可以向单个JMS目的地发送所有相关的消息,并对消息消费者使用消息过滤来选择特定的消息,或者使用包含预过滤消息的多个JMS目的地。第一种方式,我们称之为Message Filtering(消息过滤)方式,而第二种方式,则成为Multiple Destination(多目的地)方式。迄今为止,我们重点讲述的是Message Filtering方式,使用这种方式,消息会发送给单个JMS目的地,并由消息过滤者过滤,如图所示:
请注意:在Msssage Filtering方式中,是消息消费者控制了消息过滤,并决定它要接收什么消息。这种方式为消息生产者组件和消息消费者组件提供了更高程度的解耦,因为消息生产者只需了解更少的关于如何处理消息的信息。这对发布/订阅者模型来说更是如此,因为主题发布者通常不会注意某个特定主题订阅者的数量和类型。
Multiple Destination方式在消息发送到目的地之前使用消息过滤。它使用的不是消息选择器,取而代之的则是包含特定消息的多目的地方式。消息生产者通常使用Java代码应用逻辑来判断消息应该被发送到哪一个目的地。由于每个队列或主题包含了一个特定类型的预过滤消息,消息消费者就不必使用消息过滤来接收它所感兴趣的消息。如图6-2:
如图6-2所示,使用Multiple Destination方式时,是消息生产者控制着过滤,同时还控制着哪一个目的地接收哪一条消息。这就是两种过滤方式之间的一个根本区别。使用Multiple Destination方式要考虑的一个关键因素就是,消息生产者对于消息的处理过程是否足够了解,以决定将消息路由到哪一个目的地。至于消息将如何消费,消息生产者了解得越多,它和消息消费者之间的耦合就会越紧密。当然,仅仅因为使用Multiple Destination方式,并不意味着您就无法进一步对消息消费者进行基于消息选择器的过滤。这两种方式的混合方式如图6-3所示:
这种混合方式在许多方面实现了上述两种方式的最佳平衡。它解决了Message Filtering方式和Multiple Destination方式面临的许多问题。对任何一种方式的过量使用都是一个很好的暗示:您的队列和主题的整体设计还存在问题,而主要问题就在于JMS目的地的粒度级别。要进行大量消息过滤的主题订阅者会提出该主题力度太粗,它们或许应该被划分为多个主题。而另一方面,被强制订阅多个主题以找回(retrieve)所需信息的主题订阅者,则会提出该主题粒度太细,应该将它们合并起来,以适用于主题订阅者使用的大多数用例。一般来说,队列或主题所代表的粒度等级及队列或主题的总体设计,应该反映信息的使用方式。
消息过滤