本文为学习目的的个人翻译,译文及后文「译者总结」仅供参考。
原文链接:What’s New in JMS 2.0, Part One: Ease of Use。
版权归原作者或原刊登方所有。本文为非官方译本;如有不妥,请联系删除。
作者:Nigel Deakin
发布于 2013 年 5 月
了解新的易用性特性如何让你写出更少的代码行数。
本文是两部分系列文章中的第一篇,假定读者已经对 Java Message Service(JMS)1.1 有基本了解,并介绍了 JMS 2.0 中的一些新易用性特性。在第二部分中,我们将讨论新的消息传递特性。
JMS 2.0 于 2013 年 4 月发布,这是自 2002 年 1.1 版发布以来,JMS 规范的第一次更新。一个长期没有变化的 API,表面上看似乎已经陈旧且少人使用。然而,如果以不同实现的数量来衡量一个 API 标准是否成功,那么 JMS 其实是最成功的 API 之一。
在 JMS 2.0 中,重点是补上其他企业级 Java 技术近年来在易用性方面做出的改进。像 Enterprise JavaBeans 或 Java persistence 这样的技术,如今已经比十年前更易于使用,而 JMS 则一直保持着一个成功、但相当冗长的 API。
JMS 2.0 最大的变化,就是引入了一套新的消息发送与接收 API,从而减少开发人员必须编写的代码量。对于运行在 Java EE 应用服务器中的应用程序,这套新 API 还支持资源注入。这意味着应用服务器可以负责 JMS 对象的创建和管理,进一步简化应用程序。
JMS 2.0 是 Java EE 7 平台的一部分,可用于 Java EE Web、EJB 应用程序,也可以独立运行在 Java SE 环境中。正如下文所解释的,其中一些功能只在独立环境中可用,而另一些则只在 Java EE Web 或 EJB 应用程序中可用。
简化 API
这套新 API 被称为 simplified API。顾名思义,它的目标是比现有的 JMS 1.1 API 更简单、更易于使用,而后者如今(正如你所料)则被称为 classic API。
simplified API 包含三个新接口:JMSContext、JMSProducer 和 JMSConsumer:
JMSContext用一个单独对象,取代 classic API 中分离的Connection和Session对象。JMSProducer是 classic API 中MessageProducer的轻量级替代品。它允许你使用方法链(有时也称为 builder pattern)来配置消息投递选项、消息头和消息属性。JMSConsumer则取代了 classic API 中的MessageConsumer,用法也比较类似。
开发者现在可以选择继续使用 classic API(也就是 JMS 1.1 中熟悉的 Connection、Session、MessageProducer 和 MessageConsumer),也可以使用 JMS 2.0 引入的 simplified API(即 JMSContext、JMSProducer 和 JMSConsumer)。
simplified API 提供了 classic API 的全部功能,并额外增加了一些新能力。classic API 并没有被弃用,并且会继续长期保留在 JMS 中。
使用 simplified API 发送消息
JMS 1.1 的 classic API 已经使用了十多年,并且证明了它本身很有价值。那么,JMS 2.0 的 simplified API 好在哪里?答案是:它所需代码更少。
清单 1 展示了一个使用 classic API 发送单条文本消息的简单例子。
public void sendMessageJMS11(ConnectionFactory connectionFactory, Queue queueString text) { try { Connection connection = connectionFactory.createConnection(); try { Session session =connection.createSession(false,Session.AUTO_ACKNOWLEDGE); MessageProducer messageProducer = session.createProducer(queue); TextMessage textMessage = session.createTextMessage(text); messageProducer.send(textMessage); } finally { connection.close(); } } catch (JMSException ex) { // handle exception (details omitted) }}清单 1
现在对比清单 2,它展示了在 JMS 2.0 中如何使用 simplified API 完成完全相同的事情:
public void sendMessageJMS20(ConnectionFactory connectionFactory, Queue queue,String text) { try (JMSContext context = connectionFactory.createContext();){ context.createProducer().send(queue, text); } catch (JMSRuntimeException ex) { // handle exception (details omitted) }}清单 2
可以看到,我们需要编写的代码量明显减少了。下面更详细看看原因:
- 不再需要分别创建
Connection和Session,而是只创建一个JMSContext。 - 在 JMS 1.1 版本中,需要在
finally块里调用Connection.close()。而在 JMS 2.0 中,JMSContext也有一个close方法,在使用后需要关闭。不过你不必显式在代码中调用它。JMSContext实现了 Java SE 7 的java.lang.AutoCloseable接口,这意味着如果我们在 try-with-resources 代码块中创建JMSContext,那么close方法会在块结束时自动调用,而不需要我们手写关闭逻辑。事实上,所有拥有close方法的 JMS 接口都已经实现了java.lang.AutoCloseable,因此它们都可以用于 try-with-resources,包括Connection、Session和JMSContext。所以,即便你仍在使用 classic API,也依然能从这个特性中受益。要注意的是,正因为这个变化,JMS 2.0 只能与 Java SE 7 一起使用。 - 在 JMS 1.1 中创建
Session时,需要传入false和Session.AUTO_ACKNOWLEDGE这两个参数,以指定我们想创建一个非事务型 session,并让收到的消息自动确认。而在 JMS 2.0 中,这已经成为默认行为(对 Java SE 应用而言),所以无需再显式传参。如果想指定其他 session 模式(本地事务、CLIENT_ACKNOWLEDGE或DUPS_OK_ACKNOWLEDGE),也只需要传一个参数,而不是两个。 - 不再需要先创建一个
TextMessage并把消息体设置为指定字符串。现在只需要把字符串直接传给send方法即可,JMS provider 会自动创建TextMessage并把这个字符串设为消息体。 - JMS 1.1 示例中,几乎所有方法都会抛出
JMSException,所以提供了一个对应的catch块。JMS 2.0 的 simplified API 示例也有类似的异常处理,但它捕获的是JMSRuntimeException。simplified API 的一个特点是,它的方法不再声明 checked exception。如果发生错误,则抛出JMSRuntimeException。这个新异常是RuntimeException的子类,因此调用方既不需要显式捕获它,也不需要在throws子句中声明它。这和 classic API 不同,后者几乎每个方法都声明抛出JMSException,因此调用方要么捕获,要么继续抛出。
清单 1 和清单 2 都把 ConnectionFactory 和 Queue 作为参数传入。这些对象的获取方式并没有变化,所以这里和本文后续示例中都不再展开。通常,它们是通过 JNDI 从 JNDI repository 中查找得到的。
使用 simplified API 同步接收消息
清单 3 展示了一个使用 JMS 1.1 同步接收单条 TextMessage 并提取其中文本的简单例子。
public String receiveMessageJMS11(ConnectionFactory connectionFactory,Queue queue){ String body=null; try { Connection connection = connectionFactory.createConnection(); try { Session session =connection.createSession(false,Session.AUTO_ACKNOWLEDGE); MessageConsumer messageConsumer = session.createConsumer(queue); connection.start(); TextMessage textMessage = TextMessage)messageConsumer.receive(); body = textMessage.getText(); } finally { connection.close(); } } catch (JMSException ex) { // handle exception (details omitted) } return body;}清单 3
清单 4 展示了在 JMS 2.0 中如何用 simplified API 完成同样的工作:
public String receiveMessageJMS20(ConnectionFactory connectionFactory,Queue queue){ String body=null; try (JMSContext context = connectionFactory.createContext();){ JMSConsumer consumer = session.createConsumer(queue); body = consumer.receiveBody(String.class); } catch (JMSRuntimeException ex) { // handle exception (details omitted) } return body;}清单 4
和发送消息的场景一样,所需代码量再次减少。部分原因与前面相同:
- 不再需要分别创建
Connection和Session,只需要一个JMSContext。 JMSContext可以放在 try-with-resources 代码块中,从而在结束时自动关闭,不再需要显式调用close。- 由于自动确认已是默认行为,因此无需再显式指定。
除此之外,JMS 2.0 在接收消息时还额外减少了两类代码:
- 在 JMS 1.1 中,我们需要调用
connection.start()才能开始向消费者投递消息。而在 JMS 2.0 的 simplified API 中,不需要这么做:连接会自动启动。(如果需要,你也可以禁用这个行为。) - 不再需要先接收一个
Message对象,再把它强制转换成TextMessage,最后调用getText去提取消息体。现在可以直接调用receiveBody,它会直接返回消息体。
使用 simplified API 异步接收消息
前面的例子展示了如何通过调用一个会阻塞的接收方法来同步接收消息,直到消息到达或者超时。
如果你需要在 Java SE 应用中异步接收消息,那么在 JMS 1.1 中,你需要先创建一个 MessageConsumer,再通过 setMessageListener 方法指定一个实现了 MessageListener 接口的对象,最后还要调用 connection.start() 来开始消息投递:
MessageConsumer messageConsumer = session.createConsumer(queue);messageConsumer.setMessageListener(messageListener);connection.start();JMS 2.0 的 simplified API 写法类似。你需要创建一个 JMSConsumer,然后同样调用 setMessageListener 来指定实现 MessageListener 的对象。不过,消息投递会自动开始,因此不再需要调用 start()。
JMSConsumer consumer = context.createConsumer(queue);consumer.setMessageListener(messageListener);注意,如果你需要在 Java EE 7 的 Web 或 EJB 应用程序中异步接收消息,那么和之前版本的 Java EE 一样,你仍然需要使用 message-driven bean,而不是 setMessageListener 方法。
在 Java EE 应用中注入 JMSContext
如果你编写的是 Java EE Web 或 EJB 应用程序,那么使用 JMS 2.0 simplified API 会比在 Java SE 中还要简单。这是因为你现在可以把 JMSContext 直接“注入”到代码中,并把它的创建和关闭交给应用服务器来处理。
下面的代码片段来自一个 Java EE 7 session bean 或 servlet,它注入了一个 JMSContext 并用它发送消息:
@Inject @JMSConnectionFactory("jms/connectionFactory") private JMSContext context;
@Resource(lookup = "jms/dataQueue") private Queue dataQueue;
public void sendMessageJavaEE7(String body) { context.send(dataQueue, body);}可以看到,这里既没有创建 JMSContext 的代码,也没有关闭它的代码。我们只是声明了一个 JMSContext 类型的字段,并加上 @Inject 和 @JMSConnectionFactory 注解。
@Inject 会告诉容器在需要时创建 JMSContext;@JMSConnectionFactory 则告诉容器应该使用哪个 ConnectionFactory 的 JNDI 名称。
如果注入的 JMSContext 被用在一个 JTA 事务中(无论是容器管理事务还是 bean 管理事务),那么这个 JMSContext 被视为具有 transaction scope。这意味着,当 JTA 事务提交后,JMSContext 会自动关闭。
如果注入的 JMSContext 在没有 JTA 事务的情况下被使用,那么它会被视为 request scope。这意味着当请求结束时,JMSContext 会被关闭。一个 request 的长度由 Contexts and Dependency Injection(CDI)规范定义,通常对应于客户端发起的一次 HTTP 请求,或者 message-driven bean 接收到的一条消息。
被注入的 JMSContext 除了由应用服务器自动创建和关闭之外,还具备一些很强的特性。其中最重要的一点是:如果一个 servlet 调用了一个 session bean,或者一个 session bean 又调用了另一个 session bean,并且它们都使用注入的 JMSContext,那么只要这两个 JMSContext 的定义方式相同(例如使用了相同的 JMSConnectionFactory 注解),它们实际上对应的就是同一个 JMSContext 对象。这可以减少应用程序使用的 JMS 连接数量。
JMS 2.0 中的其他 API 简化
JMS 2.0 还提供了若干其他简化。
直接从 Message 中提取消息体的新方法
一条 JMS 消息由三部分组成:
- 消息头(message headers)
- 消息属性(message properties)
- 消息体(message body)
消息体的类型取决于消息本身的类型:例如,TextMessage 的消息体是一个 String,BytesMessage 的消息体则是一个字节数组,等等。
在 JMS 1.1 中,消息体通过特定消息类型上的方法获得,例如 TextMessage 的 getText 方法。但当消息被应用程序接收时,JMS API 提供给应用的总是一个 javax.jms.Message 对象,因此在获取消息体之前,你必须先把它转换成正确的子类型。这一规则既适用于通过 receive 方法同步收到消息时,也适用于通过 MessageListener.onMessage 异步收到消息时。
例如,如果你通过 receive 接收到的是一个 TextMessage,就必须先把返回值从 Message 转换成 TextMessage,然后再调用 getText:
Message message = consumer.receive(1000); // returns a TextMessageString body = ((TextMessage) message).getText();JMS 2.0 新增了一个方法,使提取消息体这件事变得稍微简单了一些。这个方法是 Message 上的 getBody,classic API 和 simplified API 的用户都可以使用。它接收一个预期的消息体类型作为参数,因此你不需要再对消息对象或消息体做显式类型转换:
Message message = consumer.receive(1000); // returns a TextMessageString body = message.getBody(String.class);下面再看看 getBody 如何简化其他消息类型的消息体提取。
如果消息是一个 BytesMessage,JMS 1.1 提供了多种方式从中提取字节数组。其中最简单的办法,是在 BytesMessage 上调用 readBytes,它会把字节拷贝到指定的字节数组中。
下面是一个接收 BytesMessage 并提取消息体字节数组的 MessageListener 示例:
void onMessage(Message message){ // delivers a BytesMessage int bodyLength = ((BytesMessage)message).getBodyLength(); byte[] bytes = new byte[bodyLength]; int bytesCopied = ((BytesMessage)message).readBytes(bytes); ...在 JMS 2.0 中,getBody 让这件事变得简单得多:
void onMessage(Message message){ // delivers a BytesMessage byte[] bytes = message.getBody(byte[].class); ...如果消息是一个 ObjectMessage,在 JMS 1.1 中,你需要先在 ObjectMessage 上调用 getObject,然后再把返回的 Serializable 转换成你真正想要的类型:
void onMessage(Message message){ // delivers an ObjectMessage MyObject body = (MyObject)((ObjectMessage) message).getObject(); ...注意这里需要两次类型转换。你先要把消息从 Message 转成 ObjectMessage 才能调用 getObject;而 getObject 返回的是一个 Serializable,所以你还得把它再转换成真正的对象类型。
在 JMS 2.0 中,这些转换都不需要了:
void onMessage(Message message){ // delivers an ObjectMessage MyObject body = message.getBody(MyObject.class); ...最后,如果消息是 MapMessage,getBody 还允许你把消息体直接作为一个 Map 返回:
Message message = consumer.receive(1000); // returns a MapMessageMap body = message.getBody(Map.class);唯一不能使用 getBody 的消息类型是 StreamMessage。这是因为它的流通常由多个对象构成,应用程序应该逐个读取。
使用 getBody 时,如果指定的类与实际消息体类型不匹配,就会抛出 MessageFormatException。JMS 2.0 还在 Message 上新增了一个配套方法 isBodyAssignableTo,用于测试后续调用 getBody 时,某个 Message 的消息体能否作为某个指定类型返回。当应用可能接收到多种消息类型时,这会很有用。
直接接收消息体的新方法
在 JMS 1.1 中,同步消费消息的应用程序会在 MessageConsumer 上调用 receive()、receive(timeout) 或 receiveNoWait()。
在 JMS 2.0 中,使用 simplified API 的应用程序也可以在 JMSConsumer 上使用同名方法。
这些方法会返回一个 Message 对象,随后再从该对象中提取消息体。前面介绍的 getBody 方法已经提供了一种相对简单的方式。
不过,对于使用 JMS 2.0 simplified API 的应用程序来说,还有一个额外选择。JMSConsumer 提供了三个方法:receiveBody(class)、receiveBody(class, timeout) 和 receiveBodyNoWait(class),它们会同步接收下一条消息,并直接返回消息体。与 getBody 一样,预期的类型通过参数传入。
因此,与其使用清单 5 或清单 6 中的代码,应用程序可以直接使用清单 7 里的这一行:
JMSConsumer consumer = ...Message message = consumer.receive(1000); // returns a TextMessageString body = ((TextMessage) message).getText();清单 5
JMSConsumer consumer = ...Message message = consumer.receive(1000); // returns a TextMessageString body = message.getBody(String.class);清单 6
JMSConsumer consumer = ...String body = consumer.receiveBody(String.class,1000);清单 7
receiveBody 这些方法可以用于接收除 StreamMessage 之外的任何消息类型(原因与它不支持 getBody 相同),也不能用于没有消息体的 Message,前提是应用程序预先知道预期消息体的类型。
这些新方法只加在了 JMSConsumer 上,并没有加到 MessageConsumer 上。这意味着,这项能力只对使用 JMS 2.0 simplified API 的应用程序可用。
此外,这些方法也不会暴露消息头或属性(例如 JMSRedelivered 消息头字段,或 JMSXDeliveryCount 消息属性),因此它们只适用于应用程序不需要访问这些信息的场景。
创建 Session 的新方法
在 JMS 1.1 中,通常通过 javax.jms.Connection 上下面这个方法来创建 javax.jms.Session,其中 transacted 参数需要设置为 true 或 false,而 acknowledgeMode 参数则需要设置为 Session.AUTO_ACKNOWLEDGE、Session.CLIENT_ACKNOWLEDGE 或 Session.DUPS_OK_ACKNOWLEDGE:
Session createSession( boolean transacted, int acknowledgeMode) throws JMSException这个方法一直以来都相当令人困惑,主要原因有两个:
- 它用两个参数来定义 session 的同一个方面。
- 在 Java EE 事务中,这两个参数反正都会被忽略。
下面分别看看这两个问题。
用两个参数定义同一件事
JMS 1.1 中 createSession 的第一个问题在于,它用两个参数来定义一个实际上只有四种可能的 session 状态:
- 如果
transacted为false,那么这个 session 就是非事务型的,而acknowledgeMode用来指定接收消息时使用哪一种确认机制(总共三种)。 - 如果
transacted为true,那么acknowledgeMode会被忽略。
这不仅没必要地增加了复杂性,而且也会导致代码产生误导。因为即使在 transacted 设置为 true 时,用户仍然必须给 acknowledgeMode 填一个值,即使它会被忽略。例如,下面这段代码在语法上完全合法:
amb Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);iguous在 Java EE 事务中,这两个参数本来就会被忽略
JMS 1.1 中 createSession 的第二个问题是:在 Java EE Web 或 EJB 应用程序中,只要当前存在 JTA 事务(默认通常就是如此),传给 createSession 的这两个参数其实都会被忽略。但由于 API 强迫开发者必须传入两个参数,这就会导致非常误导性的代码。比如一个写 EJB Bean 的开发者,可能会写出下面这样的代码,而没有意识到这个 session 实际上仍然会使用 EJB 的容器管理 JTA 事务:
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);为了解决这些问题,JMS 2.0 在 javax.jms.Connection 上新增了两个同名的 createSession 方法:一个接收单个参数,一个完全不接收参数。下面分别介绍。
JMS 2.0:只带一个参数的 createSession
在 JMS 2.0 中,javax.jms.Connection 上新增了第二个 createSession 方法,它只接收一个参数 sessionMode:
Session createSession(int sessionMode) throws JMSException在普通 Java SE 环境中,sessionMode 可以设置为 Session.AUTO_ACKNOWLEDGE、Session.CLIENT_ACKNOWLEDGE、Session.DUPS_OK_ACKNOWLEDGE 或 Session.TRANSACTED。而在 Java EE 事务中,这个参数依然会被忽略。
JMS 2.0:无参数 createSession
在 Java EE 事务中,即便只传一个参数给 createSession,依旧是误导性的,因为一旦存在 Java EE 事务,这个参数就会被忽略。为了让开发者写出不那么误导人的代码,JMS 2.0 在 javax.jms.Connection 上新增了第三个无参版本的 createSession:
Session createSession() throws JMSException这个方法特别适合在 Java EE 事务中使用,因为在那种场景下,指定 session mode 本来就没有意义,它会被忽略。不过,这个方法也可以在普通 Java SE 环境中使用,这时它等价于调用 createSession(Session.AUTO_ACKNOWLEDGE)。
JMS 2.0:带两个参数的 createSession
原来的 createSession(boolean transacted,int acknowledgeMode) 方法仍然可以继续使用,并且会长期保留在 API 中。不过,开发者会被鼓励优先使用单参数或无参数版本。
JMS 2.0 中更简单的资源配置
JMS 2.0 还在多个方面让资源配置变得更简单。
Java EE 中的默认 Connection Factory
Java EE 7 引入了一个平台默认的 JMS connection factory。这是一个内置 connection factory,它会连接到应用服务器内置的 JMS provider。
应用程序可以通过 java:comp:DefaultJMSConnectionFactory 这个 JNDI 名称来获取它,而无需事先使用管理工具手工创建这个 connection factory:
@Resource(lookup="java:comp/DefaultJMSConnectionFactory") ConnectionFactory cf这个 connection factory 主要面向那些使用应用服务器内置 JMS provider、且不需要额外应用级定制的应用程序。
当把 JMSContext 注入到应用程序中时,可以通过 JMSConnectionFactory 注解来指定所使用的 connection factory:
@Inject @JMSConnectionFactory(" jms/connectionFactory") JMSContext context1;如果省略这个注解,那么就会使用默认的 connection factory:
@Inject JMSContext context2; // uses the platform default connection factoryJava EE 中的 JMS 资源定义注解
每个 JMS 应用都始于一个 connection factory(实现了 javax.jms.ConnectionFactory 的对象)以及至少一个 destination(实现了 javax.jms.Queue 或 javax.jms.Topic 的对象)。ConnectionFactory 是在 JMS 中用于创建到 JMS provider 连接的对象,而 Queue 或 Topic 则用来标识实际发送到或接收自的物理队列或主题。
这些对象的创建和配置方式,会因 JMS provider 的不同而不同。这也是为什么 JMS 推荐你使用一个与 provider 相关的独立工具,去创建、配置并绑定应用程序所需的 connection factory 和 destination 到 JNDI 中。应用程序随后就可以通过 JNDI 查找这些对象,而无需依赖任何非标准代码。这样不仅有助于保持应用代码的可移植性,也意味着在编写代码时,不需要预先知道部署细节。
配置 ConnectionFactory 时,通常需要知道 JMS 服务器的主机名、端口等信息;配置 Queue 或 Topic 时,通常需要知道物理队列或主题名。把这些对象的创建与应用本身分离,并存入 JNDI,就允许这些细节由部署者或管理员来定义,而不是由开发者硬编码在代码里。
尽管在很多企业环境里,这种配置与代码分离的方式是必要的,但在更简单的环境中,它也可能构成不必要的负担。此外,如果应用被部署到自动化的 PaaS 平台中,那么把应用所需的 ConnectionFactory、Queue 和 Topic 自动供应出来,可能会更理想。
在很多 Java EE 应用中,任何 Java EE 7 应用服务器中现已提供的默认 JMS connection factory(上一节刚介绍过)已经让额外配置 connection factory 变得没必要了。不过,在确实需要特别配置的 connection factory,以及 queue/topic 对象的情况下,Java EE 7 还提供了另一个新特性:可以通过代码注解、部署描述符中的 XML 元素,或者二者结合的方式来创建这些对象。
其中两个主要的新注解是 javax.jms.JMSConnectionFactoryDefinition 和 javax.jms.JMSDestinationDefinition。它们可以定义在任意 Java EE 组件类中,例如 EJB Bean 或 servlet,如清单 8 所示:
@JMSConnectionFactoryDefinition( name="java:global/jms/MyConnectionFactory", maxPoolSize = 30, minPoolSize= 20, properties = { "addressList=mq://localhost:7676", "reconnectEnabled=true" })@JMSDestinationDefinition( name = "java:global/jms/DemoQueue", interfaceName = "javax.jms.Queue", destinationName = "demoQueue" )public class NewServlet extends HttpServlet { ...清单 8
如果需要定义多个 connection factory 或 destination,就要把这些注解包裹在 JMSConnectionFactoryDefinitions 或 JMSDestinationDefinitions 中,如清单 9 所示:
@JMSConnectionFactoryDefinitions({ @JMSConnectionFactoryDefinition( name="java:global/jms/MyConnectionFactory1", maxPoolSize = 30, minPoolSize= 20, properties = { "addressList=mq://localhost:7676", "reconnectEnabled=true" } ), @JMSConnectionFactoryDefinition( name="java:global/jms/MyConnectionFactory2", maxPoolSize = 30, minPoolSize= 20, properties = { "addressList=mq://localhost:7677", "reconnectEnabled=true" } )})@JMSDestinationDefinitions({ @JMSDestinationDefinition( name="java:global/jms/DemoQueue1", interfaceName = "javax.jms.Queue", destinationName = "demoQueue1" ), @JMSDestinationDefinition( name="java:global/jms/DemoQueue2", interfaceName = "javax.jms.Queue", destinationName = "demoQueue2" )})public class NewServlet extends HttpServlet { ...清单 9
JMSConnectionFactoryDefinition 注解定义了一组标准属性,包括 name(即 JNDI 名称)、clientId、user、password、maxPoolSize 和 minPoolSize。此外,properties 属性还可以用来指定应用服务器可能支持的额外非标准属性。在清单 8 和清单 9 中,addressList 和 reconnectEnabled 就是此类非标准属性的例子。
JMSDestinationDefinition 注解可定义的标准属性数量较少,主要包括 name(JNDI 名称)和 destinationName(provider 特有的队列或主题名称),同时也允许通过 properties 指定额外的非标准属性。
以这种方式定义的 connection factory 和 destination,必须位于 java:comp、java:module、java:app 或 java:global 命名空间中,并且它们通常会在定义它们的应用程序部署期间一直存在。
这些定义也可以写在部署描述符文件中(例如 web.xml 或 ejb-jar.xml),如清单 10 所示:
<jms-connection-factory> <name>java:global/jms/MyConnectionFactory</name> <max-pool-size>30</max-pool-size> <min-pool-size>20</min-pool-size> <property> <name>addressList</name> <value>mq://localhost:7676</value> </property> <property> <name>reconnectEnabled</name> <value>true</value> </property></jms-connection-factory>
<jms-destination> <name>java:global/jms/DemoQueue</name> <interfaceName>javax.jms.Queue</interfaceName> <destinationName>demoQueue</destinationName></jms-destination>清单 10
如果需要,开发者也可以在注解中先定义一部分属性,再由部署者在部署描述符中补齐剩余属性。这对于那些只有在部署时才能确定取值的属性特别有用。
在以上所有例子中,应用服务器都负责“供应(provisioning)”这些在注解或部署描述符中定义的 JNDI 资源。不过,部署者仍然有责任确保 connection factory 指向的 JMS 服务器已安装且可用,以及物理队列和主题本身已经创建完成。
结论
在这篇文章中,我们讨论了 JMS 2.0 新增的易用性特性,它们使开发者能够明显减少代码行数。在第二部分中,我们将继续介绍 JMS 2.0 中新的消息传递特性。
另请参阅
关于作者
Nigel Deakin 是 Oracle 的 Principal Member of Technical Staff,也是 JSR 343(Java Message Service 2.0)的 Specification Lead。除了负责推动 JMS 规范下一版本的发展外,他还是 Oracle JMS 开发团队的一员,参与 Open Message Queue 和 GlassFish application server 的开发。他近年曾在美国旧金山的 JavaOne 和比利时安特卫普的 Devoxx 会议上发表演讲,目前常驻英国剑桥。
加入讨论
欢迎通过 Facebook、Twitter 以及 Oracle Java Blog 加入 Java 社区的讨论。
译者总结
这篇文章聚焦的是 JMS 2.0 在“易用性”上的改进,主线非常明确:通过 simplified API、JMSContext、更直接的消息体提取方式,以及更简单的资源配置机制,减少开发者在常见消息发送与接收场景下的样板代码。
原文前半部分主要围绕 simplified API 展开,强调它相对于 classic API 的核心变化:对象更少、默认行为更合理、异常模型更轻、以及与 Java SE 7 语言特性的结合更自然。后半部分则进一步延伸到 getBody、receiveBody、新的 createSession 重载,以及 Java EE 中的默认 connection factory 和资源定义注解。
如果把这篇与第二部分结合起来看,会更容易理解 JMS 2.0 的整体方向:第一部分侧重“写起来更省事”,第二部分侧重“消息传递能力更强”。两篇文章合起来,才构成对 JMS 2.0 更新重点的完整说明。