What You See Is What You Get Element

JMS 2.0 中的新内容,第 1 部分:易用性

作者:Nigel Deakin

了解新的易用性特性如何使您可以编写更少的代码。

2013 年 5 月发布

本文是由两部分组成的系列的第一篇文章,假设您基本熟悉 Java 消息服务 (JMS) 1.1,并介绍了 JMS 2.0 中的一些新的易用性特性。在第 2 部分中,我们将介绍新增的消息传递特性。

JMS 2.0 于 2013 年 4 月发布,是自 2002 年发布 1.1 版之后对 JMS 规范的第一次更新。有人可能会认为 API 这么长时间没有变化,一定已变得陈旧、渐渐废弃。但如果按不同实现的数量来判断一个 API 标准是否成功的话,JMS 是现有最成功的 API 之一。

在 JMS 2.0 中,我们将重点放在与其他企业 Java 技术中具有同样的易用性改进上。虽然现在 Enterprise JavaBeans、Java 持久性等技术比十年前要容易使用得多,但 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 被称作简化的 API。顾名思义,其目的是比现有 JMS 1.1 API 更简单易用,后者(在意料之中地)现在被称作经典 API

简化的 API 由三个新接口构成:JMSContextJMSProducerJMSConsumer

  • JMSContext 用一个对象替代经典 API 中单独的 ConnectionSession 对象。
  • JMSProducer 是经典 API 中 MessageProducer 对象的轻量级替代品。它允许使用方法链(有时称为构建器模式)配置消息传递选项、消息头和消息属性。
  • JMSConsumer 替代经典 API 中的 MessageConsumer 对象,使用方式类似。

开发人员现在可以选择使用经典 API(熟悉的 JMS 1.1 对象 ConnectionSessionMessageProducerMessageConsumer)或简化的 API(JMS 2.0 中引入的 JMSContextJMSProducerJMSConsumer 对象)。

简化的 API 不仅提供了经典 API 的所有特性,还增加了一些其他特性。经典 API 并未弃用,将作为 JMS 的一部分永久保留。

使用简化的 API 发送消息

JMS 1.1 经典 API 投入使用已逾十载,其效用久经考验。JMS 2.0 简化的 API 在哪些方面表现更好?JMS 2.0 简化的 API 需要的代码较少。

清单 1 显示的简单示例使用经典 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

现在比较清单 1 与清单 2,清单 2 显示如何使用 JMS 2.0 中简化的 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

您可以看到,所需编写的代码量已经大为减少。我们来仔细看看。

  • 我们将创建一个 JMSContext 对象,而不是创建单独的 ConnectionSession 对象。
  • JMS 1.1 版在使用 Connection 后用一个 finally 块对 Connection 调用 close。在 JMS 2.0 中,JMSContext 对象也有一个需要在使用后调用的 close 方法。不过,无需在代码中显式调用 closeJMSContext 实现了 Java SE 7 java.lang.AutoCloseable 接口。这意味着如果我们在 try-with-resources 块(也是 Java SE 7 的一个新特性)中创建 JMSContext,就会在块的结尾处自动调用 close 方法,而无需将其显式添加到代码中。

    实际上,所有具有 close 方法的 JMS 接口均已扩展,可以实现 java.lang.AutoCloseable 接口,这样它们就都可用于 try-with-resources 块。这包括 ConnectionSession 接口以及 JMSContext。因此,即使使用经典 API,还是可以从此特性中受益。注意,由于这个改动,JMS 2.0 只能用于 Java SE 7。

  • JMS 1.1 版创建 Session 对象时,它传入参数(falseSession.AUTO_ACKNOWLEDGE)指明我们希望创建一个非事务性会话,该会话中收到的所有消息都将自动确认。在 JMS 2.0 中,这是默认设置(对于 Java SE 应用程序),因此无需指定任何参数。

    如果希望指定其他某种会话模式(本地事务、CLIENT_ACKNOWLEDGEDUPS_OK_ACKNOWLEDGE),只需传入一个参数而不是两个参数。

  • 无需创建一个 TextMessage 对象并将其正文设置为指定字符串。只需将字符串传入 send 方法。JMS 提供程序将自动创建一个 TextMessage 并将其正文设置为所提供的字符串。
  • JMS 1.1 示例为几乎所有方法抛出的 JMSException 提供了一个 catch 块。JMS 2.0 简化的 API 示例有一个类似的块,但它捕获 JMSRuntimeException

    简化的 API 的特性之一就是其方法不声明检查到的异常。如果遇到错误情况,将抛出 JMSRuntimeException。这种新异常是 RuntimeException 的一个子类,意味着无需通过调用方法来显式捕获它,也不必在其 throws 子句中声明它。这与经典 API 正好相反,在经典 API 中几乎声明所有方法以抛出 JMSException,调用方法必须捕获或自己抛出此异常。

