- 作者简介
- 内容提要
- 关于本书
- 路线图
- 代码规范与下载
- 作者在线
- 封面插图简介
- 前言
- 译者序
- 致谢
- 第1部分 Spring 的核心
- 第1章 Spring 之旅
- 第2章 装配 Bean
- 第3章 高级装配
- 第4章 面向切面的 Spring
- 第2部分 Web 中的 Spring
- 第5章 构建 Spring Web 应用程序
- 第6章 渲染 Web 视图
- 第7章 Spring MVC 的高级技术
- 第8章 使用 Spring Web Flow
- 第9章 保护 Web 应用
- 第3部分 后端中的 Spring
- 第10章 通过 Spring 和 JDBC 征服数据库
- 第11章 使用对象-关系映射持久化数据
- 第12章 使用 NoSQL 数据库
- 第13章 缓存数据
- 第14章 保护方法应用
- 第4部分 Spring 集成
- 第15章 使用远程服务
- 第16章 使用 Spring MVC 创建 REST API
- 第17章 Spring消息
- 第18章 使用 WebSocket 和 STOMP 实现消息功能
- 第19章 使用 Spring 发送 Email
- 第20章 使用 JMX 管理 Spring Bean
- 第21章 借助 Spring Boot 简化 Spring 开发
17.2.2 使用 Spring 的 JMS 模板
正如我们所看到的,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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论