原文链接:https://www.oracle.com/technical-resources/articles/java/jms20.html

作者:Nigel Deakin 出版日期:2013 年 5 月

了解新的易用性功能如何使您编写更少的代码行。

本文是两部分系列文章的第一部分,假设读者对 Java 消息服务 (JMS) 1.1 有基本了解,并介绍了 JMS 2.0 中的一些新的易用特性。在第二部分中,我们将介绍新的消息传递特性。

JMS 2.0 于 2013 年 4 月发布,这是自 2002 年发布 1.1 版以来 JMS 规范的首次更新。人们可能会认为,一个长期保持不变的 API 已经变得毫无生机和无人使用。但是,如果根据不同实现的数量来判断 API 标准的成功,JMS 是最成功的 API 之一。

JMS 2.0 的重点是追赶其他企业 Java 技术的易用性改进。虽然企业 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替代了MessageConsumer经典API中的对象,使用方式类似。

开发人员现在可以选择是使用传统 API(JMS 1.1 中熟悉的ConnectionSessionMessageProducerMessageConsumer对象)还是简化的 API(JMS 2.0 中引入的 JMSContextJMSProducer JMSConsumer 对象)。

简化版 API 提供经典 API 的所有功能以及一些附加功能。经典 API 并未弃用,并将无限期地保留为 JMS 的一部分。

使用简化 API 发送消息

JMS 1.1 经典 API 已经使用了十多年,并且已经证明了它的实用性。JMS 2.0 简化 API 在哪些方面更胜一筹?JMS 2.0 简化 API 需要的代码更少。