清单 1 和清单 2 均显示 ConnectionFactoryQueue 对象已作为参数传入。获得这些对象的方法并未改变,因此我们在这里以及在本文的其他清单中不再赘述。通常,将通过 JNDI 查询从 JNDI 信息库获取这些对象。

使用简化的 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 中简化的 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

与发送消息一样,也减少了所需编写的代码量。部分原因与前面的示例一样:

  • 我们将创建一个 JMSContext 对象,而不是创建单独的 ConnectionSession 对象。
  • 我们可以在 try-with-resources 块中创建 JMSContext,这样就可以在块结束时自动关闭它。因此无需调用 close
  • 我们无需指明我们希望自动确认收到的消息,因为默认设置就是这样。

而且,JMS 2.0 还通过其他两种方式减少了接收消息所需的代码量:

  • 在 JMS 1.1 中,我们需要调用 connection.start() 启动将消息传递给使用者,在 JMS 2.0 简化的 API 中则无需这样:连接会自动启动。(如果需要,可以禁用此行为。)
  • 无需接收 Message 对象、将其转换成 TextMessage,然后再调用 getText 提取消息正文。而是调用 receiveBody,它会直接返回消息正文。

使用简化的 API 异步接收消息

上面的示例显示了如何通过调用一个方法来同步接收消息,该方法在收到消息或发生超时前一直处于阻塞状态。

如果需要在 Java SE 应用程序中异步接收消息,在 JMS 1.1 中,您需要创建一个 MessageConsumer 对象,然后使用 setMessageListener 方法指定实现 MessageListener 接口的对象。然后需要调用 connection.start() 开始传递消息:

MessageConsumer messageConsumer = session.createConsumer(queue);
messageConsumer.setMessageListener(messageListener);
connection.start();

JMS 2.0 简化的 API 代码与此类似。您需要创建一个 JMSConsumer 对象,然后使用 setMessageListener 方法指定实现 MessageListener 接口的对象。消息传递将自动启动;无需调用 start

JMSConsumer consumer = context.createConsumer(queue);
consumer.setMessageListener(messageListener);

注意,如果需要在 Java EE 7 Web 或 EJB 应用程序中异步接收消息,则与先前版本的 Java EE 一样,需要使用消息驱动的 bean,而不是 setMessageListener 方法。

JMSContext 注入 Java EE 应用程序

如果要编写 Java EE Web 或 EJB 应用程序,那么使用 JMS 2.0 简化的 API 比在 Java SE 还要轻松。这是因为现在可以将 JMSContext“注入”代码中,让应用服务器来决定何时创建、何时关闭该对象。

以下代码是 Java EE 7 会话 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 名称。

如果在 JTA 事务(无论是容器托管还是 bean 托管)中使用注入的 JMSContext,则认为 JMSContext 具有事务作用域。这意味着提交 JTA 事务后将自动关闭 JMSContext

如果在无 JTA 事务时使用注入的 JMSContext,则认为 JMSContext 具有请求作用域。这意味着 JMSContext 将在请求结束时关闭。请求的长度在上下文和依赖注入 (CDI) 规范中定义,通常与来自客户端的 HTTP 请求或消息驱动的 bean 收到的消息有关。

