jms.jar 2.0_JMS 2.0中的新增功能,第二部分?新的消息传递功能

JMS 2.0引入了新功能,包括在同一主题订阅中允许多个消费者,实现了消息的交付延迟,并支持异步发送消息。这些改进提高了应用程序的可扩展性和效率,允许更灵活的消息处理策略。
摘要由CSDN通过智能技术生成

jms.jar 2.0

Nigel Deakin即将发布的Java EE 7中有关JMS 2.0的系列的第二部分介绍了一些新的消息传递功能。 经Oracle公司Oracle技术网许可转载。

本文是分两部分的系列文章的第二部分,介绍Java消息服务(JMS)2.0中引入的一些新消息传递功能。 它假定对JMS 1.1有基本的了解。

第一部分中 ,我们研究了JMS 2.0中引入的新易用性功能。 在这里,我们研究重要的新消息传递功能。

自从2002年发布1.1版以来,于2013年4月发布的JMS 2.0是对JMS规范的第一次更新。人们可能会认为,长期保持不变的API已经垂死和未使用。 但是,如果根据不同实现的数量来判断API标准的成功,那么JMS就是周围最成功的API之一。

在JMS 2.0中,重点一直放在赶上近年来对其他企业Java技术所做的易用性改进。 借此机会也引入了许多新的消息传递功能。

JMS 2.0是Java EE 7平台的一部分,可以在Java EE Web或EJB应用程序中使用。 它也可以在Java SE环境中独立使用。 如下所述,某些功能仅在独立环境中可用,而其他功能仅在Java EE Web或EJB应用程序中可用。

在这里,我们讨论JMS 2.0中的五个重要的新消息传递功能。

同一主题订阅中允许多个消费者

在JMS 1.1中,一个主题的订阅一次不允许有多个消费者。 这意味着无法在多个线程,连接或Java虚拟机(JVM)之间共享处理主题订阅上的消息的工作,从而限制了应用程序的可伸缩性。 通过引入一种称为共享订阅的新型主题订阅,已在JMS 2.0中消除了此限制。

让我们回顾一下主题订阅在JMS 1.1中的工作方式。 在清单1中, SessioncreateConsumer方法用于在指定主题上创建一个 持久订阅 (我们将在稍后讨论持久订阅 ):

清单1

private void createUnsharedConsumer(ConnectionFactory connectionFactory, Topic topic) 
      throws JMSException {
   Connection connection = connectionFactory.createConnection();
   Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   MessageConsumer messageConsumer = session.createConsumer(topic);
   connection.start();
   Message message = messageConsumer.receive(10000);
   while (message != null) {
      System.out.println("Message received: " + ((TextMessage) message).getText());
      message = messageConsumer.receive(10000);
   }
   connection.close();
}

清单1中 ,使用者将收到发送给该主题的每条消息的副本。 但是,如果应用程序花费很长时间来处理每条消息怎么办? 我们如何通过在两个JVM之间共享处理这些消息的工作来使应用程序更具可伸缩性,其中一个JVM处理一些消息,而另一个JVM处理其余消息?

在JMS 1.1中,无法在普通的Java SE应用程序中执行此操作。 (在Java EE中,您可以使用消息驱动bean [MDB]池来完成此操作)。 如果使用createConsumer在单独的JVM(或同一JVM上的单独线程)中创建第二个使用者,则每个使用者将使用单独的订阅,因此它将收到该主题收到的每条消息的副本。 那不是我们想要的。 如果您将“订阅”视为接收发送给该主题的每条消息的副本的逻辑实体,那么我们希望两个使用者使用同一订阅。

JMS 2.0提供了一个解决方案。 您可以使用新方法createSharedConsumer创建“共享”的非createSharedConsumerSession (对于使用经典API的应用程序)和JMSContext (对于使用简化API的应用程序)都可以使用此方法。 由于两个JVM需要能够标识它们需要共享的订阅,因此它们需要提供一个名称来标识共享的订阅,如清单2所示。

清单2
private void createSharedConsumer(ConnectionFactory connectionFactory, Topic topic) throws JMSException {
   Connection connection = connectionFactory.createConnection();
   Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
   MessageConsumer messageConsumer = session.createSharedConsumer(topic,"mySubscription");
   connection.start();
   Message message = messageConsumer.receive(10000);
   while (message != null) {
      System.out.println("Message received: " + ((TextMessage) message).getText());
      message = messageConsumer.receive(10000);
   }
   connection.close();
}

如果您在两个单独的JVM中运行清单2中的代码,则发送给该主题的每条消息都将传递给两个使用者中的一个。 这使他们可以共享处理来自订阅的消息的工作。

使用持久订阅的应用程序可以使用相同的功能。 在JMS 1.1中,使用Session createDurableSubscriber方法创建了持久预订:

MessageConsumer messageConsumer = session.createDurableSubscription(topic,"myDurableSub");

这将在指定主题上创建名为myDurableSub的持久预订。 但是,像以前一样,无法在两个JVM或同一JVM上的两个线程之间共享对该持久订阅处理消息的工作。 根据您要尝试执行的操作,您将获得JMSException或两个不同的订阅。

JMS 2.0再次提供了针对此问题的解决方案。 现在,您可以使用新方法createSharedDurableConsumer创建“共享”持久订阅。 Session (对于使用经典API的应用程序)和JMSContext (对于使用简化API的应用程序)都可以使用此方法。

MessageConsumer messageConsumer = session.createSharedDurableConsumer(topic,"myDurableSub");

总而言之,尽管JMS 1.1定义了两种不同的主题订阅类型,但JMS 2.0定义了四种类型,所有这些类型都可以使用经典或简化的API创建:

  • 非共享的非持久订阅 。 这些在JMS 1.1和JMS 2.0中都可用,并且是使用createConsumer创建的。 他们只能有一个消费者。 设置客户端标识符是可选的。
  • 未共享的持久订阅 。 它们在JMS 1.1和JMS 2.0中都可用,并且使用createDurableSubscriber或(仅在JMS 2.0中) createDurableConsumer 。 他们只能有一个消费者。 设置客户端标识符是强制性的,并且通过订阅名称和客户端标识符的组合来标识订阅。
  • 共享的非持久订阅 。 这些仅在JMS 2.0中可用,并且使用createSharedConsumer创建。 他们可以有任意数量的消费者。 设置客户端标识符是可选的。 如果设置了订阅,则通过订阅名称和客户端标识符的组合来标识订阅。
  • 共享的持久订阅 。 这些仅在JMS 2.0中可用,并且使用createSharedDurableConsumer创建。 他们可以有任意数量的消费者。 设置客户端标识符是可选的。 如果设置了订阅,则通过订阅名称和客户端标识符的组合来标识订阅。
交货延迟

现在,您可以指定邮件的传递延迟。 在指定的传递延迟过去之前,JMS提供者将不会传递消息。

如果使用的是经典API,则需要在发送MessageProducer之前通过在MessageProducer上调用setDeliveryDelay来设置传递延迟(以毫秒为单位),如清单3所示。

清单3
private void sendWithDeliveryDelayClassic(ConnectionFactory connectionFactory,Queue queue) 
   throws JMSException {

   // send a message with a delivery delay of 20 seconds
   try (Connection connection = connectionFactory.createConnection();){
      Session session = con.createSession();
      MessageProducer messageProducer = session.createProducer(queue);
      messageProducer.setDeliveryDelay(20000);
      TextMessage textMessage = session.createTextMessage("Hello world");
      messageProducer.send(textMessage);
   }
}

如果使用简化的API,则需要在发送消息之前在JMSProducer上调用setDeliveryDelay 。 此方法返回JMSProducer对象,该对象使您可以创建JMSProducer,设置传递延迟并在同一行上全部发送消息,如清单4所示。

清单4
private void sendWithDeliveryDelaySimplified(ConnectionFactory connectionFactory,Queue queue)
   throws JMSException {

   // send a message with a delivery delay of 20 seconds
   try (JMSContext context = connectionFactory.createContext();){
      context.createProducer().setDeliveryDelay(20000).send(queue,"Hello world");
   }
}
异步发送消息

JMS 2.0的另一个新功能是能够异步发送消息。

此功能可用于在Java SE或Java EE应用程序客户端容器中运行的应用程序。 它不适用于在Java EE Web或EJB容器中运行的应用程序。

通常,当发送持久消息时,直到JMS客户端已将消息发送到服务器并收到答复以通知客户端该消息已安全接收并持久后,发送方法才会返回。 我们称此为同步发送

JMS 2.0引入了执行异步发送的功能 。 异步发送消息时,send方法将消息发送到服务器,然后将控制权返回给应用程序,而无需等待服务器的答复。 该应用程序可以执行一些有用的操作,例如发送其他消息或执行某些处理,而不是在JMS客户端等待答复时被无效率地阻止。

当收到来自服务器的回复,表明该消息已被服务器接收并持久存在时,JMS提供程序通过在应用程序指定的CompletionListener对象上调用onCompletion回调方法来通知应用程序。

您可以通过两种主要方法在应用程序中使用异步发送

  • 允许应用程序在等待服务器响应之前的时间间隔内执行其他操作(例如更新显示或写入数据库)
  • 允许连续发送大量消息而无需在每条消息之后等待服务器的答复

清单5是一个示例,说明如何使用经典API实现其中的第一个示例:

清单5

private void asyncSendClassic(ConnectionFactory connectionFactory,Queue queue)
   throws Exception {

   // send a message asynchronously
   try (Connection connection = connectionFactory.createConnection();){
      Session session = connection.createSession();
      MessageProducer messageProducer = session.createProducer(queue);
      TextMessage textMessage = session.createTextMessage("Hello world");
      CountDownLatch latch = new CountDownLatch(1);
      MyCompletionListener myCompletionListener = new MyCompletionListener(latch);
      messageProducer.send(textMessage,new MyCompletionListener(latch));
      System.out.println("Message sent, now waiting for reply");

      // at this point we can do something else before waiting for the reply
      // this is not shown here

      // now wait for the reply from the server 
      latch.await();

      if (myCompletionListener.getException()==null){
         System.out.println("Reply received from server");
      } else {
         throw myCompletionListener.getException();
      }
   }
}

清单5中使用的类MyCompletionListener是应用程序提供的一个单独的类,该类实现javax.jms.CompletionListener接口,如清单6所示:

清单6
class MyCompletionListener implements CompletionListener {

   CountDownLatch latch;
   Exception exception;
   
   public MyCompletionListener(CountDownLatch latch) {
      this.latch=latch;
   }

   @Override
   public void onCompletion(Message message) {
      latch.countDown();
   }

   @Override
   public void onException(Message message, Exception exception) {
      latch.countDown();
      this.exception=exception;
   }

   public Exception getException(){
      return exception;
   }
}

在清单6中,我们在MessageProducer上使用了一种新方法来发送消息,而无需等待服务器的答复。 这是send(Message message, CompletionListener listener) 。 使用此方法发送消息可以使应用程序在服务器中处理消息时执行其他操作。 当应用程序准备好继续时,它将使用java.util.concurrent.CountDownLatch等待,直到从服务器收到了答复。 收到答复后,应用程序可以以与正常同步发送后相同的置信度继续成功发送消息。

如果您使用的是JMS 2.0简化的API,则异步发送消息稍微简单一些,如清单7所示:

清单7

private void asyncSendSimplified(ConnectionFactory connectionFactory,Queue queue) 
   throws Exception {

   // send a message asynchronously
   try (JMSContext context = connectionFactory.createContext();){
      CountDownLatch latch = new CountDownLatch(1);
      MyCompletionListener myCompletionListener = new MyCompletionListener(latch);
      context.createProducer().setAsync(myCompletionListener).send(queue,"Hello world");
      System.out.println("Message sent, now waiting for reply");

      // at this point we can do something else before waiting for the reply
      // this is not shown here

      latch.await();
      if (myCompletionListener.getException()==null){
         System.out.println("Reply received from server");
      } else {
         throw myCompletionListener.getException();
      }
   }
 }

在这种情况下,在调用send(Message message)之前,先在JMSProducer上调用setAsync(CompletionListener listener)方法。 而且,由于JMSProducer支持方法链接,因此您可以在同一行上同时进行。

JMSXDeliveryCount

JMS 2.0允许接收消息的应用程序确定消息已重新发送多少次。 可以从消息属性JMSXDeliveryCount获得此信息:

int deliveryCount = message.getIntProperty("JMSXDeliveryCount");

JMSXDeliveryCount不是新属性; 它在JMS 1.1中定义。 但是,在JMS 1.1中,对于JMS提供者来说,实际设置它是可选的,这意味着使用它的应用程序代码不可移植。 在JMS 2.0中,JMS提供者必须设置此属性,以允许可移植应用程序使用它。

那么,为什么应用程序想知道一条消息已被发送多少次?

如果要重新传递邮件,则意味着先前的传递邮件的尝试由于某种原因而失败。 如果重复发送邮件,则原因可能是接收应用程序中的问题。 也许应用程序能够接收该消息,但无法对其进行处理,因此它会引发异常或回滚事务。 如果存在长时间无法处理该消息的原因(例如,该消息在某种程度上为“不良”消息),则将一遍又一遍重新传递同一条消息,从而浪费资源并阻止后续的“良好”消息被处理。

JMSXDeliveryCount属性允许使用中的应用程序检测到消息已多次重新传递,因此在某种程度上是“不良”消息。 应用程序可以使用此信息采取一些特殊的操作(而不是简单地触发另一个重新交付),例如使用消息并将其发送到单独的“不良”消息队列中,以供管理员操作。

一些JMS提供者已经提供了非标准的功能来检测重复发送的消息并将它们转移到无效消息队列中。 尽管JMS 2.0定义了如何处理此类消息,但JMSXDeliveryCount属性允许应用程序以可移植的方式实现其自己的“不良”消息处理代码。

