9.1.3 接收 JMS 消息

在消费消息时,可以选择 拉模型(代码请求消息并等待消息到达)或 推模型(消息可用时将消息传递给代码)。

JmsTemplate 提供了几种接收消息的方法,但它们都使用拉模型。调用其中一个方法来请求消息,线程会发生阻塞,直到消息可用为止(可能是立即可用,也可能需要一段时间)。

另一方面,还可以选择使用推模型,在该模型中,定义了一个消息监听器,它在消息可用时被调用。

这两个选项都适用于各种用例。人们普遍认为推模型是最佳选择,因为它不会阻塞线程。但是在某些用例中,如果消息到达得太快,侦听器可能会负担过重。拉模型允许使用者声明他们已经准备好处理新消息。

让我们看看接收消息的两种方式。我们将从 JmsTemplate 提供的拉模型开始。

使用 JmsTemplate 接收消息

JmsTemplate 提供多个用于拉模式的方法,包括以下这些:

Message receive() throws JmsException;
Message receive(Destination destination) throws JmsException;
Message receive(String destinationName) throws JmsException;

Object receiveAndConvert() throws JmsException;
Object receiveAndConvert(Destination destination) throws JmsException;
Object receiveAndConvert(String destinationName) throws JmsException;

可以看到,这 6 个方法是 JmsTemplate 中的 send()convertAndSend() 方法的镜像。receive() 方法接收原始消息,而 receiveAndConvert() 方法使用配置的消息转换器将消息转换为域类型。对于其中的每一个,可以指定 Destination 或包含目的地名称的 String,也可以从缺省目的地获取一条消息。

要查看这些操作,需要编写一些代码来从 tacocloud.order.queue 的目的地拉取 Order。下面的程序清单显示了 OrderReceiver,这是一个使用 JmsTemplate.receive() 接收 Order 数据的服务组件。

程序清单 9.2 从队列中拉取订单

package tacos.kitchen.messaging.jms;
import javax.jms.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;

@Component
public class JmsOrderReceiver implements OrderReceiver {
  private JmsTemplate jms;
  private MessageConverter converter;

  @Autowired
  public JmsOrderReceiver(JmsTemplate jms, MessageConverter converter) {
    this.jms = jms;
    this.converter = converter;
  }
  public TacoOrder receiveOrder() {
    Message message = jms.receive("tacocloud.order.queue");
    return (TacoOrder) converter.fromMessage(message);
  }
}

这里,使用了一个 String 来指定从何处拉取订单。receive() 方法返回一个未转换的 Message。但是真正需要的是 Message 中的 Order,所以接下来要做的就是使用注入的消息转换器来转换消息。消息中的类型 ID 属性将指导转换器将其转换为 Order,但是它是作为一个 Object 返回的,在返回它之前需要进行转换。

接收原始 Message 对象在某些需要检查消息属性和标题的情况下可能很有用,但是通常只需要有效载荷。将有效负载转换为域类型需要两个步骤,需要将消息转换器注入组件。当只关心消息的有效负载时,receiveAndConvert() 要简单得多。下面的程序清单显示了如何修改 JmsOrderReceiver 来使用 receiveAndConvert() 而不是 receive()

程序清单 9.3 接收已经转换的 Order 对象

package tacos.kitchen.messaging.jms;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.OrderReceiver;

@Component
public class JmsOrderReceiver implements OrderReceiver {

  private JmsTemplate jms;

  public JmsOrderReceiver(JmsTemplate jms) {
    this.jms = jms;
  }

  @Override
  public TacoOrder receiveOrder() {
    return (TacoOrder) jms.receiveAndConvert("tacocloud.order.queue");
  }
}

这个新版本的 JmsOrderReceiver 有一个 receieveOrder() 方法,该方法已经减少到只有一行。不再需要注入 MessageConverter,因为所有的消息转换都将在 receiveAndConvert() 中完成。

在继续之前,让我们考虑一下如何在 Taco Cloud 厨房应用程序中使用 receiveOrder()。在 Taco Cloud 的一个厨房里,一名食品加工人员可能会按下一个按钮或采取一些行动,表示他们已经准备好开始制作 tacos 了。

此时,receiveOrder() 将被调用,而对 receive() 或 receiveAndConvert() 的调用将被阻塞。在订单消息准备好之前,不会发生任何其他事情。一旦订单到达,它将从 receiveOrder() 中返回,其信息用于显示订单的详细信息,以便食品加工人员开始工作。这似乎是拉模型的自然选择。

现在,让我们通过声明 JMS 监听器来了解推模型是如何工作的。

声明消息监听器

在拉模型中,接收消息需要显式调用 receive()receiveAndConvert() 方法,与拉模型不同,消息监听器是一个被动组件,在消息到达之前是空闲的。

要创建对 JMS 消息作出响应的消息监听器,只需使用 @JmsListener 对组件中的方法进行注解。下面程序清单显示了一个新的 OrderListener 组件,它被动地监听消息,而不是主动地请求消息。

程序清单 9.4 监听订单的 OrderListener 组件

package tacos.kitchen.messaging.jms.listener;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import tacos.TacoOrder;
import tacos.kitchen.KitchenUI;

@Profile("jms-listener")
@Component
public class OrderListener {

  private KitchenUI ui;

  @Autowired
  public OrderListener(KitchenUI ui) {
    this.ui = ui;
  }

  @JmsListener(destination = "tacocloud.order.queue")
  public void receiveOrder(TacoOrder order) {
    ui.displayOrder(order);
  }
}

receiveOrder() 方法由 JmsListener 注解,以监听 tacocloud.order.queue 目的地的消息。它不处理 JmsTemplate,也不被应用程序代码显式地调用。相反,Spring 中的框架代码将等待消息到达指定的目的地,当消息到达时,receiveOrder() 方法将自动调用,并将消息的 Order 有效负载作为参数。

在许多方面,@JmsListener 注解类似于 Spring MVC 的请求映射注释之一,比如 @GetMapping@PostMapping。在 Spring MVC 中,用一个请求映射方法注解的方法对指定路径的请求做出响应。类似地,使用 @JmsListener 注解的方法对到达目的地的消息做出响应。

消息监听器通常被吹捧为最佳的选择,因为它们不会阻塞,并且能够快速处理多个消息。然而,在 Taco Cloud 应用程序的上下文中,它们可能不是最佳选择。食品加工是系统中的一个重要瓶颈,可能无法在接到订单时快速准备 taco。当一个新的订单显示在屏幕上时,食品加工者可能已经完成了一半的订单。厨房用户界面需要在订单到达时对其进行缓冲,以避免给厨房员工带来过重的负担。

这并不是说消息监听器不好。相反,当消息可以快速处理时,它们是完美的选择。但是,当消息处理程序需要能够根据自己的时间请求更多消息时,JmsTemplate 提供的拉模型似乎更合适。

因为 JMS 是由标准 Java 规范定义的,并且受到许多消息 Broker 的支持,所以它是 Java 中消息传递的常用选择。但是 JMS 有一些缺点,尤其是作为 Java 规范,它的使用仅限于 Java 应用程序。RabbitMQ 和 Kafka 等较新的消息传递选项解决了这些缺点,并且适用于 JVM 之外的其他语言和平台。让我们把 JMS 放在一边,看看如何使用 RabbitMQ 进行 taco 订单消息传递。

results matching ""

    No results matching ""