除了由应用服务器自动创建和关闭之外,注入的 JMSContext 还有一些强大的特性。最重要的是,如果 servlet 调用一个会话 bean,或者一个会话 bean 调用另一个会话 bean,二者均使用注入的 JMSContext,只要两个注入的 JMSContext 对象是用相同方式定义的(例如,具有相同的 JMSConnectionFactory 批注),它们将实际对应于同一 JMSContext 对象。这就减少了应用程序所用 JMS 连接的数量。

JMS 2.0 中的其他 API 简化

JMS 2.0 还提供了另外几种简化。

直接从消息提取正文的新方法

JMS 消息由三部分组成:

  • 消息头
  • 消息属性
  • 消息正文

不同的消息类型有不同类型的正文:TextMessage 的正文是 StringBytesMessage 的正文是字节数组,等等。

在 JMS 1.1 中,使用消息类型特定的方法获取消息正文,例如,TextMessage 上的 getText 方法。然而,在应用程序接收消息时,JMS API 总是将消息作为 javax.jms.Message 对象提供,该对象需要转换成相应的子类型才能获取正文。这不仅适用于从 receive 调用返回消息时,还适用于通过调用 MessageListeneronMessage 方法异步传递消息时。

例如,如果使用 receive 方法接收 TextMessage,则需要将返回的对象从 Message 转换成 TextMessage,然后调用 getText 方法:

Message message = consumer.receive(1000); // returns a TextMessage
String body = ((TextMessage) message).getText();

JMS 2.0 新增了一种方法,可以稍微简化提取消息正文的过程。这就是 Message 上的 getBody 方法,经典和简化的 API 用户均可使用。此方法接受预期的正文类型作为参数,无需对消息或正文进行转换:

Message message = consumer.receive(1000); // returns a TextMessage
String body = message.getBody(String.class);

我们来看看 getBody 如何简化获取其他消息类型的正文所需的代码。

如果消息为 BytesMessage,JMS 1.1 提供了几种方式可从 BytesMessage 提取字节数组。最简单的方式是对 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。这将以 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 MapMessage
Map body = message.getBody(Map.class);

StreamMessage 是一种不能使用 getBody 的消息类型。这是因为流通常由应用程序应单独读取的多个对象组成。

使用 getBody 方法时,如果指定的类与正文类型不符,将引发 MessageFormatExceptionMessage 还新增了一个伴随方法 isBodyAssignableTo,可用于测试对 getBody 的后续调用是否能够将特定 Message 对象的正文以特定类型的形式返回。如果希望将出现多种类型的消息,这将很有用。

直接接收消息正文的方法

在 JMS 1.1 中,同步使用消息的应用程序对 MessageConsumer 使用 receive()receive(timeout)receiveNoWait() 方法。

在 JMS 2.0 中,使用简化的 API 的应用程序可以对 JMSConsumer 使用同样的方法执行此操作。

这些方法返回一个 Message 对象,可以从中获取消息正文。之前介绍的 getBody 方法提供了一种从该对象获取正文的简单方式。

使用 JMS 2.0 简化的 API 的应用程序还有一个选择。JMSConsumer 提供了三种方法 — receiveBody(class)receiveBody(class, timeout)receiveBodyNoWait(class) — 这三种方法将同步接收下一条消息并返回消息正文。与 getBody 一样,预期的类也是作为参数传递的。

因此无需使用清单 5 或清单 6 中的代码,应用程序使用清单 7 所示的一行代码即可。

JMSConsumer consumer = ...
Message message = consumer.receive(1000); // returns a TextMessage
String body = ((TextMessage) message).getText();

清单 5

JMSConsumer consumer = ...
Message message = consumer.receive(1000); // returns a TextMessage
String body = message.getBody(String.class);

清单 6

JMSConsumer consumer = ...
String body = consumer.receiveBody(String.class,1000);

清单 7

除了 StreamMessage(原因同样是此消息类型不支持 getBody)和 Message(因为它没有正文),receiveBody 方法可用于接收任意类型的消息,只要预先知道预期的正文类型。

