返回介绍

17.2.2 使用 Spring 的 JMS 模板

发布于 2024-08-17 00:45:49 字数 10810 浏览 0 评论 0 收藏 0

正如我们所看到的,JMS为Java开发者提供了与消息代理进行交互来发送和接收消息的标准API,而且几乎每个消息代理实现都支持JMS,因此我们不必因为使用不同的消息代理而学习私有的消息API。

虽然JMS为所有的消息代理提供了统一的接口,但是这种接口用起来并不是很方便。使用JMS发送和接收消息并不像拿一张邮票并贴在信封上那么简单。正如我们将要看到的,JMS还要求我们为邮递车加油(只是比喻的说法)。

处理失控的JMS代码

在10.3.1小节中,我向你展示了传统的JDBC代码在处理连接、语句、结果集和异常时是多么冗长和繁杂。遗憾的是,传统的JMS使用了类似的编程模型,如下面的程序清单所示。

程序清单17.1 使用传统的JMS(不使用Spring)发送消息

再次声明这是一段失控的代码!就像JDBC示例一样,差不多使用了20行代码,只是为了发送一条“Hello world!”消息。实际上,其中只有几行代码是用来发送消息的,剩下的代码仅仅是为了发送消息而进行的设置。

接收端也没有好到哪里去,如下面的程序清单所示。

与程序清单17.1一样,程序清单17.2也是用一大段代码来实现如此简单的事情。如果我们逐行地比较,我们会发现它们几乎是完全一样的。如果查看上千个其他的JMS例子,我们会发现它们也是很相似的。只不过,其中一些会从JNDI中获取连接工厂,而另一些则是使用主题代替队列。但是无论如何,它们都大致遵循相同的模式。

程序清单17.2 使用传统的JMS(不使用Spring)接收消息

因为这些样板式代码,我们每次使用JMS时都要不断地做很多重复工作。更糟糕的是,你会发现我们在重复编写其他开发者的JMS代码。

我们已经在第10章看到了Spring的JdbcTemplate是如何处理失控的JDBC样板式代码的。现在,让我来介绍一下Spring的JmsTemplate如何对JMS的样板式代码实现相同的功能。

使用JMS模版

针对如何消除冗长和重复的JMS代码,Spring给出的解决方案是JmsTemplate。JmsTemplate可以创建连接、获得会话以及发送和接收消息。这使得我们可以专注于构建要发送的消息或者处理接收到的消息。

另外,JmsTemplate可以处理所有抛出的笨拙的JMSException异常。如果在使用JmsTemplate时抛出JMSException异常,JmsTemplate将捕获该异常,然后抛出一个非检查型异常,该异常是Spring自带的JmsException异常的子类。表17.1列出了标准的JMSException异常与Spring的非检查型异常之间的映射关系。

表17.1 Spring的JmsTemplate会捕获标准的
JMSException异常,再以Spring的非检查型异常JmsException子类重新抛出

Spring(org.springframework.jms.*)

标准的JMS(javax.jms.*)

DestinationResolutionException

Spring特有的——当Spring无法解析目的地名称时抛出

IllegalStateException

IllegalStateException

InvalidClientIDException

InvalidClientIDException

InvalidDestinationException

InvalidSelectorException

InvalidSelectorException

InvalidSelectorException

JmsSecurityException

JmsSecurityException

ListenerExecutionFailedException

Spring特有的——当监听器方法执行失败时抛出

MessageConversionException

Spring特有的——当消息转换失败时抛出

MessageEOFException

MessageEOFException

MessageFormatException

MessageFormatException

MessageNotReadableException

MessageNotReadableException

MessageNotWriteableException

MessageNotWriteableException

ResourceAllocationException

ResourceAllocationException

SynchedLocalTransactionFailedException

Spring特有的——当同步的本地事务不能完成时抛出

TransactionInprogressException

TransactionInprogressException

TransactionRolledBackException

TransactionRolledBackException

UncategorizedJmsException

Spring特有的——当没有其他异常适用时抛出

对于JMS API来说,JMSException的确提供了丰富且具有描述性的子类集合,让我们更清楚地知道发生了什么错误。不过,所有的JMSException异常的子类都是检查型异常,因此必须要捕获。JmsTemplate为我们捕获这些异常,并重新抛出对应非检查型JMSException异常的子类。