清单 1 展示了一个使用经典 API 发送单条文本消息的简单示例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public void sendMessageJMS11(ConnectionFactory connectionFactory, Queue queue,String 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,它展示了我们如何在 JMS 2.0 中使用简化的 API 做完全相同的事情:

1
2
3
4
5
6
7
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 版本在使用 Connection 后使用时使用了一个 finally 块来调用 close 。在 JMS 2.0 中,对象 JMSContext 还有一个 close 方法,需要在使用后调用。但是,无需从代码中显式调用 closeJMSContext 实现 Java SE 7 java.lang.AutoCloseable 接口。这意味着,如果我们在 try-with-resources 块中创建 JMSContext (这也是 Java SE 7 的一个新功能),该方法将在块的末尾自动调用, 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 示例catchJMSException几乎所有方法都会抛出的 提供了一个块。JMS 2.0 简化 API 示例有一个类似的块,但它会捕获JMSRuntimeException

    简化 API 的一个特点是其方法不声明已检查的异常。如果遇到错误情况,则会JMSRuntimeException抛出异常。此新异常是 RuntimeException 的子类,这意味着它不需要由调用方法显式捕获或在其throws子句中声明。这与经典 API 形成对比,在经典 API 中,几乎每个方法都声明为抛出异常JMSException,调用方法必须捕获或自行抛出异常。

清单 1 和清单 2 均显示将ConnectionFactoryQueue对象作为参数传入。获取这些对象的方式没有改变,因此我们不会在此处或本文的其他清单中介绍这一点。通常,它们将通过从 JNDI 存储库进行 JNDI 查找来获取。

使用简化 API 同步接收消息

清单 3 展示了一个使用 JMS 1.1 同步接收单一消息TextMessage并提取其文本的简单示例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 做同样的事情:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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对象。
  • 我们可以在 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()以开始传递消息:

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

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

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

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

注入JMSContextJava EE 应用程序

如果您正在编写 Java EE Web 或 EJB 应用程序,那么使用 JMS 2.0 简化 API 甚至比在 Java SE 中更容易。这是因为您现在可以将“注入”到JMSContext您的代码中,并让应用服务器确定何时创建它以及何时关闭它。

以下代码是 Java EE 7 会话 bean 或 servlet 的一个片段,它注入JMSContext并使用它来发送消息:

1
2
3
4
5
6
7
8
9
@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 告诉容器它应该使用的 JNDI 名称 ConnectionFactory

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

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

注入除了由应用程序服务器自动创建和关闭之外,还 JMSContext 具有一些强大的功能。最重要的是,如果一个 servlet 调用了一个会话 bean,或者一个会话 bean 调用另一个会话 bean,并且都使用一个 injected JMSContext ,那么只要两个注入 JMSContext 的对象以相同的方式定义(例如,它们具有相同的 JMSConnectionFactory 注释),它们实际上将对应于同一个 JMSContext 对象。这样可以减少应用程序使用的 JMS 连接数。

JMS 2.0 中的其他 API 简化

JMS 2.0 还提供了其他一些简化。

直接从邮件中提取正文的新方法

JMS 消息由三部分组成:

  • 邮件标题
  • 消息属性
  • 邮件正文

正文的类型随消息类型而变化: TextMessage 的消息体是 StringBytesMessage 的消息体是一个字节数组,依此类推。

在 JMS 1.1 中,消息体是使用特定于消息类型的方法获取的,例如 on getText TextMessage 的方法。但是,当应用程序接收到消息时,JMS API 始终将消息作为 javax.jms.Message 对象提供,在获取正文之前,需要将其转换为适当的子类型。当消息是从对 的 receive 调用返回的,以及当消息是通过对 onMessage 的方法 MessageListener 的调用异步传递时,这都适用。

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

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

JMS 2.0 增加了一种新方法,使提取消息正文变得稍微简单一些。这就是getBody上的方法Message,它可供传统 API 和简化 API 的用户使用。此方法将预期的正文类型作为参数,不需要您对消息或正文执行强制类型转换:

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

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

如果消息是BytesMessage,JMS 1.1 提供了几种从 中提取字节数组的方法BytesMessage。最简单的方法是调用readBytes上的方法BytesMessage。这会将字节复制到指定的字节数组中。

MessageListener下面是一个接收BytesMessage并以字节数组形式获取主体的示例:

1
2
3
4
5
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方法使这一过程变得更加简单:

1
2
3
void onMessage(Message message){ // delivers a BytesMessage
   byte[] bytes = message.getBody(byte[].class);
   ...

如果消息是ObjectMessage,则在 JMS 1.1 中,您需要调用getObject方法ObjectMessage,然后将返回的转换Serializable为预期的主体类型:

1
2
3
void onMessage(Message message){ // delivers an ObjectMessage
   MyObject body = (MyObject)((ObjectMessage) message).getObject();
   ...

请注意需要执行两次强制转换。您需要将消息从 强制转换MessageObjectMessage,以便调用getObject。这会将正文返回为Serializable,然后您需要将其强制转换为实际类型。

在 JMS 2.0 中,无需任何强制类型转换即可完成此操作:

1
2
3
void onMessage(Message message){ // delivers an ObjectMessage
   MyObject body = message.getBody(MyObject.class);
   ...

最后,如果消息是MapMessage,则该getBody方法允许您将正文作为 返回Map

1
2
Message message = consumer.receive(1000); // returns a MapMessage
Map body = message.getBody(Map.class);

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

使用 时getBody,如果指定的类与主体类型不匹配,则会MessageFormatException抛出 。isBodyAssignableTo还向 添加了配套方法Message,它可用于测试后续调用 是否getBody能够将特定Message对象的主体作为特定类型返回。 如果预期的消息类型不止一种,则这很有用。

直接接收消息正文的方法

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

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

这些方法返回一个Message对象,可以从中获取消息正文。getBody前面描述的方法提供了一种从此对象获取正文的简单方法。

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

因此,应用程序可以使用清单 7 中所示的单行代码,而不必使用清单 5 或清单 6 中的代码。

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

清单 5

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

清单 6

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

清单 7

这些receiveBody方法可用于接收除StreamMessage(出于同样的原因,此消息类型不支持getBody)和Message(因为它没有正文)之外的任何类型的消息,只要提前知道预期正文的类别即可。

这些新方法仅添加到JMSConsumer。它们尚未添加到MessageConsumer。这意味着此功能仅适用于使用 JMS 2.0 简化 API 的应用程序。

此外,这些方法不提供对消息头或属性(例如JMSRedelivered消息头字段或JMSXDeliveryCount消息属性)的访问,因此仅当应用程序不需要访问它们时才应使用它们。

创建会话的新方法

在 JMS 1.1 中,使用了以下方法javax.jms.Connection来创建javax.jms.Session,其中 transacted 参数需要设置为true或 ,falseacknowledgeMode参数需要设置为Session.AUTO_ACKNOWLEDGESession.CLIENT_ACKNOWLEDGESession.DUPS_OK_ACKNOWLEDGE

1
2
Session createSession(
  boolean transacted, int acknowledgeMode) throws JMSException

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

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

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

定义同一事物的两个论据

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

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

除了不必要的复杂性之外,这还会导致代码具有潜在的误导性,因为如果将transacted参数设置为,即使忽略该参数false,用户仍必须将参数设置为某个值。例如 acknowledgeMode,以下代码完全有效:

1
2
amb Session session = 
  connection.createSession(true,Session.AUTO_ACKNOWLEDGE);

在 Java EE 事务中,两个参数都会被忽略

JMS 1.1 中方法的第二个问题createSession是,在 Java EE Web 或 EJB 应用程序中,如果存在当前 JTA 事务(默认情况下存在),则两个参数都会createSession被忽略。但是,由于 API 强制开发人员指定两个参数,这会导致代码极具误导性,因此编写 EJB bean 的用户可能会编写以下代码,而没有意识到会话实际上会使用 EJB 的容器管理 JTA 事务。

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

createSession为了解决这些问题,添加了两个名为 的新方法javax.jms.Connection。一个只有一个参数,另一个根本没有参数。我们将依次讨论这两个方法。

JMS 2.0:createSession带有一个参数

createSession在 JMS 2.0 中,添加了第二种方法javax.jms.Connection。该方法只有一个参数sessionMode

1
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 事务,该参数将被忽略。为了让用户编写更少误导的代码,createSession添加了第三个javax.jms.Connection没有参数的方法:

1
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 提供程序。

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

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

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

当将 注入JMSContext到应用程序时,JMSConnectionFactory使用注释来指定要使用的连接工厂:

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

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

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

Java EE 中的 JMS 资源定义注释

每个 JMS 应用程序都以一个连接工厂(实现 的对象javax.jms.ConnectionFactory)和至少一个目标(实现 或 的对象javax.jms.Queue)开始javax.jms.TopicConnectionFactory是在 JMS 中用于创建与 JMS 提供程序的连接的对象, 或QueueTopic用于标识要向其发送消息或从其接收消息的物理队列或主题的对象。

这些对象的创建方式和配置方式因 JMS 提供程序而异。这就是为什么 JMS 建议您使用单独的、提供程序特定的工具来创建、配置应用程序所需的连接工厂和目标并将其绑定到 JNDI 存储中。然后,您的应用程序可以使用 JNDI 查找这些对象,而无需使用任何非标准代码。除了保持应用程序代码的可移植性之外,这还意味着您可以编写代码而无需了解有关如何部署代码的详细信息。

配置 时ConnectionFactory,通常需要了解 JMS 服务器的主机名和端口等信息。配置QueueTopic对象时,通常需要了解队列或主题的物理名称。单独创建ConnectionFactoryQueueTopic对象并将其存储在 JNDI 中,这样部署者或管理员(而不是开发人员)就可以定义这些详细信息。

尽管在许多企业环境中,将代码与配置分开是必不可少的,但在较简单的环境中,这可能是不必要的负担。此外,如果将应用程序部署到自动化平台即服务 (PaaS) 系统中,则可能需要自动配置应用程序所需的ConnectionFactoryQueue和对象。Topic

在许多 Java EE 应用程序中,现在任何 Java EE 7 应用服务器中都提供了默认的 JMS 连接工厂(上一节中已介绍),因此根本不需要配置任何连接工厂。但是,对于需要专门配置连接工厂的情况(以及队列和主题),Java EE 7 提供了另一项新功能,允许使用代码中的注释、部署描述符中的 XML 元素或两者的组合来创建这些对象。

主要的新批注是javax.jms.JMSConnectionFactoryDefinitionjavax.jms.JMSDestinationDefinition。它们可以在任何 Java EE 组件类(如 EJB bean 或 servlet)中定义,如清单 8 所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@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 所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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 所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<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 中新增的易用功能,这些功能使开发人员能够编写更少的代码。在第二部分中,我们将介绍 JMS 2.0 中的新消息传递功能。

也可以看看

关于作者

Nigel Deakin 是 Oracle 的首席技术人员,曾担任 JSR 343(Java 消息服务 2.0)的规范负责人。除了负责领导 JMS 规范的下一版本之外,他还是 Oracle JMS 开发团队的成员,致力于 Open Message Queue 和 GlassFish 应用服务器。他最近在美国旧金山的 JavaOne 和比利时安特卫普的 Devoxx 上发表过演讲,目前居住在英国剑桥。

加入对话

在FacebookTwitterOracle Java 博客上加入 Java 社区讨论!