只有 JMSConsumer 新增了这些方法。MessageConsumer 没有新增这些方法。这意味着此特性只能用于使用 JMS 2.0 简化的 API 的应用程序。

而且,这些方法不提供对消息头或属性(如 JMSRedelivered 消息头字段或 JMSXDeliveryCount 消息属性)的访问,因此应只用于应用程序无需访问它们的情况。

创建会话的新方法

在 JMS 1.1 中,使用 javax.jms.Connection 上的以下方法创建 javax.jms.Session,其中,transacted 参数需要设置为 truefalseacknowledgeMode 参数需要设置为 Session.AUTO_ACKNOWLEDGESession.CLIENT_ACKNOWLEDGESession.DUPS_OK_ACKNOWLEDGE

Session createSession(
  boolean transacted, int acknowledgeMode) throws JMSException

这一直是一个相当令人困惑的方法,原因主要有二:

  • 它使用两个参数定义会话的一个方面。
  • 在 Java EE 事务中,这两个参数最终都会被忽略。

我们依次考虑这两个问题。

两个参数定义同一事物

JMS 1.1 中 createSession 方法的第一个问题是,它使用两个参数定义的实际上是会话的一个方面,有四种可能:

  • 如果 transacted 参数设置为 false,则会话是非事务性的,接收消息时应使用用于指定三种确认之一的 acknowledgeMode 参数。
  • 如果 transacted 参数设置为 true,则忽略 acknowledgeMode 参数。

除了不必要地使事情复杂化,这还导致代码潜在地具有误导性,因为如果 transacted 参数设置为 false,用户仍需将 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 的用户可能会写下以下代码,而没有意识到该会话实际上会使用 EJB 的容器托管的 JTA 事务。

Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

为了解决这些问题,在 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_ACKNOWLEDGESession.CLIENT_ACKNOWLEDGESession.DUPS_OK_ACKNOWLEDGESession.TRANSACTED。在 Java EE 事务中,将忽略 sessionMode

JMS 2.0:不带参数的 createSession

在 Java EE 事务中,即使向 createSession 传递一个参数也有误导性,因为如果是 Java EE 事务,将忽略该参数。为了让用户能够编写误导性较小的代码,为 javax.jms.Connection 添加了第三个 createSession 方法,该方法没有参数:

Session createSession() throws JMSException

该方法特别适用于 Java EE 事务,其中指定会话模式没有意义,因为会被忽略。不过,此方法也可用于正常 Java SE 环境,在这种情况下,等同于调用 createSession(Session.AUTO_ACKNOWLEDGE)

JMS 2.0:带两个参数的 createSession

现有方法 createSession(boolean transacted,int acknowledgeMode) 仍然可以使用,将作为 API 的一部分永久保留。但是,鼓励开发人员使用此方法的一个参数或无参数版本。

JMS 2.0 中资源配置更轻松

JMS 2.0 通过多种方式简化了资源配置。

Java EE 中的默认连接工厂

Java EE 7 引入了一个平台默认 JMS 连接工厂。这是内置的连接工厂,连接到应用服务器的内置 JMS 提供程序。

应用程序可以通过使用名称 java:comp:DefaultJMSConnectionFactory 执行 JNDI 查询获取此连接工厂,无需先使用管理工具创建连接工厂:

@Resource(lookup="java:comp/DefaultJMSConnectionFactory") ConnectionFactory cf

此连接工厂旨在用于许多使用内置 JMS 提供程序的应用程序,无需添加任何应用程序特定的配置。

JMSContext 注入应用程序时,使用 JMSConnectionFactory 批注指定要使用的连接工厂:

@Inject @JMSConnectionFactory("
  jms/connectionFactory") JMSContext context1;

如果省略此批注,将使用默认连接工厂:

@Inject JMSContext context2; // uses the platform default connection factory

Java EE 中的 JMS 资源定义批注

每个 JMS 应用程序一开始都具有一个连接工厂(实现 javax.jms.ConnectionFactory 的对象)和至少一个目标(实现 javax.jms.Queuejavax.jms.Topic 的对象)。ConnectionFactory 是 JMS 中用于创建到 JMS 提供程序的连接的对象,QueueTopic 是标识发送或接收消息的物理队列或主题的对象。

不同 JMS 提供程序创建和配置这些对象的方式各不相同。因此,JMS 建议您使用单独的、提供程序特定的工具来创建、配置应用程序需要的连接工厂和目标并将其绑定到 JNDI 存储中。然后应用程序可以使用 JNDI 查询这些对象,无需使用任何非标准模式。除了保持应用程序代码的可移植性,这还意味着无需了解代码部署方式的详细信息,即可编写代码。

配置 ConnectionFactory 时,通常需要知道 JMS 服务器的主机名和端口等内容。配置 QueueTopic 对象时,通常需要知道队列或主题的物理名称。从应用程序分别创建 ConnectionFactoryQueueTopic 对象并将其存储在 JNDI 中,让部署人员或管理员而不是开发人员来定义这些详细信息。

尽管在许多企业环境中分离代码与配置非常重要,但在较简单的环境中,这可能是一项不希望出现的负担。此外,如果将应用程序部署到一个自动化的平台即服务 (PaaS) 系统中,可能需要自动化供应应用程序所需的 ConnectionFactoryQueueTopic 对象。

在许多 Java EE 应用程序中,现在任何 Java EE 7 应用服务器中均有默认 JMS 连接工厂(上节介绍过),因此根本无需配置任何连接工厂。但对于需要专门配置连接工厂的情况(以及队列和主题),Java EE 7 提供的另一个新特性允许使用代码中的批注、部署描述文件中的 XML 元素或二者组合来创建这些对象。

主要的新批注为 javax.jms.JMSConnectionFactoryDefinitionjavax.jms.JMSDestinationDefinition。这些可以在 EJB bean 或 servlet 等任何 Java EE 组件类中定义,如清单 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

如果需要定义多个连接工厂或目标,需要将这些批注包含在 JMSConnectionFactoryDefinitionsJMSDestinationDefinitions 批注中,如清单 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 名称)、clientIduserpasswordmaxPoolSizeminPoolSize。此外,还可以使用 properties 属性指定应用服务器可能支持的其他非标准属性。清单 8 和清单 9 中的 addressListreconnectEnabled 就是这些非标准属性的示例。

JMSConnectionFactoryDefinition 批注定义较少的可以指定的标准属性,包括 name(即 JNDI 名称)和 destinationName(即提供程序特定的队列或主题名称),并允许使用 properties 属性指定其他非标准属性。

用这种方式定义的连接工厂和目标必须位于 java:compjava:modulejava:appjava:global 命名空间中,其存在时间通常与定义它们的应用程序的部署时间一样长。

还可以在部署描述文件(如 web.xmlejb-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

如果需要,开发人员可以在批注中指定某些必需的属性,由部署人员在部署描述文件中指定其余属性。对于到部署时才知道值的属性,这可能非常有用。

在以上所有示例中,应用服务器负责“供应”批注或部署描述文件中定义的 JNDI 资源。不过,部署人员仍需负责确保连接工厂所指向的 JMS 服务器已经安装并且可用,且已经创建物理队列和主题本身。

总结

在本文中,我们介绍了 JMS 2.0 中新增的易用性特性,该特性可显著减少开发人员需要编写的代码。在第 2 部分中,我们将介绍 JMS 2.0 中新增的消息传递特性。

另请参见


关于作者

Nigel Deakin 是 Oracle 的技术人员中的一位主要成员,他是 JSR 343 (Java Message Service 2.0) 规范的带头人。除了负责领导 JMS 规范的后续版本,他还是 Oracle JMS 开发团队的成员,负责 Open Message Queue 和 GlassFish 应用服务器。他最近在美国旧金山的 JavaOne 大会、比利时安特卫普的 Devoxx 大会上发表了演讲,目前他在英国剑桥工作。

分享交流

请在 FacebookTwitterOracle Java 博客上加入 Java 社区对话!