为了使用JmsTemplate,我们需要在Spring的配置文件中将它声明为一个bean。如下的XML可以完成这项工作:

因为JmsTemplate需要知道如何连接到消息代理,所以我们必须为connection-Factory属性设置实现了JMS的ConnectionFactory接口的bean引用。在这里,我们使用在12.2.1小节中所声明的connectionFactorybean引用来装配该属性。

这就是配置JmsTemplate所需要做的所有工作——现在JmsTemplate已经准备好了。让我们开始发送消息吧!

发送消息

在我们想建立的Spittr应用程序中,其中有一个特性就是当创建Spittle的时候提醒其他用户(或许是通过E-mail)。我们可以在增加Spittle的地方直接实现该特性。但是搞清楚发送提醒给谁以及实际发送这些提醒可能需要一段时间,这会影响到应用的性能。当增加一个新的Spittle时,我们希望应用是敏捷的,能够快速做出响应。

与其在增加Spittle时浪费时间发送这些信息,不如对该项工作进行排队,在响应返回给用户之后再处理它。与直接发送消息给其他用户所花费的时间相比,发送消息给队列或主题所花费的时间是微不足道的。

为了在Spittle创建的时候异步发送spittle提醒,让我们为Spittr应用引入AlertService:

正如我们所看到的,AlertService是一个接口,只定义了一个操作—— sendSpittleAlert()。

如程序清单17.3所示,AlertServiceImpl实现了AlertService接口,它使用JmsOperation(JmsTemplate所实现的接口)将Spittle对象发送给消息队列,而队列会在稍后得到处理。

程序清单17.3 使用JmsTemplate发送一个Spittle

JmsOperations的send()方法的第一个参数是JMS目的地名称,标识消息将发送给谁。当调用send()方法时,JmsTemplate将负责获得JMS连接、会话并代表发送者发送消息(如图17.5所示)。

图17.5 JmsTemplate代表发送者来负责处理发送消息的复杂过程

我们使用MessageCreator(在这里的实现是作为一个匿名内部类)来构造消息。在MessageCreator的createMessage()方法中,我们通过session创建了一个对象消息:传入一个Spittle对象,返回一个对象消息。

就是这么简单!注意,sendSpittleAlert()方法专注于组装和发送消息。在这里没有连接或会话管理的代码,JmsTemplate帮我们处理了所有的相关事项,而且我们也不需要捕获JMSException异常。JmsTemplate将捕获抛出的所有JMSException异常,然后重新抛出表17.1所列的某一种非检查型异常。

设置默认目的地

在程序清单17.3中,我们明确指定了一个目的地,在send()方法中将Spittle消息发向此目的地。当我们希望通过程序选择一个目的地时,这种形式的send()方法很适用。但是在AlertServiceImpl案例中,我们总是将Spittle消息发给相同的目的地,所以这种形式的send()方法并不能带来明显的好处。

与其每次发送消息时都指定一个目的地,不如我们为JmsTemplate装配一个默认的目的地:

在这里,将目的地的名称设置为spittle.alert.queue,但它只是一个名称:它并没有说明你所处理的目的地是什么类型。如果已经存在该名称的队列或主题的话,就会使用已有的。如果尚未存在的话,将会创建一个新的目的地(通常会是队列)。但是,如果你想指定要创建的目的地类型的话,那么你可以将之前创建的队列或主题的目的地bean装配进来:

现在,调用JmsTemplate的send()方法时,我们可以去除第一个参数了:

这种形式的send()方法只需要传入一个MessageCreator。因为希望消息发送给默认目的地,所以我们没有必要再指定特定的目的地。

在调用send()方法时,我们不必再显式指定目的地能够让任务得以简化。但是如果我们使用消息转换器的话,发送消息会更加简单。

在发送时,对消息进行转换

除了send()方法,JmsTemplate还提供了convertAndSend()方法。与send()方法不同,convertAndSend()方法并不需要MessageCreator作为参数。这是因为convertAndSend()会使用内置的消息转换器(message converter)为我们创建消息。

当我们使用convertAndSend()时,sendSpittleAlert()可以减少到方法体中只包含一行代码:

就像变魔术一样,Spittle会在发送之前转换为Message。不过就像所有的魔术一样,JmsTemplate内部会进行一些处理。它使用一个MessageConverter的实现类将对象转换为Message。

