# 17.3.4　接收 AMQP 消息

我们可以回忆一下，JMS 提供了两种从队列中获取信息的方式：使用 JmsTemplate 的同步方式以及使用消息驱动 POJO 的异步方式。Spring AMQP 提供了类似的方式来获取通过 AMQP 发送的消息。因为我们已经有了 RabbitTemplate，所以首先看一下如何使用它同步地从队列中获取消息。

**使用 RabbitTemplate 来接收消息**

RabbitTemplate 提供了多个接收信息的方法。最简单就是 receive() 方法，它位于消息的消费者端，对应于 RabbitTemplate 的 send() 方法。借助 receive() 方法，我们可以从队列中获取一个 Message 对象：

```java
Message message = rabbit.receive("spittle.alert.queue");
```

或者，如果愿意的话，你还可以配置获取消息的默认队列，这是通过在配置模板的时候，设置 queue 属性实现的：

```markup
<template id="rabbitTemplate"
          connection-factory="connectionFactory"
          exchange="spittle.alert.exchanges"
          routing-key="spittle.alerts"
          queue="spittle.alert.queue" />
```

这样的话，我们在调用 receive() 方法的时候，不需要设置任何参数就能从默认队列中获取消息了：

```java
Message message = rabbit.receive();
```

在获取到 Message 对象之后，我们可能需要将它 body 属性中的字节数组转换为想要的对象。就像在发送的时候将领域对象转换为 Message 一样，将接收到的 Message 转换为领域对象同样非常繁琐。因此，我们可以考虑使用 RabbitTemplate 的 receiveAndConvert() 方法作为替代方案：

```java
Spittle spittle = (Spittle) rabbit.receiveAndConvert("spittle.alert.queue");
```

我们还可以省略调用参数中的队列名称，这样它就会使用模板的默认队列名称：

```java
Spittle spittle = (Spittle) rabbit.receiveAndConvert();
```

receiveAndConvert() 方法会使用与 sendAndConvert() 方法相同的消息转换器，将 Message 对象转换为原始的类型。

调用 receive() 和 receiveAndConvert() 方法都会立即返回，如果队列中没有等待的消息时，将会得到 null。这就需要我们来管理轮询（polling）以及必要的线程，实现队列的监控。

我们并非必须同步轮询并等待消息到达，Spring AMQP 还提供了消息驱动 POJO 的支持，这不禁使我们回忆起 Spring JMS 中的相同特性。让我们看一下如何通过消息驱动 AMQP POJO 的方式来接收消息。

**定义消息驱动的 AMQP POJO**

如果你想在消息驱动 POJO 中异步地消费使用 Spittle 对象，首先要解决的问题就是这个 POJO 本身。如下的 SpittleAlertHandler 扮演了这个角色：

```java
package com.habuma.spittr.alerts;

import com.habuma.spittr.domain.Spittle;

public class SpittleAlertHandler {

  public void handleSpittleAlert(Spittle spittle) {
    // ... implementation goes here ...
  }
}
```

注意，这个类与借助 JMS 消费 Spittle 时所用到 SpittleAlertHandler 完全一致。我们之所以能够重用相同的 POJO 是因为这个类丝毫没有依赖于JMS 或 AMQP，并且不管通过什么机制传递过来 Spittle 对象，它都能够进行处理。

我们还需要在 Spring 应用上下文中将 SpittleAlertHandler 声明为一个 bean：

```markup
<bean id="spittleListener"
      class="com.habuma.spittr.alert.SpittleAlertHandler" />
```

同样，在使用基于 JMS 的 MDP 时，我们已经做过相同的事情，没有什么丝毫的差异。 最后，我们需要声明一个监听器容器和监听器，当消息到达的时候，能够调用 SpittleAlertHandler。在基于 JMS 的 MDP 中，我们做过相同的事情，但是基于 AMQP 的 MDP 在配置上有一个细微的差别：

```markup
<listener-container connection-factory="connectionFactory">
  <listener ref="spittleListener"
            method="handleSpittleAlert"
            queue-names="spittle.alert.queue" />
</listener-container>
```

你看到有什么差别了吗？我也同意这并不那么明显。\<listener-container> 与 \<litener> 都与 JMS 对应的元素非常类似。但是，这些元素来自 rabbit 命名空间，而不是 JMS 命名空间。

我都说过了，没那么明显。

哦，还有一个细微的差别，我们不再通过 destination 属性（JMS 中的做法）来监听队列或主题，这里我们通过 queue-names 属性来指定要监听的队列。但是，除此之外，基于 AMQP 的 MDP 与基于 JMS 的 MDP 都非常类似。

你可能也意识到了，queue-names 属性的名称使用了复数形式。在这里我们只设定了一个要监听的队列，但是允许设置多个队列的名称，用逗号分割即可。

另外一种指定要监听队列的方法是引用 \<queue> 元素所声明的队列 bean。我们可以通过 queues 属性来进行设置：

```markup
<listener-container connection-factory="connectionFactory">
  <listener ref="spittleListener"
            method="handleSpittleAlert"
            queue="spittleAlertQueue" />
</listener-container>
```

同样，这里可以接受逗号分割的 queue ID 列表。当然，这需要我们在声明队列的时候，为其指定 ID。例如，如下是重新定义的提醒队列，这次指定了 ID：

```markup
<queue id="spittleAlertQueue" name="spittle.alert.queue" />
```

注意，这里的 id 属性用来在 Spring 应用上下文中设置队列的 bean ID，而 name 属性指定了 RabbitMQ 代理中队列的名称。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/untitled-10/untitled-1/untitled.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
