> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/untitled-10/untitled-1/untitled.md).

# 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 代理中队列的名称。