MessageConverter是Spring定义的接口,只有两个需要实现的方法:

尽管这个接口实现起来很简单,但我们通常并没有必要创建自定义的实现。Spring已经提供了多个实现,如表17.2所示。

表17.2 Spring为通用的转换任务提供了多个消息转换器
(所有的消息转换器都位于org.springframework.jms.support.converter包中)

消息转换器

功  能

MappingJacksonMessageConverter

使用Jackson JSON库实现消息与JSON格式之间的相互转换

MappingJackson2MessageConverter

使用Jackson 2 JSON库实现消息与JSON格式之间的相互转换

MarshallingMessageConverter

使用JAXB库实现消息与XML格式之间的相互转换

SimpleMessageConverter

实现String与TextMessage之间的相互转换,字节数组与BytesMessage之间的相互转换,Map与MapMessage之间的相互转换以及Serializable对象与ObjectMessage之间的相互转换

默认情况下,JmsTemplate在convertAndSend()方法中会使用SimpleMessage Converter。但是通过将消息转换器声明为bean并将其注入到JmsTemplate的messageConverter属性中,我们可以重写这种行为。例如,如果你想使用JSON消息的话,那么可以声明一个MappingJacksonMessageConverter bean:

然后,我们可以将其注入到JmsTemplate中,如下所示:

各个消息转换器可能会有额外的配置,进而实现转换过程的细粒度控制。例如,MappingJacksonMessageConverter能够让我们配置转码以及自定义Jackson ObjectMapper。可以查阅每个消息转换器的JavaDoc以了解如何更加细粒度地配置它们。

接收消息

现在我们已经了解了如何使用JmsTemplate发送消息。但如果我们是接收端,那要怎么办呢?JmsTemplate是不是也可以接收消息呢?

没错,的确可以。事实上,使用JmsTemplate接收消息甚至更简单,我们只需要调用JmsTemplate的receive()方法即可,如程序清单12.4所示。

当调用JmsTemplate的receive()方法时,JmsTemplate会尝试从消息代理中获取一个消息。如果没有可用的消息,receive()方法会一直等待,直到获得消息为止。图17.6展示了这个交互过程。

程序清单17.4 使用JmsTemplate接收消息

图17.6 使用JmsTemplate从主题或队列中接收消息的时候,只需要简单地调用receive()方法。JmsTemplate会处理其他的事情

因为我们知道Spittle消息是作为一个对象消息来发送的,所以它可以在到达后转型为ObjectMessage。然后,我们调用getObject()方法把ObjectMessage转换为Spittle对象并返回此对象。

但是这里存在一个问题,我们不得不对可能抛出的JMSException进行处理。正如我已经提到的,JmsTemplate可以很好地处理抛出的JmsException检查型异常,然后把异常转换为Spring非检查型异常JmsException并重新抛出。但是它只对调用JmsTemplate的方法时才适用。JmsTemplate无法处理调用ObjectMessage的getObject()方法时所抛出的JMSException异常。

因此,我们要么捕获JMSException异常,要么声明本方法抛出JMSException异常。为了遵循Spring规避检查型异常的设计理念,我们不建议本方法抛出JMSException异常,所以我们选择捕获该异常。在catch代码块中,我们使用Spring中JmsUtils的convertJmsAccessException()方法把检查型异常JMSException转换为非检查型异常JmsException。这其实是在其他场景中由JmsTemplate为我们做的事情。

在receiveSpittleAlert()方法中,我们可以改善的一点就是使用消息转换器。在convertAndSend()中,我们已经看到了如何将对象转换为Message。不过,它们还可以用在接收端,也就是使用JmsTemplate的receiveAndConvert():

现在,没有必要将Message转换为ObjectMessage,也没有必要通过调用getObject()来获取Spittle,更无需担心检查型的JMSException异常。这个新的retrieve SpittleAlert()简洁了许多。但是,依然还有一个很小且不容易察觉的问题。

使用JmsTemplate接收消息的最大缺点在于receive()和receiveAndConvert()方法都是同步的。这意味着接收者必须耐心等待消息的到来,因此这些方法会一直被阻塞,直到有可用消息(或者直到超时)。同步接收异步发送的消息,是不是感觉很怪异?

这就是消息驱动POJO的用武之处。让我们看看如何使用能够响应消息的组件异步接收消息,而不是一直等待消息的到来。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文