清单8显示了一个MessageListener ,它抛出RuntimeException来模拟处理“错误”消息时的错误。 MessageListener使用JMSXDeliveryCount属性检测到一条消息已被重新发送十次,并采取不同的操作。

清单8
class MyMessageListener implements MessageListener {

   @Override
   public void onMessage(Message message) {
      try {
         int deliveryCount = message.getIntProperty("JMSXDeliveryCount");
    if (deliveryCount<10){
       // now throw a RuntimeException 
            // to simulate a problem processing the message
       // the message will then be redelivered
       throw new RuntimeException("Exception thrown to simulate a bad message");
         } else {
       // message has been redelivered ten times, 
       // let's do something to prevent endless redeliveries
       // such as sending it to dead message queue
       // details omitted
    }
      } catch (JMSException e) {
         throw new RuntimeException(e);
      }
   }
}

需要异步接收消息的Java EE应用程序使用MDB来实现,该MDB是通过指定许多配置属性来配置的。

Java EE的早期版本显然对MDB的配置方式含糊不清。 在EJB 3.1中,定义的唯一配置属性是:

  • acknowledgeMode (仅当交易bean管理;可设置为Auto-acknowledgeDups-ok-acknowledg E)
  • messageSelector
  • destinationType (可以设置为javax.jms.Queuejavax.jms.Topic
  • subscriptionDurability (仅用于主题;可以设置为DurableNonDurable

但是,EJB 3.1并未定义应用程序应如何指定MDB从哪个队列或主题接收消息。 它留给应用程序服务器或资源适配器来定义执行此操作的非标准方法。

EJB 3.1也没有定义-从主题接收消息并且subscriptionDurability属性设置为Durable应该如何指定订阅名称和客户机标识符。 EJB 3.1中没有标准方法来指定MDB用于创建其与JMS服务器的连接的连接工厂。

所有这些令人惊讶的局限性都在最新版本的Java EE中得到了解决。 EJB 3.2(Java EE 7的一部分)定义了以下附加配置属性:

  • destinationLookup :管理定义的Queue或Topic对象的JNDI名称,该对象代表MDB从其接收消息的队列或主题
  • connectionFactoryLookup :管理定义的ConnectionFactory对象的JNDI名称,MDB将使用该名称来连接到JMS提供程序
  • clientId :MDB连接到JMS提供者时使用的客户端标识符
  • subscriptionName :当subscriptionDurability设置为Durable时使用的持久预订名称

大多数应用程序服务器仍然支持clientIdsubscriptionName ,因此将它们定义为标准只是将现有实践形式化。

当然,总是可以配置JMS MDB使用的队列或主题,许多(但不是全部)应用程序服务器提供了一种指定连接工厂的方法。 但是,此方法是非标准的,并且在一台应用程序服务器之间变化。 应用服务器仍然可以自由支持这些非标准机制。 但是,您可以确信使用destinationLookupconnectionFactoryLookup应用程序可以与多个应用程序服务器一起使用。

清单9显示了一个JMS MDB,它使用来自主题的持久预订的消息并使用新的标准属性:

清单9

@MessageDriven(activationConfig = { 
   @ActivationConfigProperty(
      propertyName = "connectionFactoryLookup", propertyValue = "jms/MyConnectionFactory"),
   @ActivationConfigProperty(
      propertyName = "destinationLookup", propertyValue = "jmq/PriceFeed"), 
   @ActivationConfigProperty(
      propertyName = "destinationType ", propertyValue = "javax.jms.Topic "),
   @ActivationConfigProperty(
      propertyName = "subscriptionDurability ", propertyValue = "Durable"), 
   @ActivationConfigProperty(
      propertyName = "subscriptionName", propertyValue = "MySub"), 
   @ActivationConfigProperty(
      propertyName = "clientId", propertyValue = "MyClientId") }) 
   
public class MyMDB implements MessageListener {
   public void onMessage(Message message) {
      ...
结论

上述所有五个功能使Java开发人员可以更轻松地进行消息传递。 与第1部分中讨论的易用性功能一起,它们代表了JMS 2.0的重大进步-作为Java领域中最成功的API之一,JMS 2.0应该会继续蓬勃发展。

也可以看看

作者简介: Oracle公司技术人员的主要成员Nigel Deakin是Java Message Service 2.0的JSR 343的规范负责人。 除了负责领导下一版JMS规范的职责外,他还是Oracle JMS开发团队的成员,致力于Open Message Queue和GlassFish应用服务器。 他最近在美国旧金山的JavaOne和比利时安特卫普的Devoxx上发表了讲话,他的总部设在英国剑桥。

图片由andrewrennie提供


翻译自: https://jaxenter.com/whats-new-in-jms-2-0-part-twonew-messaging-features-106166.html

jms.jar 2.0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值