# Spring AMQP

# 1.前言

Spring AMQP 项目将核心 Spring 概念应用于基于 AMQP 的消息传递解决方案的开发。我们提供了一个“模板”,作为发送和接收消息的高级抽象。我们还为消息驱动的 POJO 提供支持。这些库促进了 AMQP 资源的管理,同时促进了依赖注入和声明式配置的使用。在所有这些情况下,你都可以看到与 Spring 框架中的 JMS 支持的相似之处。有关其他项目相关信息,请访问 Spring AMQP 项目homepage (opens new window)

# 2.最新更新

# 2.1. 2.4 自 2.3 以来的变化

本部分描述了版本 2.4 和版本 2.4 之间的更改。有关以前版本的更改,请参见变更历史

# 2.1.1.@RabbitListener变化

MessageProperties现在可用于参数匹配。有关更多信息,请参见带注释的端点方法签名

# 2.1.2.RabbitAdmin变化

一个新的属性recoverManualDeclarations允许恢复手动声明的队列/交换/绑定。有关更多信息,请参见恢复自动删除声明

# 2.1.3.远程支持

使用 Spring Framework 的 RMI 支持的远程支持已被弃用,并将在 3.0 中删除。有关更多信息,请参见Spring Remoting with AMQP

# 3.导言

参考文档的第一部分是对 Spring AMQP 和底层概念的高级概述。它包括一些代码片段,可以让你尽快启动和运行。

# 3.1.不耐烦人士的快速之旅

# 3.1.1.导言

这是从 AMQP 开始的五分钟之旅。

先决条件:安装并运行 RabbitMQ 代理(https://www.rabbitmq.com/download.html (opens new window))。然后获取 Spring-Rabbit JAR 及其所有依赖项——这样做的最简单方法是在构建工具中声明一个依赖项。例如,对于 Maven,你可以执行类似于以下内容的操作:

<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
  <version>2.4.2</version>
</dependency>

对于 Gradle,你可以做以下类似的事情:

compile 'org.springframework.amqp:spring-rabbit:2.4.2'
# 兼容性

Spring Framework 版本的最小依赖关系是 5.2.0.

最小amqp-clientJava 客户端库版本是 5.7.0.

# 非常,非常快

这一节提供了最快的介绍。

首先,添加以下import语句,以使本节后面的示例有效:

import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

下面的示例使用普通的、命令式的 Java 来发送和接收消息:

ConnectionFactory connectionFactory = new CachingConnectionFactory();
AmqpAdmin admin = new RabbitAdmin(connectionFactory);
admin.declareQueue(new Queue("myqueue"));
AmqpTemplate template = new RabbitTemplate(connectionFactory);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");

请注意,在本机 Java Rabbit 客户机中也有一个ConnectionFactory。我们在前面的代码中使用了 Spring 抽象。它缓存通道(以及可选的连接)以供重用。我们依赖于代理中的默认交换(因为在 SEND 中没有指定),以及所有队列的默认绑定到默认交换的默认名称(因此,我们可以在 SEND 中使用队列名称作为路由键)。这些行为是在 AMQP 规范中定义的。

# 带有 XML 配置的

下面的示例与前面的示例相同,但将资源配置具体化为 XML:

ApplicationContext context =
    new GenericXmlApplicationContext("classpath:/rabbit-context.xml");
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/rabbit
           https://www.springframework.org/schema/rabbit/spring-rabbit.xsd
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <rabbit:connection-factory id="connectionFactory"/>

    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>

    <rabbit:admin connection-factory="connectionFactory"/>

    <rabbit:queue name="myqueue"/>

</beans>

默认情况下,<rabbit:admin/>声明会自动查找类型为QueueExchangeBinding的 bean,并代表用户向代理声明它们。因此,你不需要在简单的 Java 驱动程序中显式地使用这个 Bean。有很多选项可以配置 XML 模式中组件的属性。你可以使用 XML 编辑器的自动完成功能来探索它们并查看它们的文档。

# 使用 Java 配置的

下面的示例重复了与前面的示例相同的示例,但使用了在 Java 中定义的外部配置:

ApplicationContext context =
    new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate template = context.getBean(AmqpTemplate.class);
template.convertAndSend("myqueue", "foo");
String foo = (String) template.receiveAndConvert("myqueue");

........

@Configuration
public class RabbitConfiguration {

    @Bean
    public CachingConnectionFactory connectionFactory() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin amqpAdmin() {
        return new RabbitAdmin(connectionFactory());
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(connectionFactory());
    }

    @Bean
    public Queue myQueue() {
       return new Queue("myqueue");
    }
}
# 具有 Spring 引导自动配置和异步 POJO 侦听器

Spring 引导会自动配置基础设施 bean,如下例所示:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ApplicationRunner runner(AmqpTemplate template) {
        return args -> template.convertAndSend("myqueue", "foo");
    }

    @Bean
    public Queue myQueue() {
        return new Queue("myqueue");
    }

    @RabbitListener(queues = "myqueue")
    public void listen(String in) {
        System.out.println(in);
    }

}

# 4.参考文献

参考文档的这一部分详细介绍了构成 Spring AMQP 的各个组件。主要章节涵盖了开发 AMQP 应用程序的核心类。这一部分还包括关于示例应用程序的一章。

# 4.1.使用 Spring AMQP

本章探讨了接口和类,它们是使用 Spring AMQP 开发应用程序所必需的组件。

# 4.1.1.AMQP 抽象

Spring AMQP 由两个模块组成(每个模块由分布中的 JAR 表示):和。“ Spring-AMQP”模块包含org.springframework.amqp.core包。在这个包中,你可以找到代表核心 AMQP“模型”的类。我们的目的是提供不依赖于任何特定 AMQP 代理实现或客户库的通用抽象。最终用户代码可以在供应商的实现中更具可移植性,因为它可以仅针对抽象层进行开发。然后,这些抽象由特定于代理的模块实现,例如“ Spring-Rabbit”。目前只有一个 RabbitMQ 实现。然而,除了 RabbitMQ 之外,还使用 Apache QPID 在.NET 中验证了这些抽象。由于 AMQP 在协议级别上运行,原则上,你可以将 RabbitMQ 客户机与支持相同协议版本的任何代理一起使用,但是我们目前不测试任何其他代理。

本概述假定你已经熟悉 AMQP 规范的基础知识。如果没有,请查看其他资源中列出的资源。

# Message

0-9-1AMQP 规范没有定义Message类或接口。相反,当执行basicPublish()之类的操作时,内容将作为字节数组参数传递,其他属性将作为单独的参数传递。 Spring AMQP 将Message类定义为更通用的 AMQP 域模型表示的一部分。Message类的目的是将主体和属性封装在一个实例中,这样 API 就可以变得更简单。下面的示例显示了Message类定义:

public class Message {

    private final MessageProperties messageProperties;

    private final byte[] body;

    public Message(byte[] body, MessageProperties messageProperties) {
        this.body = body;
        this.messageProperties = messageProperties;
    }

    public byte[] getBody() {
        return this.body;
    }

    public MessageProperties getMessageProperties() {
        return this.messageProperties;
    }
}

MessageProperties接口定义了几个常见的属性,如“messageID”、“timestamp”、“ContentType”等。你还可以通过调用setHeader(String key, Object value)方法,使用用户定义的“headers”扩展这些属性。

从版本1.5.71.6.111.7.42.0.0开始,如果消息体是序列化的SerializableJava 对象,则在执行toString()操作(例如在日志消息中)时,它不再被反序列化(默认情况下)。
这是为了防止不安全的反序列化。
默认情况下,只有java.utiljava.lang类被反序列化。
要恢复到以前的行为,你可以通过调用Message.addAllowedListPatterns(…​)来添加允许的类/包模式。
支持简单的``通配符,例如com.something., *.MyClass
不能反序列化的主体在日志消息中用byte[<size>]表示。
# exchange

Exchange接口表示一个 AMQP 交换,这是消息生成器发送到的内容。在代理的虚拟主机中,每个交换都有一个唯一的名称以及一些其他属性。下面的示例显示了Exchange接口:

public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}

正如你所看到的,Exchange还具有由ExchangeTypes中定义的常量表示的“类型”。基本类型是:directtopicfanout,和headers。在核心包中,你可以为每种类型找到Exchange接口的实现。这些Exchange类型的行为在它们处理与队列绑定的方式方面有所不同。例如,Direct交换允许队列由固定的路由密钥(通常是队列的名称)绑定。TopicExchange 支持使用路由模式的绑定,其中可能包括“exactly-one”和“zero-or-more”的“*”和“#”通配符。FanoutExchange 将发布到绑定到它的所有队列,而不考虑任何路由密钥。有关这些和其他交换类型的更多信息,请参见其他资源

AMQP 规范还要求任何代理提供没有名称的“默认”直接交换。
所有声明的队列都绑定到默认的Exchange,并将其名称作为路由键。
你可以在[AmqpTemplate](#AMQP-template)中了解 Spring AMQP 中默认交换的使用情况。
# 队列

Queue类表示消息使用者从其接收消息的组件。像各种Exchange类一样,我们的实现旨在作为这种核心 AMQP 类型的抽象表示。下面的清单显示了Queue类:

public class Queue  {

    private final String name;

    private volatile boolean durable;

    private volatile boolean exclusive;

    private volatile boolean autoDelete;

    private volatile Map<String, Object> arguments;

    /**
     * The queue is durable, non-exclusive and non auto-delete.
     *
     * @param name the name of the queue.
     */
    public Queue(String name) {
        this(name, true, false, false);
    }

    // Getters and Setters omitted for brevity

}

请注意,构造函数使用队列名。根据实现的不同,管理模板可以提供生成唯一命名的队列的方法。这样的队列可以作为“回复”地址或在其他临时的情况下很有用。因此,自动生成队列的“独占”和“自动删除”属性都将设置为“true”。

有关使用名称空间支持声明队列的信息,包括队列参数,请参见配置代理中有关队列的部分。
# 绑定

考虑到生产者向交易所发送消息,而消费者从队列接收消息,将队列连接到交易所的绑定对于通过消息传递将生产者和消费者连接起来至关重要。在 Spring AMQP 中,我们定义了一个Binding类来表示这些连接。本节回顾了将队列绑定到交易所的基本选项。

你可以使用固定的路由密钥将队列绑定到DirectExchange,如下例所示:

new Binding(someQueue, someDirectExchange, "foo.bar");

可以使用路由模式将队列绑定到TopicExchange,如下例所示:

new Binding(someQueue, someTopicExchange, "foo.*");

可以将队列绑定到没有路由密钥的FanoutExchange,如下例所示:

new Binding(someQueue, someFanoutExchange);

我们还提供了BindingBuilder以促进“Fluent API”风格,如下例所示:

Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
为了清楚起见,前面的示例显示了BindingBuilder类,但是当为“bind()”方法使用静态导入时,这种样式很好用。

就其本身而言,Binding类的实例仅保存有关连接的数据。换句话说,它不是一个“活跃”的组件。但是,正如你将在后面的配置代理中看到的那样,AmqpAdmin类可以使用Binding实例来实际触发代理上的绑定操作。此外,正如你在同一部分中看到的那样,你可以在@Configuration类中使用 Spring 的@Bean注释来定义Binding实例。 Bean 还有一种方便的基类,它进一步简化了用于生成与 AMQP 相关的定义的方法,并识别队列、交换和绑定,以便它们在应用程序启动时都在 AMQP 代理上声明。

AmqpTemplate也在核心包中定义。作为实际 AMQP 消息传递所涉及的主要组件之一,它在其自己的部分中进行了详细讨论(参见[AmqpTemplate](#AMQP-template))。

# 4.1.2.连接和资源管理

尽管我们在上一节中描述的 AMQP 模型是通用的,并且适用于所有实现,但是当我们进入资源管理时,细节是特定于代理实现的。因此,在本节中,我们将重点关注仅存在于我们的“ Spring-Rabbit”模块中的代码,因为在这一点上,RabbitMQ 是唯一受支持的实现。

管理到 RabbitMQ 代理的连接的中心组件是ConnectionFactory接口。ConnectionFactory实现的职责是提供org.springframework.amqp.rabbit.connection.Connection的实例,它是com.rabbitmq.client.Connection的包装器。

# 选择连接工厂

有三家连接工厂可供选择。

  • PooledChannelConnectionFactory

  • ThreadChannelConnectionFactory

  • CachingConnectionFactory

前两个是在 2.3 版本中添加的。

对于大多数用例,应该使用PooledChannelConnectionFactory。如果你希望确保严格的消息排序,而不需要使用作用域操作,则可以使用ThreadChannelConnectionFactory。如果你想要使用相关的发布者确认,或者如果你希望通过其CacheMode打开多个连接,则应该使用CachingConnectionFactory

这三家工厂都支持简单的发布者确认。

在配置RabbitTemplate以使用独立连接时,现在可以从版本 2.3.2 开始,将发布连接工厂配置为不同的类型。默认情况下,发布工厂是相同的类型,主工厂上设置的任何属性也会传播到发布工厂。

# PooledChannelConnectionFactory

该工厂基于 Apache 池 2 管理单个连接和两个通道池。一个池用于事务通道,另一个池用于非事务通道。池是带有默认配置的GenericObjectPools;提供了一个回调来配置池;有关更多信息,请参阅 Apache 文档。

Apache commons-pool2jar 必须位于类路径上才能使用这个工厂。

@Bean
PooledChannelConnectionFactory pcf() throws Exception {
    ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
    rabbitConnectionFactory.setHost("localhost");
    PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory);
    pcf.setPoolConfigurer((pool, tx) -> {
        if (tx) {
            // configure the transactional pool
        }
        else {
            // configure the non-transactional pool
        }
    });
    return pcf;
}
# ThreadChannelConnectionFactory

这个工厂管理一个连接和两个ThreadLocals,一个用于事务通道,另一个用于非事务通道。这个工厂确保同一线程上的所有操作使用相同的通道(只要它保持打开状态)。这便于在不需要作用域操作的情况下进行严格的消息排序。为了避免内存泄漏,如果你的应用程序使用许多短期线程,你必须调用工厂的closeThreadChannel()来释放通道资源。从版本 2.3.7 开始,一个线程可以将其通道传输到另一个线程。有关更多信息,请参见多线程环境中的严格消息排序

# CachingConnectionFactory

提供的第三个实现是CachingConnectionFactory,默认情况下,它建立一个可以由应用程序共享的单个连接代理。共享连接是可能的,因为与 AMQP 进行消息传递的“工作单元”实际上是一个“通道”(在某些方面,这类似于 JMS 中的连接与会话之间的关系)。连接实例提供了createChannel方法。CachingConnectionFactory实现支持对这些通道的缓存,并且它根据通道是否是事务性的,为它们维护单独的缓存。在创建CachingConnectionFactory实例时,可以通过构造函数提供“hostname”。你还应该提供“用户名”和“密码”属性。要配置通道缓存的大小(默认为 25),可以调用setChannelCacheSize()方法。

从版本 1.3 开始,你可以将CachingConnectionFactory配置为缓存连接以及仅缓存通道。在这种情况下,对createConnection()的每次调用都会创建一个新的连接(或从缓存中检索一个空闲的连接)。关闭连接将其返回到缓存(如果未达到缓存大小)。在这种连接上创建的通道也会被缓存。在某些环境中,使用单独的连接可能是有用的,例如从 HA 集群消费,与负载均衡器结合,以连接到不同的集群成员,以及其他环境。要缓存连接,请将cacheMode设置为CacheMode.CONNECTION

这并不限制连接的数量。
相反,它指定了允许多少空闲的打开连接。

从版本 1.5.5 开始,提供了一个名为connectionLimit的新属性。设置此属性时,将限制允许的连接总数。设置时,如果达到限制,则使用channelCheckoutTimeLimit来等待连接变为空闲。如果超过了时间,则抛出一个AmqpTimeoutException

当缓存模式CONNECTION时,不支持自动声明队列和其他
(参见交换、队列和绑定的自动声明)。

此外,在编写本文时,默认情况下,amqp-client库为每个连接创建一个固定的线程池(默认大小:Runtime.getRuntime().availableProcessors() * 2线程)。
当使用大量连接时,你应该考虑在CachingConnectionFactory上设置一个自定义的executor
然后,相同的执行器可以被所有的连接使用,并且它的线程可以被共享。
执行器的线程池应该是无界的,或者为预期的使用进行适当的设置(通常,每个连接至少有一个线程)。
如果在每个连接上创建了多个通道,池的大小会影响并发性,因此变量(或简单缓存的)线程池执行器将是最合适的。

重要的是要理解,缓存大小(默认情况下)不是一个限制,而仅仅是可以缓存的通道数量。如果缓存大小为 10,那么实际上可以使用任意数量的通道。如果使用了超过 10 个通道,并且它们都返回到缓存中,则在缓存中使用 10 个。其余部分是封闭的。

从 1.6 版本开始,默认通道缓存大小从 1 增加到 25.在大容量、多线程的环境中,较小的缓存意味着以较高的速率创建和关闭通道。增加默认的缓存大小可以避免这种开销。你应该通过 RabbitMQ 管理 UI 监视正在使用的通道,并且如果你看到许多通道正在创建和关闭,请考虑进一步增加缓存大小。缓存仅按需增长(以满足应用程序的并发性需求),因此此更改不会影响现有的低容量应用程序。

从版本 1.4.2 开始,CachingConnectionFactory具有一个名为channelCheckoutTimeout的属性。当此属性大于零时,channelCacheSize将成为连接上可以创建的通道数量的限制。如果达到限制,则调用线程块,直到通道可用或达到超时为止,在这种情况下,将抛出AmqpTimeoutException

框架内使用的通道(例如,RabbitTemplate)将可靠地返回到缓存。
如果你在框架外创建通道,(例如,
通过直接访问连接并调用createChannel()),则必须可靠地(通过关闭)返回它们,可能是在finally块中,以避免渠道耗尽。

下面的示例展示了如何创建一个新的connection:

CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");

Connection connection = connectionFactory.createConnection();

在使用 XML 时,配置可能看起来像以下示例:

<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
    <constructor-arg value="somehost"/>
    <property name="username" value="guest"/>
    <property name="password" value="guest"/>
</bean>
还有一个SingleConnectionFactory实现,它仅在框架的单元测试代码中可用。
它比CachingConnectionFactory更简单,因为它不缓存通道,但由于缺乏性能和弹性,它并不打算用于简单测试之外的实际使用。
如果出于某种原因需要实现自己的ConnectionFactoryAbstractConnectionFactory基类可能提供了一个很好的起点。

使用 Rabbit 名称空间可以快速方便地创建ConnectionFactory,如下所示:

<rabbit:connection-factory id="connectionFactory"/>

在大多数情况下,这种方法更可取,因为框架可以为你选择最好的默认值。创建的实例是CachingConnectionFactory。请记住,通道的默认缓存大小是 25.如果你希望有更多的通道是 cachedm,那么可以通过设置“channelcachesize”属性来设置一个更大的值。在 XML 中,它将如下所示:

<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
    <constructor-arg value="somehost"/>
    <property name="username" value="guest"/>
    <property name="password" value="guest"/>
    <property name="channelCacheSize" value="50"/>
</bean>

此外,在名称空间中,你可以添加“channel-cache-size”属性,如下所示:

<rabbit:connection-factory
    id="connectionFactory" channel-cache-size="50"/>

默认的缓存模式是CHANNEL,但你可以将其配置为缓存连接。在下面的示例中,我们使用connection-cache-size:

<rabbit:connection-factory
    id="connectionFactory" cache-mode="CONNECTION" connection-cache-size="25"/>

你可以使用命名空间提供主机和端口属性,如下所示:

<rabbit:connection-factory
    id="connectionFactory" host="somehost" port="5672"/>

或者,如果在集群环境中运行,则可以使用 Addresses 属性,如下所示:

<rabbit:connection-factory
    id="connectionFactory" addresses="host1:5672,host2:5672" address-shuffle-mode="RANDOM"/>

有关address-shuffle-mode的信息,请参见连接到集群

下面是一个自定义线程工厂的示例,该工厂使用rabbitmq-为线程名称添加前缀:

<rabbit:connection-factory id="multiHost" virtual-host="/bar" addresses="host1:1234,host2,host3:4567"
    thread-factory="tf"
    channel-cache-size="10" username="user" password="password" />

<bean id="tf" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
    <constructor-arg value="rabbitmq-" />
</bean>
# addresolver

从版本 2.1.15 开始,你现在可以使用AddressResover来解析连接地址。这将覆盖addresseshost/port属性的任何设置。

# 命名连接

从版本 1.7 开始,为注入AbstractionConnectionFactory提供了一个ConnectionNameStrategy。生成的名称用于特定于应用程序的目标 RabbitMQ 连接的标识。如果 RabbitMQ 服务器支持该连接名,则会在管理 UI 中显示该连接名。这个值不一定是唯一的,也不能用作连接标识符——例如,在 HTTPAPI 请求中。这个值应该是人类可读的,并且是connection_name键下ClientProperties的一部分。你可以使用一个简单的 lambda,如下所示:

connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION");

ConnectionFactory参数可用于通过某种逻辑来区分目标连接名。默认情况下,AbstractConnectionFactorybeanName、表示对象的十六进制字符串和内部计数器用于生成connection_name<rabbit:connection-factory>名称空间组件还提供了connection-name-strategy属性。

SimplePropertyValueConnectionNameStrategy的实现将连接名设置为应用程序属性。你可以将其声明为@Bean,并将其注入到连接工厂中,如下例所示:

@Bean
public SimplePropertyValueConnectionNameStrategy cns() {
    return new SimplePropertyValueConnectionNameStrategy("spring.application.name");
}

@Bean
public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    ...
    connectionFactory.setConnectionNameStrategy(cns);
    return connectionFactory;
}

属性必须存在于应用程序上下文的Environment中。

当使用 Spring 引导及其自动配置的连接工厂时,只需要声明ConnectionNameStrategy``@Bean
引导自动检测 Bean 并将其连接到工厂。
# 阻塞的连接和资源约束

内存闹钟 (opens new window)对应的代理可能会阻止连接以进行交互。从版本 2.0 开始,org.springframework.amqp.rabbit.connection.Connection可以提供com.rabbitmq.client.BlockedListener实例,以通知连接阻塞和未阻塞事件。此外,AbstractConnectionFactory分别通过其内部的BlockedListener实现了ConnectionBlockedEventConnectionUnblockedEvent。这些允许你提供应用程序逻辑,以便对代理上的问题做出适当的反应,并(例如)采取一些纠正措施。

当应用程序被配置为单个CachingConnectionFactory时,就像默认的 Spring 引导自动配置一样,当连接被代理阻塞时,应用程序停止工作。
并且当它被代理阻塞时,它的任何客户机停止工作。
如果我们在同一个应用程序中有生产者和消费者,当生产者阻塞连接时(因为代理上不再有资源了),而消费者无法释放它们(因为连接被阻塞了),我们最终可能会陷入死锁。,
为了缓解这个问题,我们建议再有一个单独的CachingConnectionFactory实例,具有相同的选项——一个用于生产者,一个用于消费者,
对于在消费者线程上执行的事务生产者,单独的CachingConnectionFactory是不可能的,因为它们应该重用与消费者事务关联的Channel

从版本 2.0.2 开始,RabbitTemplate有一个配置选项,可以自动使用第二个连接工厂,除非正在使用事务。有关更多信息,请参见使用单独的连接。发布者连接的ConnectionNameStrategy与主策略相同,将.publisher附加到调用方法的结果中。

从版本 1.7.7 开始,将提供一个AmqpResourceNotAvailableException,当SimpleConnection.createChannel()无法创建Channel时,将抛出该参数(例如,因为已达到channelMax限制,并且缓存中没有可用通道)。你可以在RetryPolicy中使用此异常来恢复一些后退后的操作。

# 配置底层客户端连接工厂

CachingConnectionFactory使用了 Rabbit 客户机ConnectionFactory的实例。在CachingConnectionFactory上设置等效属性时,会传递许多配置属性(例如host, port, userName, password, requestedHeartBeat, and connectionTimeout)。要设置其他属性(例如clientProperties),你可以定义 Rabbit Factory 的实例,并使用CachingConnectionFactory的适当构造函数提供对它的引用。当使用名称空间(如前所述)时,你需要在connection-factory属性中提供对已配置工厂的引用。为了方便起见,提供了工厂 Bean 以协助在 Spring 应用程序上下文中配置连接工厂,如下一节中所讨论的。

<rabbit:connection-factory
      id="connectionFactory" connection-factory="rabbitConnectionFactory"/>
4.0.x 客户端默认支持自动恢复。
虽然与此功能兼容, Spring AMQP 有自己的恢复机制,通常不需要客户端恢复功能。
我们建议禁用amqp-client自动恢复,为了避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException实例,你可能会注意到此异常,例如,当RetryTemplateRabbitTemplate中配置了RetryTemplate时,
由于自动恢复连接在定时器上恢复,因此可以通过使用 Spring AMQP 的恢复机制更快地恢复连接,
从 1.7.1 版本开始, Spring AMQP 禁用amqp-client自动恢复,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory
RabbitMQConnectionFactoryRabbitConnectionFactoryBean创建的实例还具有默认禁用的选项。
# RabbitConnectionFactoryBean和配置 SSL

从版本 1.4 开始,提供了一个方便的RabbitConnectionFactoryBean,以便通过使用依赖项注入在底层客户机连接工厂上方便地配置 SSL 属性。其他设置者将委托给底层工厂。以前,你必须以编程方式配置 SSL 选项。下面的示例展示了如何配置RabbitConnectionFactoryBean:

<rabbit:connection-factory id="rabbitConnectionFactory"
    connection-factory="clientConnectionFactory"
    host="${host}"
    port="${port}"
    virtual-host="${vhost}"
    username="${username}" password="${password}" />

<bean id="clientConnectionFactory"
        class="org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean">
    <property name="useSSL" value="true" />
    <property name="sslPropertiesLocation" value="file:/secrets/rabbitSSL.properties"/>
</bean>

有关配置 SSL 的信息,请参见RabbitMQ 文档 (opens new window)。省略keyStoretrustStore配置以在没有证书验证的情况下通过 SSL 连接。下一个示例展示了如何提供密钥和信任存储区配置。

sslPropertiesLocation属性是一个 Spring Resource指向包含以下键的属性文件:

keyStore=file:/secret/keycert.p12
trustStore=file:/secret/trustStore
keyStore.passPhrase=secret
trustStore.passPhrase=secret

keyStoretruststore是指向存储的 Spring Resources。通常,这个属性文件是由具有读访问权限的应用程序的操作系统保护的。

从 Spring AMQP 版本 1.5 开始,你可以直接在工厂 Bean 上设置这些属性。如果同时提供离散属性和sslPropertiesLocation,则后者中的属性将覆盖离散值。

从版本 2.0 开始,默认情况下服务器证书是经过验证的,因为它更安全。,如果你出于某种原因希望跳过此验证,请将工厂 Bean 的skipServerCertificateValidation属性设置为true
从版本 2.1 开始,RabbitConnectionFactoryBean现在默认调用enableHostnameVerification()
要恢复到以前的行为,请将enableHostnameVerification属性设置为false
从版本 2.2.5 开始,工厂 Bean 默认情况下将始终使用 TLSv1.2;以前,它在某些情况下使用 v1.1,在其他情况下使用 v1.2(取决于其他属性)。<gtr="524"/>如果出于某种原因需要使用 v1.1,请设置<gtr="522"/>属性:<gtr="523"/>。
# 连接到集群

要连接到群集,请在CachingConnectionFactory上配置addresses属性:

@Bean
public CachingConnectionFactory ccf() {
    CachingConnectionFactory ccf = new CachingConnectionFactory();
    ccf.setAddresses("host1:5672,host2:5672,host3:5672");
    return ccf;
}

每当建立新的连接时,底层连接工厂将尝试连接到每个主机。从版本 2.1.8 开始,通过将addressShuffleMode属性设置为RANDOM,可以使连接顺序是随机的;在创建任何新连接之前,将应用 shuffle。从版本 2.6 开始,添加了INORDERshuffle 模式,这意味着在创建连接后,第一个地址将被移动到末尾。如果你希望从所有节点上的所有碎片中消费,那么你可能希望在RabbitMQ 分片插件 (opens new window)CacheMode.CONNECTION之间使用此模式,并使用适当的并发性。

@Bean
public CachingConnectionFactory ccf() {
    CachingConnectionFactory ccf = new CachingConnectionFactory();
    ccf.setAddresses("host1:5672,host2:5672,host3:5672");
    ccf.setAddressShuffleMode(AddressShuffleMode.RANDOM);
    return ccf;
}
# 路由连接工厂

从 1.3 版本开始,引入了AbstractRoutingConnectionFactory。这个工厂提供了一种机制来配置几个ConnectionFactories的映射,并在运行时通过一些lookupKey确定一个目标ConnectionFactory。通常,实现会检查线程绑定的上下文。为了方便起见, Spring AMQP 提供了SimpleRoutingConnectionFactory,它从SimpleResourceHolder获得当前线程绑定的lookupKey。以下示例展示了如何在 XML 和 Java 中配置SimpleRoutingConnectionFactory:

<bean id="connectionFactory"
      class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
    <property name="targetConnectionFactories">
        <map>
            <entry key="#{connectionFactory1.virtualHost}" ref="connectionFactory1"/>
            <entry key="#{connectionFactory2.virtualHost}" ref="connectionFactory2"/>
        </map>
    </property>
</bean>

<rabbit:template id="template" connection-factory="connectionFactory" />
public class MyService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void service(String vHost, String payload) {
        SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);
        rabbitTemplate.convertAndSend(payload);
        SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());
    }

}

在使用后解除对资源的绑定是很重要的。有关更多信息,请参见JavaDoc (opens new window)AbstractRoutingConnectionFactory

从版本 1.4 开始,RabbitTemplate支持 SPELsendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpression属性,这些属性在每个 AMQP 协议交互操作(sendsendAndReceivereceivereceiveAndReply)上进行评估,将所提供的lookupKey值解析为AbstractRoutingConnectionFactory。你可以在表达式中使用 Bean 引用,例如@vHostResolver.getVHost(#root)。对于send操作,要发送的消息是根求值对象。对于receive操作,queueName是根求值对象。

该路由算法如下:如果选择器表达式是null或者是被求值为null或者所提供的ConnectionFactory不是AbstractRoutingConnectionFactory的实例,则一切都像以前一样工作,依赖于所提供的ConnectionFactory实现。如果评估结果不是null,但是对于lookupKey没有目标ConnectionFactory,并且AbstractRoutingConnectionFactory配置为lenientFallback = true,则会发生相同的情况。在AbstractRoutingConnectionFactory的情况下,它确实回退到基于determineCurrentLookupKey()routing实现。但是,如果lenientFallback = false,则抛出一个IllegalStateException

名称空间支持还在<rabbit:template>组件上提供send-connection-factory-selector-expressionreceive-connection-factory-selector-expression属性。

此外,从版本 1.4 开始,你可以在侦听器容器中配置路由连接工厂。在这种情况下,队列名称列表被用作查找键。例如,如果你使用setQueueNames("thing1", "thing2")配置容器,那么查找键是[thing1,thing]"(请注意,该键中没有空格)。

从 1.6.9 版本开始,你可以在侦听器容器上使用setLookupKeyQualifier向查找键添加限定符。例如,这样做可以监听具有相同名称但位于不同虚拟主机中的队列(在该虚拟主机中,每个虚拟主机都有一个连接工厂)。

例如,使用查找键限定符thing1和监听队列thing2的容器,可以用来注册目标连接工厂的查找键可以是thing1[thing2]

目标(如果提供的话,也是默认的)连接工厂必须具有相同的 Publisher 确认和返回设置。
参见发布者确认并返回
# 队列亲和性和LocalizedQueueConnectionFactory

在集群中使用 HA 队列时,为了获得最佳性能,你可能希望连接到领先队列所在的物理代理。CachingConnectionFactory可以配置多个代理地址。这是故障转移,客户端尝试按顺序连接。LocalizedQueueConnectionFactory使用管理插件提供的 REST API 来确定哪个节点是队列的领头节点。然后,它创建(或从缓存检索)一个CachingConnectionFactory,该节点仅连接到该节点。如果连接失败,则确定新的引导节点,并由使用者连接到该节点。LocalizedQueueConnectionFactory配置了默认的连接工厂,以防无法确定队列的物理位置,在这种情况下,它将正常地连接到集群。

LocalizedQueueConnectionFactoryRoutingConnectionFactory,而SimpleMessageListenerContainer使用队列名称作为查找键,如上面路由连接工厂中讨论的那样。

由于这个原因(使用队列名进行查找),只有当容器被配置为侦听单个队列时,才能使用LocalizedQueueConnectionFactory
必须在每个节点上启用 RabbitMQ 管理插件。
这个连接工厂用于长寿命的连接,例如SimpleMessageListenerContainer使用的连接。
它不用于短连接,例如使用RabbitTemplate,因为在建立连接之前调用 REST API 的开销很大,
也用于发布操作,队列是未知的,并且消息无论如何都会发布给所有集群成员,因此查找节点的逻辑没有什么价值。

下面的配置示例展示了如何配置工厂:

@Autowired
private ConfigurationProperties props;

@Bean
public CachingConnectionFactory defaultConnectionFactory() {
    CachingConnectionFactory cf = new CachingConnectionFactory();
    cf.setAddresses(this.props.getAddresses());
    cf.setUsername(this.props.getUsername());
    cf.setPassword(this.props.getPassword());
    cf.setVirtualHost(this.props.getVirtualHost());
    return cf;
}

@Bean
public LocalizedQueueConnectionFactory queueAffinityCF(
        @Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) {
    return new LocalizedQueueConnectionFactory(defaultCF,
            StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()),
            StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()),
            StringUtils.commaDelimitedListToStringArray(this.props.getNodes()),
            this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(),
            false, null);
}

注意,前三个参数是addressesadminUrisnodes的数组。这些位置在于,当容器试图连接到队列时,它使用 Admin API 来确定哪个节点是队列的领导者,并以与该节点相同的阵列位置连接到该地址。

# 发布者确认并返回

通过将CachingConnectionFactory属性publisherConfirmType设置为ConfirmType.CORRELATED,并将publisherReturns属性设置为“true”,可以支持确认(具有相关性)和返回的消息。

当设置这些选项时,由工厂创建的Channel实例被包装在PublisherCallbackChannel中,这用于促进回调。当获得这样的信道时,客户端可以将PublisherCallbackChannel.Listener注册为ChannelPublisherCallbackChannel实现包含将确认或返回路由到适当侦听器的逻辑。这些特性将在下面的小节中进一步解释。

另见simplePublisherConfirmsin作用域操作

有关更多的背景信息,请参见 RabbitMQ 团队的博客文章介绍出版商确认 (opens new window)
# 连接和通道侦听器

连接工厂支持注册ConnectionListenerChannelListener实现。这允许你接收关于连接和通道相关事件的通知。(ConnectionListener用于RabbitAdmin在建立连接时执行声明-有关更多信息,请参见交换、队列和绑定的自动声明)。下面的清单显示了ConnectionListener接口定义:

@FunctionalInterface
public interface ConnectionListener {

    void onCreate(Connection connection);

    default void onClose(Connection connection) {
    }

    default void onShutDown(ShutdownSignalException signal) {
    }

}

从版本 2.0 开始,org.springframework.amqp.rabbit.connection.Connection对象可以提供com.rabbitmq.client.BlockedListener实例,以通知连接阻塞和未阻塞事件。下面的示例展示了 Channellistener 接口定义:

@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}

关于可能需要注册ChannelListener的一种情况,请参见发布是异步的——如何检测成功和失败

# 记录通道关闭事件

版本 1.5 引入了一种机制,使用户能够控制日志记录级别。

CachingConnectionFactory使用默认策略记录通道闭包,如下所示:

  • 正常的通道关闭(200OK)没有记录.

  • 如果由于被动队列声明失败而关闭了通道,则将在调试级别记录该通道。

  • 如果一个通道由于basic.consume由于独占消费者条件而被拒绝而被关闭,则该通道将在 Info 级别记录。

  • 所有其他记录在错误级别。

要修改此行为,可以在其closeExceptionLogger属性中将自定义ConditionalExceptionLogger插入到CachingConnectionFactory中。

另见消费者活动

# 运行时缓存属性

从版本 1.6 开始,CachingConnectionFactory现在通过getCacheProperties()方法提供缓存统计信息。这些统计信息可用于优化缓存,以在生产中对其进行优化。例如,可以使用高水位标记来确定是否应该增加缓存大小。如果它等于缓存大小,你可能需要考虑进一步增加。下表描述了CacheMode.CHANNEL属性:

Property 意义
<br/>connectionName<br/> ConnectionNameStrategy生成的连接的名称。
<br/>channelCacheSize<br/> 当前配置的允许空闲的最大通道。
<br/>localPort<br/> 连接的本地端口(如果可用)。
这可用于与 RabbitMQ 管理 UI 上的连接和通道关联。
<br/>idleChannelsTx<br/> 当前空闲(缓存)的事务通道的数量。
<br/>idleChannelsNotTx<br/> 当前空闲(缓存)的非事务通道的数量。
<br/>idleChannelsTxHighWater<br/> 已并发空闲(缓存)的事务通道的最大数量。
<br/>idleChannelsNotTxHighWater<br/> 已并发空闲(缓存)的非事务通道的最大数量。

下表描述了CacheMode.CONNECTION属性:

Property 意义
<br/>connectionName:<localPort><br/> ConnectionNameStrategy生成的连接的名称。
<br/>openConnections<br/> 表示到代理的连接的连接对象的数量。
<br/>channelCacheSize<br/> 当前配置的允许空闲的最大通道。
<br/>connectionCacheSize<br/> 当前配置的允许空闲的最大连接数.
<br/>idleConnections<br/> 当前空闲的连接数。
<br/>idleConnectionsHighWater<br/> 并发空闲的连接的最大数量。
<br/>idleChannelsTx:<localPort><br/> 此连接当前空闲(缓存)的事务通道的数量。
你可以使用属性名称的localPort部分来关联 RabbitMQ 管理 UI 上的连接和通道。
<br/>idleChannelsNotTx:<localPort><br/> 此连接当前空闲(缓存)的非事务通道的数量。
属性名称的localPort部分可用于与 RabbitMQ 管理 UI 上的连接和通道关联。
<br/>idleChannelsTxHighWater:<localPort><br/> 并发空闲(缓存)的事务通道的最大数量。
属性名称的 localport 部分可用于与 RabbitMQ 管理 UI 上的连接和通道关联。
<br/>idleChannelsNotTxHighWater:<localPort><br/> 非事务通道的最大数量已同时空闲(缓存)。
你可以使用属性名称的localPort部分来关联 RabbitMQ 管理 UI 上的连接和通道。

还包括cacheMode属性(CHANNELCONNECTION)。

cacheStats

图 1.JVisualVM 示例

# RabbitMQ 自动连接/拓扑恢复

自 Spring AMQP 的第一个版本以来,该框架已经在代理失败的情况下提供了自己的连接和通道恢复。同样,如配置代理中所讨论的,RabbitAdmin在重新建立连接时重新声明任何基础设施 bean(队列和其他)。因此,它不依赖现在由amqp-client库提供的自动恢复 (opens new window)。 Spring AMQP 现在使用4.0.xamqp-client版本,该版本在默认情况下具有启用的自动恢复功能。 Spring 如果你愿意,AMQP 仍然可以使用其自己的恢复机制,在客户端中禁用它,(通过将底层automaticRecoveryEnabled上的RabbitMQ connectionFactory属性设置为false)。然而,该框架与启用的自动恢复完全兼容。这意味着你在代码中创建的任何消费者(可能通过RabbitTemplate.execute())都可以自动恢复。

只有被定义为 bean 的元素(队列、交换、绑定)在连接失败后才会重新声明。
直接从用户代码调用RabbitAdmin.declare*()方法声明的元素对于框架来说是未知的,因此无法恢复。
如果你需要变量数量的声明,考虑定义类型Declarables的 Bean 或 bean,如声明交换、队列和绑定的集合中讨论的那样。

# 4.1.3.添加自定义客户端连接属性

现在,CachingConnectionFactory允许你访问底层连接工厂,以允许例如设置自定义客户机属性。下面的示例展示了如何做到这一点:

connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2");

当查看连接时,这些属性会出现在 RabbitMQ 管理 UI 中。

# 4.1.4.AmqpTemplate

与 Spring 框架和相关项目提供的许多其他高级抽象一样, Spring AMQP 提供了一个发挥核心作用的“模板”。定义主要操作的接口称为AmqpTemplate。这些操作涵盖了发送和接收消息的一般行为。换句话说,它们不是任何实现所独有的——因此名称中的“AMQP”。另一方面,该接口的一些实现与 AMQP 协议的实现相关联。与 JMS 本身是一个接口级 API 不同,AMQP 是一个线路级协议。该协议的实现提供了自己的客户端库,因此模板接口的每个实现都依赖于特定的客户端库。目前,只有一个实现:RabbitTemplate。在下面的示例中,我们经常使用AmqpTemplate。然而,当你查看配置示例或在其中实例化模板或调用 setter 的任何代码摘录时,你可以看到实现类型(例如,RabbitTemplate)。

如前所述,AmqpTemplate接口定义了用于发送和接收消息的所有基本操作。我们将分别在发送消息接收消息中研究消息的发送和接收。

另见异步兔子模板

# 添加重试功能

从版本 1.3 开始,你现在可以将RabbitTemplate配置为使用RetryTemplate来帮助处理代理连接问题。有关完整信息,请参见spring-retry (opens new window)项目。下面只是一个使用指数后退策略和默认SimpleRetryPolicy的示例,它在向调用方抛出异常之前进行了三次尝试。

下面的示例使用 XML 名称空间:

<rabbit:template id="template" connection-factory="connectionFactory" retry-template="retryTemplate"/>

<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
    <property name="backOffPolicy">
        <bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
            <property name="initialInterval" value="500" />
            <property name="multiplier" value="10.0" />
            <property name="maxInterval" value="10000" />
        </bean>
    </property>
</bean>

下面的示例使用了 Java 中的@Configuration注释:

@Bean
public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    RetryTemplate retryTemplate = new RetryTemplate();
    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(500);
    backOffPolicy.setMultiplier(10.0);
    backOffPolicy.setMaxInterval(10000);
    retryTemplate.setBackOffPolicy(backOffPolicy);
    template.setRetryTemplate(retryTemplate);
    return template;
}

从版本 1.4 开始,除了retryTemplate属性外,recoveryCallback选项还支持RabbitTemplate选项。它被用作RetryTemplate.execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)的第二个参数。

RecoveryCallback在某种程度上是有限的,因为重试上下文只包含lastThrowable字段。,对于更复杂的用例,
,你应该使用一个外部RetryTemplate,这样你就可以通过上下文的属性向RecoveryCallback传递额外的信息。
下面的示例展示了如何做到这一点:
retryTemplate.execute(
    new RetryCallback<Object, Exception>() {

        @Override
        public Object doWithRetry(RetryContext context) throws Exception {
            context.setAttribute("message", message);
            return rabbitTemplate.convertAndSend(exchange, routingKey, message);
        }

    }, new RecoveryCallback<Object>() {

        @Override
        public Object recover(RetryContext context) throws Exception {
            Object message = context.getAttribute("message");
            Throwable t = context.getLastThrowable();
            // Do something with message
            return null;
        }
    });
}

在这种情况下,将不是RetryTemplate注入到RabbitTemplate中。

# 发布是异步的——如何检测成功和失败

发布消息是一种异步机制,默认情况下,RabbitMQ 会删除无法路由的消息。对于成功的发布,你可以收到异步确认,如相关发布者确认并返回中所述。考虑两种失败场景:

  • 发布到交易所,但没有匹配的目标队列。

  • 发布到一个不存在的交易所。

第一种情况由 Publisher Returns 覆盖,如相关发布者确认并返回中所述。

对于第二种情况,消息会被删除,并且不会生成返回。底层通道已关闭,但有一个例外。默认情况下,该异常会被记录,但是你可以用ChannelListener注册CachingConnectionFactory来获得此类事件的通知。下面的示例展示了如何添加ConnectionListener:

this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

    @Override
    public void onShutDown(ShutdownSignalException signal) {
        ...
    }

});

你可以检查信号的reason属性,以确定发生的问题。

要检测发送线程上的异常,可以在RabbitTemplate上检测setChannelTransacted(true),并在txCommit()上检测异常。但是,交易大大妨碍了业绩。,因此在仅为这个用例启用事务之前,请仔细考虑这一点。

# 相关发布者确认并返回

RabbitTemplateAmqpTemplate实现支持 Publisher 确认和返回。

对于返回的消息,对于特定的消息,模板的mandatory属性必须设置为true,或者mandatory-expression必须计算为true。此功能需要一个CachingConnectionFactory,其publisherReturns属性设置为true(参见发布者确认并返回)。它通过调用setReturnsCallback(ReturnsCallback callback)来注册RabbitTemplate.ReturnsCallback,从而将返回发送到客户机。回调必须实现以下方法:

void returnedMessage(ReturnedMessage returned);

ReturnedMessage具有以下属性:

  • message-返回的消息本身

  • replyCode-指示返回原因的代码

  • replyText-返回的文本原因-例如NO_ROUTE

  • exchange-消息发送到的交换器

  • routingKey-使用的路由密钥

每个RabbitTemplate只支持一个ReturnsCallback。另见回复超时

对于 Publisher 确认(也称为 Publisher 确认),模板需要一个CachingConnectionFactory,其publisherConfirm属性设置为ConfirmType.CORRELATED。它通过调用setConfirmCallback(ConfirmCallback callback)来注册RabbitTemplate.ConfirmCallback,从而将确认信息发送到客户机。回调必须实现这个方法:

void confirm(CorrelationData correlationData, boolean ack, String cause);

CorrelationData是客户端在发送原始消息时提供的对象。对于ackack为真,对于nack,为假。对于nack实例,如果在生成nack时可用,则原因可能包含nack的原因。一个例子是,当向不存在的交易所发送消息时。在这种情况下,代理将关闭该通道。闭包的原因包括在cause中。在 1.4 版本中添加了cause

只有一个ConfirmCallbackRabbitTemplate所支持。

当 Rabbit 模板发送操作完成时,通道将关闭。
这将阻止在连接工厂缓存已满(当缓存中有空间时,通道没有物理关闭,返回和确认正常进行)。
当缓存已满时,框架将延迟关闭最多五秒钟,以便留出时间进行确认和返回。
当使用确认时,当接收到最后一次确认时,通道关闭。
当只使用返回时,通道在整整五秒钟内保持打开。
我们通常建议将连接工厂的channelCacheSize设置为足够大的值,以便将发布消息的通道返回到缓存中,而不是关闭。
你可以通过使用 RabbitMQ 管理插件来监视通道的使用情况。
如果你看到通道正在快速打开和关闭,你应该考虑增加缓存大小,以减少服务器上的开销。
在版本 2.1 之前,在收到确认信息之前,已将启用用于发布确认的通道返回到缓存。
其他一些进程可以签出该通道并执行某些操作这会导致通道关闭——例如将消息发布到一个不存在的 Exchange。
这可能会导致确认丢失。
版本 2.1 并在以后不再将信道返回到缓存中,而确认是未执行的。
RabbitTemplate在每次操作之后对信道执行逻辑close()
通常,这意味着一次在一个信道上只有一个确认是未执行的。
从版本 2.2 开始,回调是在连接工厂的executor线程之一上调用的,
这是为了避免在回调中执行兔子操作时可能出现死锁,
与以前的版本一样,回调是在amqp-client连接 I/O 线程上直接调用的;如果你执行一些 RPC 操作(例如打开一个新通道),这将导致死锁,因为 I/O 线程块在等待结果,但是结果需要由 I/O 线程本身来处理。
对于那些版本,有必要将工作(例如发送消息)交给回调中的另一个线程。
这不再是必需的,因为框架现在将回调调用交给执行器。
只要返回回调在 60 秒或更短的时间内执行,在 ACK 之前接收返回消息的保证仍然保持。
确认计划在返回回调结束之后或 60 秒之后交付,以先到者为准。

从版本 2.1 开始,CorrelationData对象有一个ListenableFuture,你可以使用它来获取结果,而不是在模板上使用ConfirmCallback。下面的示例展示了如何配置CorrelationData实例:

CorrelationData cd1 = new CorrelationData();
this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1);
assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck());

由于它是ListenableFuture<Confirm>,所以你可以在准备就绪时获得get()结果,或者为异步回调添加侦听器。Confirm对象是一个简单的 Bean,具有 2 个属性:ackreason(对于nack实例)。对于代理生成的nack实例,原因未填充。它是为框架生成的nack实例填充的(例如,在ack实例未完成时关闭连接)。

此外,当同时启用确认和返回时,CorrelationData将填充返回的消息,只要CorrelationData具有唯一的id;默认情况下,从版本 2.3 开始总是这样。可以保证,返回的消息在使用ack设置 future 之前已经设置好了。

另请参见作用域操作,以获得等待发布者确认的更简单机制。

# 作用域操作

通常,当使用模板时,一个Channel将从缓存中签出(或创建),用于操作,并返回到缓存中进行重用。在多线程环境中,不能保证下一个操作使用相同的通道。然而,有时你可能希望对通道的使用有更多的控制,并确保多个操作都在同一个通道上执行。

从版本 2.0 开始,提供了一个名为invoke的新方法,并提供了一个OperationsCallback。在回调范围内和提供的RabbitOperations参数上执行的任何操作都使用相同的专用Channel,它将在结束时关闭(不返回到缓存)。如果通道是PublisherCallbackChannel,则在接收到所有确认后将其返回到缓存中(参见相关发布者确认并返回)。

@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}

如果你希望在底层Channel上使用waitForConfirms()方法,那么你可能需要此方法的一个示例。 Spring API 以前没有公开这种方法,因为通道通常是缓存和共享的,正如前面讨论的那样。RabbitTemplate现在提供waitForConfirms(long timeout)waitForConfirmsOrDie(long timeout),将其委托给在OperationsCallback范围内使用的专用通道。出于显而易见的原因,这些方法不能在该范围之外使用。

请注意,允许你与请求进行关联确认的更高级别的抽象在其他地方提供(参见相关发布者确认并返回)。如果你只想等待代理确认交付,那么可以使用以下示例中所示的技术:

Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
    messages.forEach(m -> t.convertAndSend(ROUTE, m));
    t.waitForConfirmsOrDie(10_000);
    return true;
});

如果你希望RabbitAdmin操作在OperationsCallback范围内的同一通道上被调用,则必须使用与RabbitTemplate操作相同的RabbitTemplate构造管理。

如果模板操作已经在现有事务的范围内执行,那么前面的讨论是没有意义的,例如,当运行在一个事务侦听器容器线程上并在一个事务模板上执行操作时,
在这种情况下,该操作在该通道上执行,并在线程返回到容器时提交。
在该场景中无需使用invoke

以这种方式使用确认时,实际上并不需要为将确认与请求关联而设置的许多基础设施(除非还启用了返回)。从版本 2.2 开始,连接工厂支持一个名为publisherConfirmType的新属性。当这被设置为ConfirmType.SIMPLE时,避免了基础设施的确认,并且可以更有效地进行处理。

此外,RabbitTemplate在已发送消息MessageProperties中设置publisherSequenceNumber属性。如果你希望检查(或记录或以其他方式使用)特定的确认,则可以使用重载的invoke方法进行检查,如下例所示:

public <T> T invoke(OperationsCallback<T> action, com.rabbitmq.client.ConfirmCallback acks,
        com.rabbitmq.client.ConfirmCallback nacks);
这些ConfirmCallback对象(对于acknack实例)是 Rabbit 客户机回调,而不是模板回调。

下面的示例日志记录acknack实例:

Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
    messages.forEach(m -> t.convertAndSend(ROUTE, m));
    t.waitForConfirmsOrDie(10_000);
    return true;
}, (tag, multiple) -> {
        log.info("Ack: " + tag + ":" + multiple);
}, (tag, multiple) -> {
        log.info("Nack: " + tag + ":" + multiple);
}));
作用域操作绑定到线程。
有关多线程环境中严格排序的讨论,请参见多线程环境中的严格消息排序
# 多线程环境中的严格消息排序

作用域操作中的讨论仅在同一线程上执行操作时才适用。

考虑以下情况:

  • thread-1向队列发送消息,并将工作移交给thread-2

  • thread-2将消息发送到相同的队列

由于 RabbitMQ 的异步特性和缓存通道的使用,不确定是否使用相同的通道,因此不能保证消息到达队列的顺序。(在大多数情况下,它们会按顺序到达,但超量交付的可能性不是零)。要解决此用例,你可以使用大小1的有界通道缓存(连同channelCheckoutTimeout)来确保消息总是发布在相同的通道上,并且将保证顺序。要做到这一点,如果你对连接工厂有其他用途,例如消费者,那么你应该为模板使用专用的连接工厂,或者将模板配置为使用嵌入在主连接工厂中的发布者连接工厂(参见使用单独的连接)。

用一个简单的 Spring 引导应用程序最好地说明了这一点:

@SpringBootApplication
public class Application {

	private static final Logger log = LoggerFactory.getLogger(Application.class);

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	@Bean
	TaskExecutor exec() {
		ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
		exec.setCorePoolSize(10);
		return exec;
	}

	@Bean
	CachingConnectionFactory ccf() {
		CachingConnectionFactory ccf = new CachingConnectionFactory("localhost");
		CachingConnectionFactory publisherCF = (CachingConnectionFactory) ccf.getPublisherConnectionFactory();
		publisherCF.setChannelCacheSize(1);
		publisherCF.setChannelCheckoutTimeout(1000L);
		return ccf;
	}

	@RabbitListener(queues = "queue")
	void listen(String in) {
		log.info(in);
	}

	@Bean
	Queue queue() {
		return new Queue("queue");
	}

	@Bean
	public ApplicationRunner runner(Service service, TaskExecutor exec) {
		return args -> {
			exec.execute(() -> service.mainService("test"));
		};
	}

}

@Component
class Service {

	private static final Logger LOG = LoggerFactory.getLogger(Service.class);

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	Service(RabbitTemplate template, TaskExecutor exec) {
		template.setUsePublisherConnection(true);
		this.template = template;
		this.exec = exec;
	}

	void mainService(String toSend) {
		LOG.info("Publishing from main service");
		this.template.convertAndSend("queue", toSend);
		this.exec.execute(() -> secondaryService(toSend.toUpperCase()));
	}

	void secondaryService(String toSend) {
		LOG.info("Publishing from secondary service");
		this.template.convertAndSend("queue", toSend);
	}

}

尽管发布是在两个不同的线程上执行的,但它们都将使用相同的通道,因为缓存的上限是单个通道。

从版本 2.3.7 开始,ThreadChannelConnectionFactory支持使用prepareContextSwitchswitchContext方法将一个线程的通道转移到另一个线程。第一个方法返回一个上下文,该上下文被传递给调用第二个方法的第二个线程。线程可以有一个非事务通道,也可以有一个绑定到它的事务通道(或其中一个);除非使用两个连接工厂,否则不能单独传输它们。以下是一个例子:

@SpringBootApplication
public class Application {

	private static final Logger log = LoggerFactory.getLogger(Application.class);

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	@Bean
	TaskExecutor exec() {
		ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
		exec.setCorePoolSize(10);
		return exec;
	}

	@Bean
	ThreadChannelConnectionFactory tccf() {
		ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
		rabbitConnectionFactory.setHost("localhost");
		return new ThreadChannelConnectionFactory(rabbitConnectionFactory);
	}

	@RabbitListener(queues = "queue")
	void listen(String in) {
		log.info(in);
	}

	@Bean
	Queue queue() {
		return new Queue("queue");
	}

	@Bean
	public ApplicationRunner runner(Service service, TaskExecutor exec) {
		return args -> {
			exec.execute(() -> service.mainService("test"));
		};
	}

}

@Component
class Service {

	private static final Logger LOG = LoggerFactory.getLogger(Service.class);

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	private final ThreadChannelConnectionFactory connFactory;

	Service(RabbitTemplate template, TaskExecutor exec,
			ThreadChannelConnectionFactory tccf) {

		this.template = template;
		this.exec = exec;
		this.connFactory = tccf;
	}

	void mainService(String toSend) {
		LOG.info("Publishing from main service");
		this.template.convertAndSend("queue", toSend);
		Object context = this.connFactory.prepareSwitchContext();
		this.exec.execute(() -> secondaryService(toSend.toUpperCase(), context));
	}

	void secondaryService(String toSend, Object threadContext) {
		LOG.info("Publishing from secondary service");
		this.connFactory.switchContext(threadContext);
		this.template.convertAndSend("queue", toSend);
		this.connFactory.closeThreadChannel();
	}

}
一旦调用prepareSwitchContext,如果当前线程执行更多的操作,它们将在新的通道上执行。
当不再需要线程绑定通道时,关闭该通道非常重要。
# 消息传递集成

从版本 1.4 开始,RabbitMessagingTemplate(构建在RabbitTemplate之上)提供了与 Spring Framework 消息传递抽象的集成——即org.springframework.messaging.Message。这允许你通过使用spring-messaging``Message<?>抽象来发送和接收消息。这个抽象被其他 Spring 项目所使用,例如 Spring 集成和 Spring 的 STOMP 支持。涉及两个消息转换器:一个用于在 Spring-messagingMessage<?>和 Spring AMQP 的Message抽象之间进行转换,另一个用于在 Spring AMQP 的Message抽象和底层 RabbitMQ 客户端库所需的格式之间进行转换。默认情况下,消息有效负载由提供的RabbitTemplate实例的消息转换器进行转换。或者,可以将自定义MessagingMessageConverter与其他有效负载转换器一起注入,如下例所示:

MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter();
amqpMessageConverter.setPayloadConverter(myPayloadConverter);
rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter);
# 验证用户 ID

从版本 1.6 开始,模板现在支持user-id-expression(使用 Java 配置时userIdExpression)。如果发送了消息,则在计算该表达式后设置 User ID 属性(如果尚未设置)。评估的根对象是要发送的消息。

以下示例展示了如何使用user-id-expression属性:

<rabbit:template ... user-id-expression="'guest'" />

<rabbit:template ... user-id-expression="@myConnectionFactory.username" />

第一个例子是一个字面表达式。第二个从应用程序上下文中的连接工厂 Bean 获得username属性。

# 使用单独的连接

从版本 2.0.2 开始,你可以将usePublisherConnection属性设置为true,以便在可能的情况下使用与侦听器容器使用的不同的连接。这是为了避免当生产者由于任何原因而被封锁时,消费者被封锁。为此目的,连接工厂维护第二个内部连接工厂;默认情况下,它与主工厂的类型相同,但是如果你希望使用不同的工厂类型进行发布,则可以明确地设置该类型。如果 Rabbit 模板在侦听器容器启动的事务中运行,则使用容器的通道,而不考虑此设置。

通常,你不应该在模板中使用RabbitAdmin,而该模板将此设置为true
使用获取连接工厂的RabbitAdmin构造函数。
如果你使用另一个获取模板的构造函数,请确保模板的属性为false
这是因为,通常,管理员用于声明侦听器容器的队列。
使用属性设置为true的模板将意味着在与侦听器容器使用的连接不同的连接上声明排他队列(例如AnonymousQueue)。,在这种情况下,
,容器不能使用队列。

# 4.1.5.发送消息

在发送消息时,可以使用以下任何一种方法:

void send(Message message) throws AmqpException;

void send(String routingKey, Message message) throws AmqpException;

void send(String exchange, String routingKey, Message message) throws AmqpException;

我们可以从前面清单中的最后一个方法开始讨论,因为它实际上是最明确的方法。它允许在运行时提供 AMQP 交换名(以及路由密钥)。最后一个参数是负责实际创建消息实例的回调。使用此方法发送消息的示例可能如下所示:下面的示例展示了如何使用send方法发送消息:

amqpTemplate.send("marketData.topic", "quotes.nasdaq.THING1",
    new Message("12.34".getBytes(), someProperties));

如果你计划在大部分时间或全部时间使用该模板实例发送到相同的 Exchange,则可以在模板本身上设置exchange属性。在这种情况下,你可以使用前面列表中的第二种方法。下面的示例在功能上与前面的示例等价:

amqpTemplate.setExchange("marketData.topic");
amqpTemplate.send("quotes.nasdaq.FOO", new Message("12.34".getBytes(), someProperties));

如果在模板上同时设置了exchangeroutingKey属性,则可以使用仅接受Message的方法。下面的示例展示了如何做到这一点:

amqpTemplate.setExchange("marketData.topic");
amqpTemplate.setRoutingKey("quotes.nasdaq.FOO");
amqpTemplate.send(new Message("12.34".getBytes(), someProperties));

考虑 Exchange 和 Routing 键属性的一种更好的方法是,显式方法参数总是覆盖模板的默认值。实际上,即使你没有显式地在模板上设置这些属性,也始终存在默认值。在这两种情况下,默认值都是空的String,但这实际上是一个合理的默认值。就路由密钥而言,它首先并不总是必要的(例如,对于Fanout交换)。此外,队列可以绑定到带有空String的交换。对于模板的路由密钥属性,这两种情况都是依赖默认的空String值的合法场景。就交换名称而言,空String是常用的,因为 AMQP 规范将“默认交换”定义为没有名称。由于所有队列都自动绑定到默认的 Exchange(这是一个直接的 Exchange),使用它们的名称作为绑定值,因此前面列表中的第二个方法可以用于通过默认 Exchange 向任何队列发送简单的点对点消息。通过在运行时提供方法参数,可以将队列名提供为routingKey。下面的示例展示了如何做到这一点:

RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange
template.send("queue.helloWorld", new Message("Hello World".getBytes(), someProperties));

或者,你可以创建一个模板,该模板可以主要用于发布,也可以专门用于发布到单个队列。下面的示例展示了如何做到这一点:

RabbitTemplate template = new RabbitTemplate(); // using default no-name Exchange
template.setRoutingKey("queue.helloWorld"); // but we'll always send to this Queue
template.send(new Message("Hello World".getBytes(), someProperties));
# Message Builder API

从版本 1.3 开始,消息生成器 API 由MessageBuilderMessagePropertiesBuilder提供。这些方法为创建消息或消息属性提供了一种方便的“fluent”方法。以下示例展示了 Fluent API 的实际应用:

Message message = MessageBuilder.withBody("foo".getBytes())
    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
    .setMessageId("123")
    .setHeader("bar", "baz")
    .build();
MessageProperties props = MessagePropertiesBuilder.newInstance()
    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
    .setMessageId("123")
    .setHeader("bar", "baz")
    .build();
Message message = MessageBuilder.withBody("foo".getBytes())
    .andProperties(props)
    .build();

在[MessageProperties](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/messageproperties.html)上定义的每个属性都可以被设置。其他方法包括setHeader(String key, String value)removeHeader(String key)removeHeaders()copyProperties(MessageProperties properties)。每个属性设置方法都有一个set*IfAbsent()变量。在存在默认初始值的情况下,该方法被命名为set*IfAbsentOrDefault()

提供了五种静态方法来创建初始消息生成器:

public static MessageBuilder withBody(byte[] body) (1)

public static MessageBuilder withClonedBody(byte[] body) (2)

public static MessageBuilder withBody(byte[] body, int from, int to) (3)

public static MessageBuilder fromMessage(Message message) (4)

public static MessageBuilder fromClonedMessage(Message message) (5)
1 构建器创建的消息有一个直接引用该参数的主体。
2 构建器创建的消息有一个主体,它是一个新的数组,在参数中包含一个字节的副本。
3 生成器创建的消息有一个主体,它是一个新的数组,其中包含来自参数的字节范围。
有关更多详细信息,请参见[Arrays.copyOfRange()](https://DOCS.oracle.com/javase/7/DOCS/api/java/util/array.html)。
4 构建器创建的消息有一个直接引用到参数主体的主体。
该参数的属性被复制到一个新的MessageProperties对象。
5 构建器创建的消息有一个主体,它是一个新的数组,其中包含一个参数主体的副本。
该参数的属性被复制到一个新的MessageProperties对象。

提供了三个静态方法来创建MessagePropertiesBuilder实例:

public static MessagePropertiesBuilder newInstance() (1)

public static MessagePropertiesBuilder fromProperties(MessageProperties properties) (2)

public static MessagePropertiesBuilder fromClonedProperties(MessageProperties properties) (3)
1 使用默认值初始化一个新的消息属性对象。
2 “构建器是用提供的属性对象初始化的,build()将返回该对象。,
3 参数的属性被复制到一个新的MessageProperties对象。

使用RabbitTemplateAmqpTemplate实现,每个send()方法都有一个重载版本,该版本接受一个额外的CorrelationData对象。当启用了 Publisher Confirms 时,将在[AmqpTemplate]中描述的回调中返回此对象。这使得发送者将确认(acknack)与发送的消息关联起来。

从 1.6.7 版本开始,引入了CorrelationAwareMessagePostProcessor接口,允许在消息转换后修改相关数据。下面的示例展示了如何使用它:

Message postProcessMessage(Message message, Correlation correlation);

在 2.0 版本中,此接口已被弃用。该方法已被移动到MessagePostProcessor,并使用一个将其委托给postProcessMessage(Message message)的默认实现。

从 1.6.7 版本开始,还提供了一个名为CorrelationDataPostProcessor的新回调接口。这是在所有MessagePostProcessor实例(在send()方法中提供,以及在setBeforePublishPostProcessors()中提供)之后调用的。实现方式可以更新或替换send()方法(如果有的话)中提供的相关数据。Message和原始CorrelationData(如果有的话)作为参数提供。下面的示例展示了如何使用postProcess方法:

CorrelationData postProcess(Message message, CorrelationData correlationData);
# Publisher 返回

当模板的mandatory属性是true时,返回的消息由[AmqpTemplate]中描述的回调提供。

从版本 1.4 开始,RabbitTemplate支持 spelmandatoryExpression属性,该属性根据每个请求消息作为根求值对象进行求值,解析为boolean值。 Bean 引用,例如@myBean.isMandatory(#root),可以在表达式中使用。

在发送和接收操作中,RabbitTemplate还可以在内部使用发布服务器返回。有关更多信息,请参见回复超时

# 批处理

版本 1.4.2 引入了BatchingRabbitTemplate。这是RabbitTemplate的一个子类,具有一个重写的send方法,该方法根据BatchingStrategy批处理消息。只有当批处理完成时,消息才会发送到 RabbitMQ。下面的清单显示了BatchingStrategy接口定义:

public interface BatchingStrategy {

    MessageBatch addToBatch(String exchange, String routingKey, Message message);

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

}
批处理的数据保存在内存中。
未发送的消息可能会在系统故障时丢失。

aSimpleBatchingStrategy。它支持将消息发送到单个 Exchange 或 Routing Key。它具有以下特性:

  • batchSize:一批消息在发送之前的数量。

  • bufferLimit:批处理消息的最大大小。如果超过了batchSize,这会抢占该参数,并导致发送部分批处理。

  • timeout:当没有新的活动向批处理添加消息时,发送部分批处理的时间。

SimpleBatchingStrategy通过在每条嵌入的消息之前使用四字节的二进制长度来格式化批处理。这是通过将springBatchFormat消息属性设置为lengthHeader4来与接收系统通信的。

批处理的消息在默认情况下由侦听器容器自动去批处理(通过使用springBatchFormat消息头)。
拒绝批处理中的任何消息将导致整个批处理被拒绝。

但是,有关更多信息,请参见@RabbitListener 与批处理

# 4.1.6.接收消息

消息接收总是比发送复杂一点。有两种方法可以接收Message。更简单的选项是使用轮询方法调用一次轮询一个Message。更复杂但更常见的方法是注册一个侦听器,该侦听器异步地按需接收Messages。在接下来的两个子节中,我们将考虑每种方法的示例。

# 轮询消费者

AmqpTemplate本身可以用于对Message的接收进行民意测验。默认情况下,如果没有可用的消息,null将立即返回。不存在阻塞。从版本 1.5 开始,你可以设置receiveTimeout,以毫秒为单位,并且 ReceiveMethods 块等待消息的时间最长可达那么长。小于零的值意味着无限期地阻塞(或至少直到与代理的连接丢失为止)。版本 1.6 引入了receive方法的变体,这些方法允许在每次调用时传入超时。

由于接收操作为每条消息创建了一个新的QueueingConsumer,因此这种技术并不适合于大容量环境。
考虑使用异步消费者或对这些用例使用零的receiveTimeout

有四种简单的receive方法可用。与发送端的Exchange一样,有一个方法要求在模板本身上直接设置默认的队列属性,并且有一个方法在运行时接受队列参数。版本 1.6 引入了一些变体,可以在每个请求的基础上接受receiveTimeout来覆盖receiveTimeout。下面的清单显示了这四种方法的定义:

Message receive() throws AmqpException;

Message receive(String queueName) throws AmqpException;

Message receive(long timeoutMillis) throws AmqpException;

Message receive(String queueName, long timeoutMillis) throws AmqpException;

与发送消息的情况一样,AmqpTemplate具有一些用于接收 POJO 而不是Message实例的方便方法,并且实现提供了一种定制MessageConverter的方法,用于创建返回的Object:下面的列表显示了这些方法:

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;

Object receiveAndConvert(long timeoutMillis) throws AmqpException;

Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException;

从版本 2.0 开始,这些方法有一些变体,它们接受一个额外的ParameterizedTypeReference参数来转换复杂类型。模板必须配置为SmartMessageConverter。有关更多信息,请参见[从Message转换为RabbitTemplate]。

sendAndReceive方法类似,从版本 1.3 开始,AmqpTemplate具有几种方便的receiveAndReply方法,用于同步接收、处理和回复消息。下面的清单显示了这些方法定义:

<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback)
       throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback)
     throws AmqpException;

<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback,
    String replyExchange, String replyRoutingKey) throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback,
    String replyExchange, String replyRoutingKey) throws AmqpException;

<R, S> boolean receiveAndReply(ReceiveAndReplyCallback<R, S> callback,
     ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException;

<R, S> boolean receiveAndReply(String queueName, ReceiveAndReplyCallback<R, S> callback,
            ReplyToAddressCallback<S> replyToAddressCallback) throws AmqpException;

AmqpTemplate实现处理receivereply阶段。在大多数情况下,你应该只提供ReceiveAndReplyCallback的实现,以便为接收到的消息执行某些业务逻辑,并在需要时构建一个应答对象或消息。注意,aReceiveAndReplyCallback可能返回null。在这种情况下,不会发送任何回复,并且receiveAndReply的工作原理与receive方法类似。这使得相同的队列可以用于混合消息,其中一些消息可能不需要回复。

只有当所提供的回调不是ReceiveAndReplyMessageCallback的实例时,才应用自动消息(请求和回复)转换,该实例提供了原始消息交换契约。

对于需要自定义逻辑在运行时根据接收到的消息确定replyTo地址并从ReceiveAndReplyCallback回复的情况,ReplyToAddressCallback是有用的。默认情况下,请求消息中的replyTo信息用于路由答复。

下面的清单展示了一个基于 POJO 的接收和回复示例:

boolean received =
        this.template.receiveAndReply(ROUTE, new ReceiveAndReplyCallback<Order, Invoice>() {

                public Invoice handle(Order order) {
                        return processOrder(order);
                }
        });
if (received) {
        log.info("We received an order!");
}
# 异步消费者
Spring AMQP 还通过使用@RabbitListener注释来支持带注释的侦听器端点,并提供一个开放的基础设施来以编程方式注册端点。
这是迄今为止设置异步消费者的最方便的方式。
有关更多详细信息,请参见注释驱动的监听器端点
预取默认值过去是 1,这可能会导致有效的使用者利用率不足,
从版本 2.0 开始,默认的预取值现在是 250,这应该会使使用者在大多数常见的情况下都很忙,
因此提高了吞吐量。,尽管如此,仍然存在,预取值应该较低的场景:对于大消息,

,特别是如果处理速度较慢(消息在客户端进程中可能会增加大量内存)

当需要严格的消息排序时(在这种情况下,预取值应设置为 1)

* 其他特殊情况

同样,对于低容量的消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以便在消费者之间获得更均匀的消息分布。

关于预取的更多背景信息,请参见消息侦听器容器配置x-expires
,请参阅这篇关于RabbitMQ 中的消费者利用 (opens new window)的文章和这篇关于排队论 (opens new window)的文章。
# 消息侦听器

对于异步Message接收,涉及一个专用组件(而不是AmqpTemplate)。该组件是用于Message消费回调的容器。在本节的后面部分,我们将考虑容器及其属性。不过,首先我们应该看看回调,因为这是应用程序代码与消息传递系统集成的地方。回调有几个选项,从MessageListener接口的实现开始,下面的清单显示了这个选项:

public interface MessageListener {
    void onMessage(Message message);
}

如果回调逻辑出于任何原因依赖于 AMQP 通道实例,则可以使用ChannelAwareMessageListener。它看起来很相似,但有一个额外的参数。下面的清单显示了ChannelAwareMessageListener接口定义:

public interface ChannelAwareMessageListener {
    void onMessage(Message message, Channel channel) throws Exception;
}
在版本 2.1 中,此接口从包o.s.amqp.rabbit.core移动到o.s.amqp.rabbit.listener.api
# MessageListenerAdapter

如果希望在应用程序逻辑和消息传递 API 之间保持更严格的分离,则可以依赖框架提供的适配器实现。这通常被称为“消息驱动的 POJO”支持。

版本 1.5 引入了一种更灵活的 POJO 消息传递机制,@RabbitListener注释。
有关更多信息,请参见注释驱动的监听器端点

在使用适配器时,你只需要提供对适配器本身应该调用的实例的引用。下面的示例展示了如何做到这一点:

MessageListenerAdapter listener = new MessageListenerAdapter(somePojo);
listener.setDefaultListenerMethod("myMethod");

你可以对适配器进行子类,并提供getListenerMethodName()的实现,以根据消息动态地选择不同的方法。这个方法有两个参数,originalMessageextractedMessage,后者是任何转换的结果。默认情况下,配置了SimpleMessageConverter。有关其他可用转换器的更多信息和信息,请参见[SimpleMessageConverter]。

从版本 1.4.2 开始,原始消息具有consumerQueueconsumerTag属性,可用于确定接收消息的队列。

从版本 1.5 开始,你可以将消费者队列或标记的映射配置为方法名,以动态地选择要调用的方法。如果映射中没有条目,我们将返回到默认的侦听器方法。默认的侦听器方法(如果未设置)是handleMessage

从版本 2.0 开始,提供了一个方便的FunctionalInterface。下面的清单显示了FunctionalInterface的定义:

@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}

该接口通过使用 Java8Lambdas 方便了适配器的配置,如下例所示:

new MessageListenerAdapter((ReplyingMessageListener<String, String>) data -> {
    ...
    return result;
}));

从版本 2.2 开始,buildListenerArguments(Object)已被弃用,取而代之的是新的buildListenerArguments(Object, Channel, Message)。新方法帮助 Listener 获得@SendToMessage参数,以执行更多操作,例如在手动确认模式下调用channel.basicReject(long, boolean)。下面的清单展示了最基本的示例:

public class ExtendedListenerAdapter extends MessageListenerAdapter {

    @Override
    protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) {
        return new Object[]{extractedMessage, channel, message};
    }

}

现在,如果需要接收“通道”和“消息”,你可以将ExtendedListenerAdapter配置为与MessageListenerAdapter相同的配置。Listener 的参数应设置为buildListenerArguments(Object, Channel, Message)返回,如下面的 Listener 示例所示:

public void handleMessage(Object object, Channel channel, Message message) throws IOException {
    ...
}
# 容器

既然你已经看到了ChannelAwareMessageListener-listening 回调的各种选项,那么我们就可以将注意力转向容器了。基本上,容器处理“主动”职责,以便侦听器回调可以保持被动。容器是“生命周期”组件的一个示例。它提供了启动和停止的方法。在配置容器时,实质上是在 AMQP 队列和MessageListener实例之间建立桥梁。你必须提供对ConnectionFactory的引用,以及该侦听器应该从其中使用消息的队列名称或队列实例。

在 2.0 版本之前,有一个侦听器容器,SimpleMessageListenerContainer。现在有第二个容器,DirectMessageListenerContainer。容器和你在选择要使用的容器时可能应用的条件之间的差异在选择容器中进行了描述。

下面的清单显示了最基本的示例,该示例通过使用SimpleMessageListenerContainer来工作:

SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory);
container.setQueueNames("some.queue");
container.setMessageListener(new MessageListenerAdapter(somePojo));

作为一个“活动”组件,最常见的方法是创建具有 Bean 定义的侦听器容器,以便它可以在后台运行。下面的示例展示了使用 XML 的一种方法:

<rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>

下面的清单展示了使用 XML 的另一种方法:

<rabbit:listener-container connection-factory="rabbitConnectionFactory" type="direct">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>

前面的两个示例都创建了DirectMessageListenerContainer(请注意type属性——它默认为simple)。

或者,你可能更喜欢使用 Java 配置,它看起来类似于前面的代码片段:

@Configuration
public class ExampleAmqpConfiguration {

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }

    @Bean
    public CachingConnectionFactory rabbitConnectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public MessageListener exampleListener() {
        return new MessageListener() {
            public void onMessage(Message message) {
                System.out.println("received: " + message);
            }
        };
    }
}
# 消费者优先权

从 RabbitMQ3.2 版本开始,代理现在支持消费者优先权(参见使用 RabbitMQ 的消费者优先级 (opens new window))。这可以通过在消费者上设置x-priority参数来实现。SimpleMessageListenerContainer现在支持设置消费者参数,如下例所示:

container.setConsumerArguments(Collections.
<String, Object> singletonMap("x-priority", Integer.valueOf(10)));

为了方便起见,名称空间在listener元素上提供了priority属性,如下例所示:

<rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <rabbit:listener queues="some.queue" ref="somePojo" method="handle" priority="10" />
</rabbit:listener-container>

从版本 1.3 开始,你可以修改容器在运行时监听的队列。见监听器容器队列

# auto-delete队列

当容器被配置为侦听auto-delete队列时,队列具有x-expires选项,或者在代理上配置了活着的时间 (opens new window)策略,当容器停止时(即最后一个使用者被取消时),代理将队列删除。在版本 1.3 之前,由于缺少队列,无法重新启动容器。RabbitAdmin仅在连接关闭或打开时自动重新声明队列,以此类推,而在停止和启动容器时不会发生这种情况。

从版本 1.3 开始,容器使用选择容器在启动过程中重新声明任何丢失的队列。

你还可以使用条件声明(参见有条件声明)和@RabbitHandler管理来延迟队列声明,直到容器启动。下面的示例展示了如何做到这一点:

<rabbit:queue id="otherAnon" declared-by="containerAdmin" />

<rabbit:direct-exchange name="otherExchange" auto-delete="true" declared-by="containerAdmin">
    <rabbit:bindings>
        <rabbit:binding queue="otherAnon" key="otherAnon" />
    </rabbit:bindings>
</rabbit:direct-exchange>

<rabbit:listener-container id="container2" auto-startup="false">
    <rabbit:listener id="listener2" ref="foo" queues="otherAnon" admin="containerAdmin" />
</rabbit:listener-container>

<rabbit:admin id="containerAdmin" connection-factory="rabbitConnectionFactory"
    auto-startup="false" />

在这种情况下,队列和交换由containerAdmin声明,其具有,因此在上下文初始化期间不会声明元素。同样,由于同样的原因,容器没有启动。当容器稍后被启动时,它使用其对containerAdmin的引用来声明元素。

# 批处理消息

批处理的消息(由生成者创建)由侦听器容器(使用springBatchFormat消息头)自动进行去批处理。拒绝批处理中的任何消息都会导致整个批处理被拒绝。有关批处理的更多信息,请参见Batching

从版本 2.2 开始,SimpleMessageListenerContainer可以用于在消费者端(生产者发送离散消息的地方)创建批处理。

设置容器属性consumerBatchEnabled以启用此功能。deBatchingEnabled也必须为真,以便容器负责处理这两种类型的批。当consumerBatchEnabled为真时实现BatchMessageListenerChannelAwareBatchMessageListener。从版本 2.2.7 开始,SimpleMessageListenerContainerDirectMessageListenerContainer都可以将生产者创建了批删除为List<Message>。有关在@RabbitListener中使用此功能的信息,请参见DirectMessageListenerContainer

# 消费者活动

容器在侦听器(使用者)遇到某种故障时发布应用程序事件。事件ListenerContainerConsumerFailedEvent具有以下属性:

  • container:消费者遇到问题的侦听器容器。

  • reason:失败的文本原因。

  • fatal:表示失败是否致命的布尔值。对于非致命的异常,容器会根据recoveryIntervalrecoveryBackoff(对于SimpleMessageListenerContainer)或recoveryBackoff(对于DirectMessageListenerContainer)尝试重新启动消费者。

  • throwable:抓到的Throwable

可以通过实现ApplicationListener<ListenerContainerConsumerFailedEvent>来消耗这些事件。

concurrentConsumers大于 1 时,所有使用者都会发布系统范围的事件(例如连接失败)。

如果一个使用者因其队列被独占使用而失败,那么默认情况下,以及在发布事件时,将发布WARN日志。要更改此日志记录行为,请在SimpleMessageListenerContainer实例的exclusiveConsumerExceptionLogger属性中提供一个自定义ConditionalExceptionLogger。另见记录通道关闭事件

致命错误总是记录在ERROR级别。这是不可更改的。

在容器生命周期的各个阶段还发布了其他几个事件:

  • AsyncConsumerStartedEvent:启动消费者时。

  • AsyncConsumerRestartedEvent:当使用者在失败后重新启动时-SimpleMessageListenerContainer仅。

  • AsyncConsumerTerminatedEvent:当消费者被正常停止时。

  • AsyncConsumerStoppedEvent:仅当消费者被停止时-SimpleMessageListenerContainer

  • ConsumeOkEvent:当从代理收到consumeOk时,包含队列名和consumerTag

  • ListenerContainerIdleEvent:见检测空闲异步消费者

  • 检测空闲异步消费者:检测到缺少队列时。

# 消费者标签

你可以提供一种生成消费者标签的策略。默认情况下,消费者标记是由代理生成的。下面的清单显示了ConsumerTagStrategy接口定义:

public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}

队列是可用的,以便它可以(可选地)在标记中使用。

消息侦听器容器配置

# 注释驱动的侦听器端点

异步接收消息的最简单方法是使用带注释的侦听器端点基础结构。简而言之,它允许你将托管 Bean 方法公开为 Rabbit 侦听器端点。下面的示例展示了如何使用@RabbitListener注释:

@Component
public class MyService {

    @RabbitListener(queues = "myQueue")
    public void processOrder(String data) {
        ...
    }

}

前面示例的思想是,每当名为myQueue的队列上有消息可用时,都会相应地调用ClassMapper方法(在这种情况下,使用消息的有效负载)。

通过使用RabbitListenerContainerFactory,带注释的端点基础设施在幕后为每个带注释的方法创建了一个消息侦听器容器。

在前面的示例中,myQueue必须已经存在并绑定到某个交换。只要应用程序上下文中存在RabbitAdmin,就可以自动声明和绑定队列。

可以为注释属性(queues等)指定属性占位符(${some.property})或 spel 表达式(#{someExpression})。
参见监听多个队列有关为什么可能使用 spel 而不是属性占位符的示例。
下面的列表显示了如何声明 Rabbit 侦听器的三个示例:
@Component
public class MyService {

  @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "myQueue", durable = "true"),
        exchange = @Exchange(value = "auto.exch", ignoreDeclarationExceptions = "true"),
        key = "orderRoutingKey")
  )
  public void processOrder(Order order) {
    ...
  }

  @RabbitListener(bindings = @QueueBinding(
        value = @Queue,
        exchange = @Exchange(value = "auto.exch"),
        key = "invoiceRoutingKey")
  )
  public void processInvoice(Invoice invoice) {
    ...
  }

  @RabbitListener(queuesToDeclare = @Queue(name = "${my.queue}", durable = "true"))
  public String handleWithSimpleDeclare(String data) {
      ...
  }

}

在第一个示例中,如果需要,一个队列myQueue将与交换器一起自动(持久)声明,并用路由密钥绑定到交换器。在第二个示例中,声明并绑定了一个匿名(排他的、自动删除的)队列。可以提供多个QueueBinding条目,让侦听器监听多个队列。在第三个示例中,如果有必要,将使用默认绑定到默认的 Exchange,并使用队列名称作为路由密钥,声明一个具有从属性my.queue中检索到的名称的队列。

自 2.0 版本以来,@Exchange注释支持任何交换类型,包括自定义。有关更多信息,请参见AMQP 概念 (opens new window)

当需要更高级的配置时,可以使用普通的@Bean定义。

请注意第一个示例中的 Exchange 上的ignoreDeclarationExceptions。例如,这允许绑定到可能具有不同设置的现有交换机(例如internal)。默认情况下,现有交易所的属性必须匹配。

从版本 2.0 开始,你现在可以将队列绑定到具有多个路由密钥的交易所,如下例所示:

...
    key = { "red", "yellow" }
...

还可以在@QueueBinding注释中为队列、交换和绑定指定参数,如下例所示:

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "auto.headers", autoDelete = "true",
                        arguments = @Argument(name = "x-message-ttl", value = "10000",
                                                type = "java.lang.Integer")),
        exchange = @Exchange(value = "auto.headers", type = ExchangeTypes.HEADERS, autoDelete = "true"),
        arguments = {
                @Argument(name = "x-match", value = "all"),
                @Argument(name = "thing1", value = "somevalue"),
                @Argument(name = "thing2")
        })
)
public String handleWithHeadersExchange(String foo) {
    ...
}

注意,对于队列,x-message-ttl参数被设置为 10 秒。因为参数类型不是String,所以我们必须指定它的类型——在本例中,Integer。与所有此类声明一样,如果队列已经存在,则参数必须与队列上的参数匹配。对于头交换,我们将绑定参数设置为匹配将thing1头设置为somevalue的消息,并且thing2头必须带有任何值。x-match参数意味着这两个条件都必须满足。

参数名称、值和类型可以是属性占位符(${…​})或 SPEL 表达式(#{…​})。name必须解析为Stringtype的表达式必须解析为Class或类的完全限定名称。value必须解析为可以由DefaultConversionService转换为类型的内容(例如前面示例中的x-message-ttl)。

如果名称解析为null或空的String,则忽略@Argument

# 元注解

有时你可能希望对多个侦听器使用相同的配置。为了减少样板配置,你可以使用元注释来创建自己的侦听器注释。下面的示例展示了如何做到这一点:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@RabbitListener(bindings = @QueueBinding(
        value = @Queue,
        exchange = @Exchange(value = "metaFanout", type = ExchangeTypes.FANOUT)))
public @interface MyAnonFanoutListener {
}

public class MetaListener {

    @MyAnonFanoutListener
    public void handle1(String foo) {
        ...
    }

    @MyAnonFanoutListener
    public void handle2(String foo) {
        ...
    }

}

在前面的示例中,由@MyAnonFanoutListener注释创建的每个侦听器都将一个匿名的自动删除队列绑定到 FanOut Exchange,metaFanout。从版本 2.2.3 开始,支持@AliasFor,以允许在元注释注释上覆盖属性。此外,用户注释现在可以是@Repeatable,从而允许为一个方法创建多个容器。

@Component
static class MetaAnnotationTestBean {

    @MyListener("queue1")
    @MyListener("queue2")
    public void handleIt(String body) {
    }

}

@RabbitListener
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyListeners.class)
static @interface MyListener {

    @AliasFor(annotation = RabbitListener.class, attribute = "queues")
    String[] value() default {};

}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
static @interface MyListeners {

    MyListener[] value();

}
# 启用侦听器端点注释

要启用对@RabbitListener注释的支持,你可以将@EnableRabbit添加到你的一个@Configuration类中。下面的示例展示了如何做到这一点:

@Configuration
@EnableRabbit
public class AppConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        factory.setContainerCustomizer(container -> /* customize the container */);
        return factory;
    }
}

自 2.0 版本以来,DirectMessageListenerContainerFactory也可用。它创建DirectMessageListenerContainer实例。

有关帮助你在SimpleRabbitListenerContainerFactoryDirectRabbitListenerContainerFactory之间进行选择的信息,请参见选择容器

从版本 2.2.2 开始,你可以提供ContainerCustomizer实现(如上图所示)。在创建和配置了容器之后,可以使用它来进一步配置容器;例如,你可以使用它来设置容器工厂不公开的属性。

默认情况下,基础结构会寻找一个名为rabbitListenerContainerFactory的 Bean 作为工厂用来创建消息侦听器容器的源。在这种情况下,忽略 RabbitMQ 基础设施设置,processOrder方法可以通过三个线程的核心轮询大小和十个线程的最大池大小来调用。

你可以自定义用于每个注释的侦听器容器工厂,或者可以通过实现RabbitListenerConfigurer接口来配置显式默认值。只有当至少有一个端点在没有特定容器工厂的情况下注册时,才需要默认设置。有关详细信息和示例,请参见Javadoc (opens new window)

容器工厂提供了用于添加MessagePostProcessor实例的方法,这些实例是在接收消息(调用侦听器之前)和发送回复之前应用的。

有关回复的信息,请参见回复管理

从版本 2.0.6 开始,你可以向监听器容器工厂添加RetryTemplateRecoveryCallback。在发送回复时使用它。当重试用完时,将调用RecoveryCallback。你可以使用SendRetryContextAccessor从上下文中获取信息。下面的示例展示了如何做到这一点:

factory.setRetryTemplate(retryTemplate);
factory.setReplyRecoveryCallback(ctx -> {
    Message failed = SendRetryContextAccessor.getMessage(ctx);
    Address replyTo = SendRetryContextAccessor.getAddress(ctx);
    Throwable t = ctx.getLastThrowable();
    ...
    return null;
});

如果你更喜欢 XML 配置,那么可以使用<rabbit:annotation-driven>元素。检测到任何带有@RabbitListener注释的 bean。

对于SimpleRabbitListenerContainer实例,你可以使用类似于以下的 XML:

<rabbit:annotation-driven/>

<bean id="rabbitListenerContainerFactory"
      class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="concurrentConsumers" value="3"/>
    <property name="maxConcurrentConsumers" value="10"/>
</bean>

对于DirectMessageListenerContainer实例,你可以使用类似于以下的 XML:

<rabbit:annotation-driven/>

<bean id="rabbitListenerContainerFactory"
      class="org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="consumersPerQueue" value="3"/>
</bean>

从版本 2.0 开始,@RabbitListener注释具有concurrency属性。它支持 SPEL 表达式(@RabbitListener)和属性占位符(${…​})。其含义和允许的值取决于容器类型,如下所示:

  • 对于DirectMessageListenerContainer,该值必须是一个整数值,该值设置容器上的consumersPerQueue属性。

  • 对于SimpleRabbitListenerContainer,该值可以是一个整数值,它在容器上设置concurrentConsumers属性,或者它可以具有以下形式,m,其中mconcurrentConsumers属性,nmaxConcurrentConsumers属性。

在这两种情况下,此设置都会覆盖工厂上的设置。以前,如果你的侦听器需要不同的并发性,则必须定义不同的容器工厂。

注释还允许通过autoStartupexecutor(自 2.2)注释属性重写工厂taskExecutor属性。对每个执行器使用不同的执行器可能有助于在日志和线程转储中识别与每个侦听器关联的线程。

版本 2.2 还添加了ackMode属性,它允许你覆盖容器工厂的ackMode属性。

@RabbitListener(id = "manual.acks.1", queues = "manual.acks.1", ackMode = "MANUAL")
public void manual1(String in, Channel channel,
    @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {

    ...
    channel.basicAck(tag, false);
}
# 带注释方法的消息转换

在调用侦听器之前,管道中有两个转换步骤。第一步使用MessageConverter将传入的 Spring AMQPMessage转换为 Spring-messagingMessage。当调用目标方法时,如果需要,消息有效负载将被转换为方法参数类型。

第一步的默认MessageConverter是一个 Spring AMQPSimpleMessageConverter,它处理向Stringjava.io.Serializable对象的转换。所有其他参数保持为byte[]。在下面的讨论中,我们将其称为“消息转换器”。

第二步的默认转换器是GenericMessageConverter,它委托给一个转换服务(DefaultFormattingConversionService的实例)。在下面的讨论中,我们将其称为“方法参数转换器”。

要更改消息转换器,可以将其作为一个属性添加到容器工厂 Bean。下面的示例展示了如何做到这一点:

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    ...
    factory.setMessageConverter(new Jackson2JsonMessageConverter());
    ...
    return factory;
}

这配置了一个 Jackson2 转换器,该转换器期望出现头信息以指导转换。

还可以使用ContentTypeDelegatingMessageConverter,它可以处理不同内容类型的转换。

从版本 2.3 开始,你可以通过在Message属性中指定 Bean 名称来覆盖工厂转换器。

@Bean
public Jackson2JsonMessageConverter jsonConverter() {
    return new Jackson2JsonMessageConverter();
}

@RabbitListener(..., messageConverter = "jsonConverter")
public void listen(String in) {
    ...
}

这样就避免了仅仅为了更改转换器就必须声明一个不同的容器工厂。

在大多数情况下,不需要自定义方法参数转换器,除非你希望使用自定义ConversionService

在 1.6 之前的版本中,转换 JSON 的类型信息必须在消息头中提供,或者需要自定义ClassMapper。从版本 1.6 开始,如果没有类型信息标头,则可以从目标方法参数推断类型。

这种类型推理在方法级别上仅适用于@RabbitListener

有关更多信息,请参见Jackson2JSONMessageConverter

如果你希望自定义方法参数转换器,可以按以下方式进行:

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    ...

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(new GenericMessageConverter(myConversionService()));
        return factory;
    }

    @Bean
    public DefaultConversionService myConversionService() {
        DefaultConversionService conv = new DefaultConversionService();
        conv.addConverter(mySpecialConverter());
        return conv;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    ...

}
对于多方法侦听器(参见多方法侦听器),方法选择是基于消息的有效负载消息转换后
只有在方法已被选择之后才调用方法参数转换器。
# 向 @RabbitListener 添加自定义HandlerMethodArgumentResolver

从版本 2.3.7 开始,你可以添加自己的HandlerMethodArgumentResolver并解析自定义方法参数。你所需要的只是实现ClassMapper并使用来自类RabbitListenerEndpointRegistrar的方法setCustomMethodArgumentResolvers()

@Configuration
class CustomRabbitConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setCustomMethodArgumentResolvers(
				new HandlerMethodArgumentResolver() {

					@Override
					public boolean supportsParameter(MethodParameter parameter) {
						return CustomMethodArgument.class.isAssignableFrom(parameter.getParameterType());
					}

					@Override
					public Object resolveArgument(MethodParameter parameter, org.springframework.messaging.Message<?> message) {
						return new CustomMethodArgument(
								(String) message.getPayload(),
								message.getHeaders().get("customHeader", String.class)
						);
					}

				}
			);
    }

}
# 程序化端点注册

RabbitListenerEndpoint提供了一个 Rabbit 端点的模型,并负责为该模型配置容器。除了通过RabbitListener注释检测到的端点之外,该基础结构还允许你以编程方式配置端点。下面的示例展示了如何做到这一点:

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
        endpoint.setQueueNames("anotherQueue");
        endpoint.setMessageListener(message -> {
            // processing
        });
        registrar.registerEndpoint(endpoint);
    }
}

在前面的示例中,我们使用了SimpleRabbitListenerEndpoint,它提供了要调用的实际MessageListener,但是你也可以构建自己的端点变体来描述自定义调用机制。

需要注意的是,你也可以完全跳过@RabbitListener的使用,并通过RabbitListenerConfigurer以编程方式注册端点。

# 带注释的端点方法签名

到目前为止,我们已经在我们的端点中注入了一个简单的String,但是它实际上可以有一个非常灵活的方法签名。下面的示例重写它,以使用自定义标头注入Order:

@Component
public class MyService {

    @RabbitListener(queues = "myQueue")
    public void processOrder(Order order, @Header("order_type") String orderType) {
        ...
    }
}

下面的列表显示了可用于与侦听器端点中的参数进行匹配的参数:

  • RAWorg.springframework.amqp.core.Message

  • 来自 RAWMessageMessageProperties

  • 在其上接收消息的com.rabbitmq.client.Channel

  • 从传入的 AMQP 消息转换的${…​}

  • @Header-带注释的方法参数,以提取特定的标头值,包括标准的 AMQP 标头。

  • @Headers-带注释的参数,该参数还必须分配给java.util.Map,以获得对所有标题的访问权限。

  • 转换后的有效载荷

不是受支持类型之一的非注释元素(即MessageMessagePropertiesMessage<?>Channel)与有效负载匹配。你可以通过使用@Payload对参数进行注释来使其显式。你还可以通过添加一个额外的@Valid来打开验证。

注入 Spring 的消息抽象的能力特别有用,可以从存储在特定传输消息中的所有信息中受益,而无需依赖特定传输 API。下面的示例展示了如何做到这一点:

@RabbitListener(queues = "myQueue")
public void processOrder(Message<Order> order) { ...
}

方法参数的处理由DefaultMessageH和lerMethodFactory提供,你可以进一步自定义该参数以支持其他方法参数。转换和验证支持也可以在那里进行定制。

例如,如果我们想在处理Order之前确保我们的Order是有效的,我们可以用@Valid注释有效负载,并配置必要的验证器,如下所示:

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setValidator(myValidator());
        return factory;
    }
}
# @rabbitlistener@payload validation

从版本 2.3.7 开始,现在更容易添加Validator来验证@RabbitListener@RabbitHandler``@Payload参数。现在,你只需将验证器添加到注册商本身即可。

@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
当使用 Spring boot 和验证启动器时,LocalValidatorFactoryBean是自动配置的:
@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    @Autowired
    private LocalValidatorFactoryBean validator;
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(this.validator);
    }
}

验证:

public static class ValidatedClass {
  @Max(10)
  private int bar;
  public int getBar() {
    return this.bar;
  }
  public void setBar(int bar) {
    this.bar = bar;
  }
}

and

@RabbitListener(id="validated", queues = "queue1", errorHandler = "validationErrorHandler",
      containerFactory = "jsonListenerContainerFactory")
public void validatedListener(@Payload @Valid ValidatedClass val) {
    ...
}
@Bean
public RabbitListenerErrorHandler validationErrorHandler() {
    return (m, e) -> {
        ...
    };
}
# 监听多个队列

当你使用queues属性时,你可以指定关联的容器可以侦听多个队列。你可以使用@Header注释使 POJO 方法可以使用接收消息的队列名称。下面的示例展示了如何做到这一点:

@Component
public class MyService {

    @RabbitListener(queues = { "queue1", "queue2" } )
    public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
        ...
    }

}

从版本 1.5 开始,你可以使用属性占位符和 SPEL 来外部化队列名称。下面的示例展示了如何做到这一点:

@Component
public class MyService {

    @RabbitListener(queues = "#{'${property.with.comma.delimited.queue.names}'.split(',')}" )
    public void processOrder(String data, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
        ...
    }

}

在版本 1.5 之前,只能以这种方式指定单个队列。每个队列都需要一个单独的属性。

# 回复管理

MessageListenerAdapter中现有的支持已经允许你的方法具有一个非 void 返回类型。在这种情况下,调用的结果被封装在一条消息中,发送到原始消息的ReplyToAddress头中指定的地址,或发送到侦听器上配置的默认地址。你可以通过使用消息抽象的@SendTo注释来设置默认地址。

假设我们的processOrder方法现在应该返回一个OrderStatus,我们可以按以下方式编写它来自动发送回复:

@RabbitListener(destination = "myQueue")
@SendTo("status")
public OrderStatus processOrder(Order order) {
    // order processing
    return status;
}

如果需要以与传输无关的方式设置额外的头,则可以返回Message,如下所示:

@RabbitListener(destination = "myQueue")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
    // order processing
    return MessageBuilder
        .withPayload(status)
        .setHeader("code", 1234)
        .build();
}

或者,你可以在beforeSendReplyMessagePostProcessors容器工厂属性中使用MessagePostProcessor来添加更多标题。从版本 2.2.3 开始,被调用的 Bean/方法在回复消息中是可用的,该回复消息可以在消息后置处理器中用来将信息通信回调用者:

factory.setBeforeSendReplyPostProcessors(msg -> {
    msg.getMessageProperties().setHeader("calledBean",
            msg.getMessageProperties().getTargetBean().getClass().getSimpleName());
    msg.getMessageProperties().setHeader("calledMethod",
            msg.getMessageProperties().getTargetMethod().getName());
    return m;
});

从版本 2.2.5 开始,你可以配置一个ReplyPostProcessor来修改发送之前的回复消息;在correlationId头设置为与请求匹配后调用它。

@RabbitListener(queues = "test.header", group = "testGroup", replyPostProcessor = "echoCustomHeader")
public String capitalizeWithHeader(String in) {
    return in.toUpperCase();
}

@Bean
public ReplyPostProcessor echoCustomHeader() {
    return (req, resp) -> {
        resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader"));
        return resp;
    };
}

@SendTo值假定为一个响应exchangeroutingKey对,该对遵循exchange/routingKey模式,其中一个部分可以省略。有效数值如下:

  • thing1/thing2:replyTo交易所和routingKey.queues交易所:replyTo交易所和默认(空)routingKey/thing2/thing2交易所和默认(空)交易所。/或空:默认routingKey交易所和“默认<gt="1295"/>交易所。

同样,你可以使用@SendTo而不使用value属性。这种情况等于一个空的!{…​}模式。@SendTo仅在入站消息没有replyToAddress属性的情况下才使用。

从版本 1.5 开始,@SendTo值可以是 Bean 初始化 SPEL 表达式,如下例所示:

@RabbitListener(queues = "test.sendTo.spel")
@SendTo("#{spelReplyTo}")
public String capitalizeWithSendToSpel(String foo) {
    return foo.toUpperCase();
}
...
@Bean
public String spelReplyTo() {
    return "test.sendTo.reply.spel";
}

表达式必须求值为String,它可以是一个简单的队列名称(发送到默认的 Exchange),也可以是与前面示例之前讨论的exchange/routingKey形式一起。

在初始化过程中,对#{…​}表达式进行一次求值。

对于动态回复路由,消息发送方应该包括reply_to消息属性,或者使用备用运行时 SPEL 表达式(在下一个示例之后描述)。

从版本 1.6 开始,@SendTo可以是一个 SPEL 表达式,在运行时根据请求和回复进行求值,如下例所示:

@RabbitListener(queues = "test.sendTo.spel")
@SendTo("!{'some.reply.queue.with.' + result.queueName}")
public Bar capitalizeWithSendToSpel(Foo foo) {
    return processTheFooAndReturnABar(foo);
}

spel 表达式的运行时性质用!{…​}分隔符表示。表达式的求值上下文#root对象具有三个属性:

  • request:o.s.amqp.core.Message请求对象。

  • source:转换后的o.s.messaging.Message<?>

  • result:方法结果。

上下文有一个映射属性访问器、一个标准类型转换器和一个 Bean 解析器,它允许引用上下文中的其他 bean(例如,@someBeanName.determineReplyQ(request, result))。

总之,#{…​}在初始化过程中被求值一次,而#root对象是应用程序上下文。bean 由它们的名称引用。!{…​}在运行时对每个消息进行求值,根对象具有前面列出的属性。bean 的名字是引用的,前缀是@

从版本 2.1 开始,还支持简单属性占位符(例如,${some.reply.to})。对于较早的版本,可以使用以下方法,如下例所示:

@RabbitListener(queues = "foo")
@SendTo("#{environment['my.send.to']}")
public String listen(Message in) {
    ...
    return ...
}
# 回复 ContentType

如果使用复杂的消息转换器,例如ContentTypeDelegatingMessageConverter,则可以通过在侦听器上设置replyContentType属性来控制回复的内容类型。这允许转换器为应答选择适当的委托转换器。

@RabbitListener(queues = "q1", messageConverter = "delegating",
        replyContentType = "application/json")
public Thing2 listen(Thing1 in) {
    ...
}

默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被这个值覆盖。诸如SimpleMessageConverter之类的转换器使用回复类型而不是内容类型来确定所需的转换,并在回复消息中适当地设置内容类型。这可能不是所需的操作,可以通过将converterWinsContentType属性设置为SimpleRabbitListenerEndpoint来重写。例如,如果返回一个包含 JSON 的String,则SimpleMessageConverter将在答复中将内容类型设置为text/plain。下面的配置将确保正确设置内容类型,即使使用SimpleMessageConverter

@RabbitListener(queues = "q1", replyContentType = "application/json",
        converterWinsContentType = "false")
public String listen(Thing in) {
    ...
    return someJsonString;
}

当返回类型是 Spring AMQPMessage或 Spring 消息传递Message<?>时,这些属性(replyContentTypeconverterWinsContentType)不适用。在第一种情况下,不涉及转换;只需设置contentType消息属性。在第二种情况下,使用消息头来控制行为:

@RabbitListener(queues = "q1", messageConverter = "delegating")
@SendTo("q2")
public Message<String> listen(String in) {
    ...
    return MessageBuilder.withPayload(in.toUpperCase())
            .setHeader(MessageHeaders.CONTENT_TYPE, "application/xml")
            .build();
}

此内容类型将在MessageProperties中传递给转换器。默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被这个值覆盖。如果你希望重写该行为,还可以将AmqpHeaders.CONTENT_TYPE_CONVERTER_WINS设置为true,并且转换器设置的任何值都将被保留。

# 多方法侦听器

从版本 1.5.0 开始,你可以在类级别上指定@RabbitListener注释。与新的@RabbitHandler注释一起,这允许单个侦听器根据传入消息的有效负载类型调用不同的方法。这一点最好用一个例子来描述:

@RabbitListener(id="multi", queues = "someQueue")
@SendTo("my.reply.queue")
public class MultiListenerBean {

    @RabbitHandler
    public String thing2(Thing2 thing2) {
        ...
    }

    @RabbitHandler
    public String cat(Cat cat) {
        ...
    }

    @RabbitHandler
    public String hat(@Header("amqp_receivedRoutingKey") String rk, @Payload Hat hat) {
        ...
    }

    @RabbitHandler(isDefault = true)
    public String defaultMethod(Object object) {
        ...
    }

}

在这种情况下,如果转换的有效负载是Thing2CatHat,则会调用单独的@RabbitHandler方法。你应该理解,系统必须能够基于有效负载类型来识别唯一的方法。将检查类型是否可分配给没有注释或使用@Payload注释的单个参数。请注意,同样的方法签名也适用,如方法级别@RabbitListener前面描述的)中所讨论的那样。

从版本 2.0.3 开始,可以将@RabbitHandler方法指定为默认方法,如果其他方法不匹配,则调用该方法。最多只能指定一种方法。

@RabbitHandler仅用于处理转换后的消息负载,如果希望接收未转换的 RAWMessage对象,则必须在方法上使用@RabbitListener,而不是在类上。
# @Repeatable @RabbitListener

从版本 1.6 开始,@RabbitListener注释被标记为@Repeatable。这意味着注释可以多次出现在相同的注释元素(方法或类)上。在这种情况下,将为每个注释创建一个单独的侦听器容器,每个注释都调用相同的侦听器@Bean。可重复的注释可以用于 Java8 或更高版本。

# 代理@RabbitListener和泛型

如果你的服务旨在被代理(例如,在@Transactional的情况下),那么当接口具有通用参数时,你应该记住一些考虑因素。考虑以下示例:

interface TxService<P> {

   String handle(P payload, String header);

}

static class TxServiceImpl implements TxService<Foo> {

    @Override
    @RabbitListener(...)
    public String handle(Thing thing, String rk) {
         ...
    }

}

对于通用接口和特定实现,你将被迫切换到 CGLIB 目标类代理,因为接口handle方法的实际实现是一种桥接方法。在事务管理的情况下,通过使用一个注释选项来配置 CGLIB 的使用:@EnableTransactionManagement(proxyTargetClass = true)。在这种情况下,所有注释都必须在实现中的目标方法上声明,如下例所示:

static class TxServiceImpl implements TxService<Foo> {

    @Override
    @Transactional
    @RabbitListener(...)
    public String handle(@Payload Foo foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}
# 处理异常

默认情况下,如果一个带注释的侦听器方法抛出一个异常,它将被抛出到容器,消息将被重新请求并重新交付、丢弃或路由到死信交换,这取决于容器和代理配置。任何东西都不会退还给寄件人。

从版本 2.0 开始,@RabbitListener注释有两个新属性:errorHandlerreturnExceptions

默认情况下,不会对它们进行配置。

你可以使用errorHandler来提供RabbitListenerErrorHandler实现的 Bean 名称。此功能接口有一种方法,如下所示:

@FunctionalInterface
public interface RabbitListenerErrorHandler {

    Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message,
              ListenerExecutionFailedException exception) throws Exception;

}

如你所见,你可以访问从容器接收的原始消息、消息转换器产生的 Spring-messagingMessage<?>对象,以及侦听器抛出的异常(包装在ListenerExecutionFailedException中)。错误处理程序可以返回一些结果(作为回复发送),也可以抛出原始异常或新的异常(根据returnExceptions设置,将其抛出到容器或返回给发送方)。

true时,returnExceptions属性将导致异常返回给发送方。异常包装在RemoteInvocationResult对象中。在发送端,有一个可用的RemoteInvocationAwareMessageConverterAdapter,如果将其配置为RabbitTemplate,则会重新抛出服务器端异常,并包装在AmqpRemoteException中。服务器异常的堆栈跟踪是通过合并服务器和客户端堆栈跟踪来合成的。

这种机制通常仅对默认的SimpleMessageConverter有效,后者使用 Java 序列化。
异常通常不是“Jackson 友好的”,并且不能序列化到 JSON。
如果使用 JSON,请考虑在抛出异常时使用errorHandler返回一些其他 Jackson 友好的Error对象。
在版本 2.1 中,此接口从包o.s.amqp.rabbit.listener移动到o.s.amqp.rabbit.listener.api

从版本 2.1.7 开始,Channel在消息消息头中可用;这允许你在使用AcknowledgeMode.MANUAL时对失败的消息进行 ACK 或 NACK:

public Object handleError(Message amqpMessage, org.springframework.messaging.Message<?> message,
          ListenerExecutionFailedException exception) {
              ...
              message.getHeaders().get(AmqpHeaders.CHANNEL, Channel.class)
                  .basicReject(message.getHeaders().get(AmqpHeaders.DELIVERY_TAG, Long.class),
                               true);
          }

从版本 2.2.18 开始,如果抛出消息转换异常,将调用错误处理程序,在message参数中使用null。这允许应用程序向调用者发送一些结果,表明收到了格式错误的消息。以前,此类错误是由容器抛出和处理的。

# 容器管理

为注释创建的容器未在应用程序上下文中注册。你可以通过在RabbitListenerEndpointRegistry Bean 上调用getListenerContainers()来获得所有容器的集合。然后可以迭代这个集合,例如,停止或启动所有容器,或者调用注册表本身上的Lifecycle方法,这些方法将调用每个容器上的操作。

你还可以通过使用其id,使用getListenerContainer(String id),获得对单个容器的引用——例如,对于上面的代码片段创建的容器,registry.getListenerContainer("multi")

从版本 1.5.2 开始,你可以使用getListenerContainerIds()获得已注册容器的id值。

从版本 1.5 开始,你现在可以将group分配到RabbitListener端点上的容器。这提供了一种机制来获取对容器子集的引用。添加一个group属性会导致类型Collection<MessageListenerContainer>的 Bean 被注册到带有组名的上下文中。

# @rabbitlistener with batching

当接收到a batch的消息时,解批处理通常由容器执行,侦听器在一次调用一条消息。从版本 2.2 开始,你可以将侦听器容器工厂和侦听器配置为在一个调用中接收整个批处理,只需设置工厂的batchListener属性,并将方法有效负载参数设置为List:

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());
    factory.setBatchListener(true);
    return factory;
}

@RabbitListener(queues = "batch.1")
public void listen1(List<Thing> in) {
    ...
}

// or

@RabbitListener(queues = "batch.2")
public void listen2(List<Message<Thing>> in) {
    ...
}

batchListener属性设置为 true 会自动关闭工厂创建的容器中的deBatchingEnabled容器属性(除非consumerBatchEnabledtrue-见下文)。实际上,将 debatching 从容器移动到侦听器适配器,并且适配器将创建传递给侦听器的列表。

启用批处理的工厂不能与多方法监听器一起使用。

也从版本 2.2 开始。当一次只接收一条批处理消息时,最后一条消息包含一个布尔头,该头设置为true。这个头可以通过添加@Header(AmqpHeaders.LAST_IN_BATCH)BooleanLast<gtr="1416"/>MessageProperties.islastinBatch()<gtr="1417"/>AmQpHeaders 来获得。batch_size` 是用每个消息片段中批处理的大小填充的。

此外,在SimpleMessageListenerContainer中添加了一个新的属性consumerBatchEnabled。如果这是真的,容器将创建一批消息,最多为batchSize;如果receiveTimeout在没有新消息到达的情况下经过,则传递部分批消息。如果收到了生产者创建的批处理,则将其删除并添加到消费者侧批处理中;因此,实际交付的消息数量可能超过batchSize,表示从代理收到的消息的数量。deBatchingEnabledconsumerBatchEnabled为真时必须为真;容器工厂将强制执行此要求。

@Bean
public SimpleRabbitListenerContainerFactory consumerBatchContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(rabbitConnectionFactory());
    factory.setConsumerTagStrategy(consumerTagStrategy());
    factory.setBatchListener(true); // configures a BatchMessageListenerAdapter
    factory.setBatchSize(2);
    factory.setConsumerBatchEnabled(true);
    return factory;
}

当使用consumerBatchEnabled@RabbitListener时:

@RabbitListener(queues = "batch.1", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch1(List<Message> amqpMessages) {
    this.amqpMessagesReceived = amqpMessages;
    this.batch1Latch.countDown();
}

@RabbitListener(queues = "batch.2", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch2(List<org.springframework.messaging.Message<Invoice>> messages) {
    this.messagingMessagesReceived = messages;
    this.batch2Latch.countDown();
}

@RabbitListener(queues = "batch.3", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch3(List<Invoice> strings) {
    this.batch3Strings = strings;
    this.batch3Latch.countDown();
}
  • 第一个是用 RAW 调用的,未转换的org.springframework.amqp.core.Messages 接收到的。

  • 第二个是用org.springframework.messaging.Message<?>s 调用的,带有转换的有效负载和映射的头/属性。

  • 第三种是使用转换后的有效负载进行调用,不访问 headers/properteis。

还可以添加Channel参数,该参数在使用MANUALACK 模式时经常使用。这在第三个示例中不是很有用,因为你无法访问delivery_tag属性。

# 使用容器工厂

引入了监听器容器工厂来支持@RabbitListener并使用RabbitListenerEndpointRegistry注册容器,如程序化端点注册中所讨论的那样。

从版本 2.1 开始,它们可以用于创建任何侦听器容器——甚至是没有侦听器的容器(例如在 Spring 集成中使用的容器)。当然,必须在容器启动之前添加一个侦听器。

创建这样的容器有两种方法:

  • 使用 simplerabbitlistenerendPoint

  • 创建后添加监听器

下面的示例展示了如何使用SimpleRabbitListenerEndpoint创建侦听器容器:

@Bean
public SimpleMessageListenerContainer factoryCreatedContainerSimpleListener(
        SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) {
    SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
    endpoint.setQueueNames("queue.1");
    endpoint.setMessageListener(message -> {
        ...
    });
    return rabbitListenerContainerFactory.createListenerContainer(endpoint);
}

下面的示例展示了如何在创建后添加侦听器:

@Bean
public SimpleMessageListenerContainer factoryCreatedContainerNoListener(
        SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory) {
    SimpleMessageListenerContainer container = rabbitListenerContainerFactory.createListenerContainer();
    container.setMessageListener(message -> {
        ...
    });
    container.setQueueNames("test.no.listener.yet");
    return container;
}

在这两种情况下,侦听器也可以是ChannelAwareMessageListener,因为它现在是MessageListener的子接口。

如果你希望创建具有类似属性的多个容器,或者使用预先配置的容器工厂(例如 Spring Boot Auto Configuration 提供的容器工厂),那么这些技术是有用的。

以这种方式创建的容器是正常的@Bean实例,并且不在RabbitListenerEndpointRegistry中注册。
# 异步@RabbitListener返回类型

从版本 2.1 开始,@RabbitListener(和@RabbitHandler)方法可以用异步返回类型ListenableFuture<?>Mono<?>来指定,让回复被异步发送。

侦听器容器工厂必须配置AcknowledgeMode.MANUAL,这样使用者线程就不会对消息进行 ACK;相反,异步完成将在异步操作完成时对消息进行 ACK 或 NACK,
当异步结果以错误完成时,消息是否被重新请求取决于抛出的异常类型、容器配置和容器错误处理程序,
默认情况下,消息将被重新请求,除非容器的defaultRequeueRejected属性被设置为false(默认情况下是true)。
如果异步结果是用AmqpRejectAndDontRequeueException完成的,则消息将不会被重新请求。,
如果容器的defaultRequeueRejected属性是false,你可以通过将 Future 的异常设置为ImmediateRequeueException来重写它,并且消息将被重新请求,
如果侦听器方法中发生了某些异常,从而阻止了异步结果对象的创建,你必须捕获该异常并返回一个适当的返回对象,该对象将导致消息被确认或重新请求。

从版本 2.2.21、2.3.13、2.4.1 开始,当检测到异步返回类型时,AcknowledgeMode将自动设置MANUAL。此外,带有致命异常的传入消息将被单独地负面确认,以前任何先前未确认的消息也被负面确认。

# 线程和异步消费者

异步消费者涉及许多不同的线程。

SimpleMessageListenerContainer中配置的TaskExecutor中的线程用于在RabbitMQ Client传递新消息时调用MessageListener。如果未配置,则使用SimpleAsyncTaskExecutor。如果使用池执行程序,则需要确保池大小足以处理配置的并发性。使用DirectMessageListenerContainer,在RabbitMQ Client线程上直接调用MessageListener。在这种情况下,taskExecutor用于监视消费者的任务。

当使用默认SimpleAsyncTaskExecutor时,对于侦听器所调用的线程,侦听器容器beanNamethreadNamePrefix中使用。
这对于日志分析很有用。
我们通常建议始终在日志附录配置中包括线程名称。
TaskExecutor通过容器上的taskExecutor属性特别提供时,它被原封不动地使用,
建议你使用类似的技术来命名由自定义TaskExecutor Bean 定义创建的线程,以帮助在日志消息中进行线程标识。

在创建连接时,在CachingConnectionFactory中配置的Executor被传递到RabbitMQ Client中,其线程用于将新消息传递到侦听器容器。如果未对此进行配置,则客户机将使用一个内部线程池执行器,每个连接的池大小(在编写时)为Runtime.getRuntime().availableProcessors() * 2

如果你有大量的工厂或者正在使用CacheMode.CONNECTION,那么你可能希望考虑使用带有足够线程的共享ThreadPoolTaskExecutor来满足你的工作负载。

使用DirectMessageListenerContainer,你需要确保连接工厂配置了一个任务执行器,该执行器具有足够的线程来支持跨使用该工厂的所有侦听器容器的所需并发性。
缺省池大小(在编写本文时)为Runtime.getRuntime().availableProcessors() * 2

RabbitMQ client使用ThreadFactory为低级 I/O 操作创建线程。要修改这个工厂,你需要配置底层的 RabbitMQConnectionFactory,如配置底层客户机连接工厂中所讨论的那样。

# 选择容器

2.0 版引入了DirectMessageListenerContainer。在此之前,只有SimpleMessageListenerContainer可用。SMLC 为每个使用者使用一个内部队列和一个专用线程。如果一个容器被配置为监听多个队列,那么将使用同一个使用者线程来处理所有队列。并发性由concurrentConsumers和其他属性控制。当消息从 RabbitMQ 客户机到达时,客户机线程通过队列将消息传递给消费者线程。之所以需要这种架构,是因为在 RabbitMQ 客户端的早期版本中,不可能实现多个并发交付。较新版本的客户机具有修订后的线程模型,现在可以支持并发。这允许引入 DMLC,现在在 RabbitMQ 客户端线程上直接调用侦听器。因此,它的架构实际上比 SMLC“更简单”。然而,这种方法有一些局限性,并且 SMLC 的某些功能在 DMLC 中不可用。此外,并发性由consumersPerQueue(以及客户库的线程池)控制。concurrentConsumers和相关属性在此容器中不可用。

以下特性可用于 SMLC,但不适用于 DMLC:

  • batchSize:使用 SMLC,你可以将其设置为控制事务中传递的消息数量或减少 ACK 的数量,但它可能会导致失败后重复传递的数量增加。(DMLC 确实有messagesPerAck,你可以使用它来减少 ACK,与batchSize和 SMLC 相同,但它不能用于事务——每个消息都在单独的事务中传递和 ACK’d)。

  • consumerBatchEnabled:在消费者中启用离散消息的批处理;有关更多信息,请参见消息侦听器容器配置

  • maxConcurrentConsumers和使用者缩放间隔或触发器——DMLC 中没有自动缩放。但是,它确实允许你以编程方式更改consumersPerQueue属性,并对消费者进行相应的调整。

然而,与 SMLC 相比,DMLC 有以下好处:

  • 在运行时添加和删除队列更有效。使用 SMLC,整个使用者线程将被重新启动(所有使用者将被取消并重新创建)。使用 DMLC,不受影响的消费者不会被取消。

  • 避免了 RabbitMQ 客户端线程和使用者线程之间的上下文切换。

  • 线程在消费者之间共享,而不是在 SMLC 中为每个消费者拥有一个专用线程。但是,请参见线程和异步消费者中有关连接工厂配置的重要注释。

有关将哪些配置属性应用于每个容器的信息,请参见消息侦听器容器配置

# 检测空闲异步消费者

尽管效率很高,但异步用户的一个问题是检测它们何时空闲——如果一段时间内没有消息到达,用户可能希望采取一些措施。

从版本 1.6 开始,现在可以将侦听器容器配置为在一段时间之后没有消息传递的情况下发布ListenerContainerIdleEvent。当容器处于空闲状态时,每idleEventInterval毫秒就会发布一个事件。

要配置此功能,请在容器上设置idleEventInterval。下面的示例展示了如何在 XML 和 Java 中这样做(对于SimpleMessageListenerContainerSimpleRabbitListenerContainerFactory):

<rabbit:listener-container connection-factory="connectionFactory"
        ...
        idle-event-interval="60000"
        ...
        >
    <rabbit:listener id="container1" queue-names="foo" ref="myListener" method="handle" />
</rabbit:listener-container>
@Bean
public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    ...
    container.setIdleEventInterval(60000L);
    ...
    return container;
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(rabbitConnectionFactory());
    factory.setIdleEventInterval(60000L);
    ...
    return factory;
}

在每种情况下,当容器处于空闲状态时,每分钟都会发布一次事件。

# 事件消耗

你可以通过实现ApplicationListener来捕获空闲事件——它可以是一个普通的侦听器,也可以是一个窄到只接收这个特定事件的侦听器。还可以使用 Spring Framework4.2 中介绍的@EventListener

下面的示例将@RabbitListener@EventListener合并为一个类。你需要理解,应用程序侦听器获取所有容器的事件,因此,如果你想根据哪个容器空闲来采取特定的操作,你可能需要检查侦听器 ID。你也可以为此目的使用@EventListener``condition

这些事件有四个属性:

  • source:侦听器容器实例

  • id:侦听器 ID(或容器 Bean 名称)

  • idleTime:事件发布时容器处于空闲状态的时间

  • queueNames:容器监听的队列的名称

下面的示例展示了如何同时使用@RabbitListener@EventListener注释来创建侦听器:

public class Listener {

    @RabbitListener(id="someId", queues="#{queue.name}")
    public String listen(String foo) {
        return foo.toUpperCase();
    }

    @EventListener(condition = "event.listenerId == 'someId'")
    public void onApplicationEvent(ListenerContainerIdleEvent event) {
        ...
    }

}
事件侦听器看到所有容器的事件。
因此,在前面的示例中,我们根据侦听器 ID 缩小接收到的事件的范围。
如果希望使用 IDLE 事件停止 Lister 容器,则不应在调用侦听器的线程上调用container.stop()
这样做总是会导致延迟和不必要的日志消息。,相反,
,你应该将事件传递给一个不同的线程,然后该线程可以停止容器。
# 监视侦听器性能

从版本 2.2 开始,如果在类路径上检测到Micrometer,并且在应用程序上下文中存在MeterRegistry,则侦听器容器将自动为侦听器创建和更新微米计Timers。可以通过将容器属性micrometerEnabled设置为false来禁用计时器。

两个计时器被维护-一个用于对听者的成功调用,另一个用于失败调用。对于一个简单的MessageListener,每个配置的队列都有一对计时器。

计时器名为spring.rabbitmq.listener,并具有以下标记:

  • listenerId:(侦听器 ID 或容器 Bean 名称)

  • queue:(当consumerBatchEnabledtrue时,一个简单侦听器或配置的队列名称列表的队列名称是true-因为批处理可能包含来自多个队列的消息)

  • result:successfailure

  • exception:noneListenerExecutionFailedException

可以使用micrometerTags容器属性添加其他标记。

# 4.1.7.容器和以代理命名的队列

虽然使用AnonymousQueue实例作为自动删除队列是可取的,但从版本 2.1 开始,你可以使用带有侦听器容器的代理命名队列。下面的示例展示了如何做到这一点:

@Bean
public Queue queue() {
    return new Queue("", false, true, true);
}

@Bean
public SimpleMessageListenerContainer container() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf());
    container.setQueues(queue());
    container.setMessageListener(m -> {
        ...
    });
    container.setMissingQueuesFatal(false);
    return container;
}

请注意名称的空String。当RabbitAdmin声明队列时,它使用代理返回的名称更新Queue.actualName属性。在配置容器以使其工作时,必须使用setQueues(),以便容器可以在运行时访问声明的名称。仅仅设置名字是不够的。

在容器运行时,不能将以代理命名的队列添加到容器中。
当一个连接被重置并建立了一个新的连接时,新的队列将获得一个新的名称,
由于在重新启动的容器和重新声明的队列之间存在竞争条件,因此将容器的missingQueuesFatal属性设置为false非常重要,因为容器最初可能会尝试重新连接到旧队列。

# 4.1.8.消息转换器

AmqpTemplate还定义了用于发送和接收委托给MessageConverter的消息的几种方法。MessageConverter为每个方向提供了一个方法:一个用于转换toaMessage,另一个用于转换来自aMessage。请注意,当转换为Message时,除了对象之外,还可以提供属性。object参数通常对应于消息体。下面的清单显示了MessageConverter接口定义:

public interface MessageConverter {

    Message toMessage(Object object, MessageProperties messageProperties)
            throws MessageConversionException;

    Object fromMessage(Message message) throws MessageConversionException;

}

AmqpTemplate上的相关Message-发送方法比我们前面讨论的方法更简单,因为它们不需要Message实例。相反,MessageConverter负责“创建”每个Message,方法是将提供的对象转换为Message主体的字节数组,然后添加任何提供的MessageProperties。下面的清单显示了各种方法的定义:

void convertAndSend(Object message) throws AmqpException;

void convertAndSend(String routingKey, Object message) throws AmqpException;

void convertAndSend(String exchange, String routingKey, Object message)
    throws AmqpException;

void convertAndSend(Object message, MessagePostProcessor messagePostProcessor)
    throws AmqpException;

void convertAndSend(String routingKey, Object message,
    MessagePostProcessor messagePostProcessor) throws AmqpException;

void convertAndSend(String exchange, String routingKey, Object message,
    MessagePostProcessor messagePostProcessor) throws AmqpException;

在接收端,只有两种方法:一种接受队列名称,另一种依赖于模板的“queue”属性已设置。下面的清单显示了这两种方法的定义:

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;
异步消费者中提到的MessageListenerAdapter也使用了MessageConverter
# SimpleMessageConverter

MessageConverter策略的默认实现称为SimpleMessageConverter。这是RabbitTemplate的实例所使用的转换器,如果你没有显式地配置替代选项。它处理基于文本的内容、序列化的 Java 对象和字节数组。

#Message转换

如果输入Message的内容类型以“text”开头(例如,“text/plain”),则它还会检查 Content-Encoding 属性,以确定将MessageBody 字节数组转换为 JavaString时要使用的字符集。如果输入Message上没有设置内容编码属性,则默认情况下它使用 UTF-8 字符集。如果需要覆盖该默认设置,可以配置SimpleMessageConverter的实例,设置其defaultCharset属性,并将其注入RabbitTemplate实例。

如果输入Message的 Content-Type 属性值设置为“application/x-java-serialized-object”,则SimpleMessageConverter将尝试将字节数组反序列化为 Java 对象。虽然这对于简单的原型设计可能很有用,但我们不建议依赖 Java 序列化,因为它会导致生产者和消费者之间的紧密耦合。当然,它也排除了任何一方使用非 Java 系统的可能性。由于 AMQP 是一种线级协议,因此很不幸的是,在这样的限制下,它将失去很多这种优势。在接下来的两节中,我们将探索在不依赖 Java 序列化的情况下传递富领域对象内容的一些替代方案。

对于所有其他内容类型,SimpleMessageConverter直接以字节数组的形式返回Message正文内容。

有关重要信息,请参见Java 反序列化

# 转换为Message

当从任意 Java 对象转换为Message时,SimpleMessageConverter同样处理字节数组、字符串和可序列化实例。它将这些转换为字节(在字节数组的情况下,没有任何可转换的内容),并且相应地 SESContent-Type 属性。如果要转换的Object与这些类型之一不匹配,则Message正文为空。

# SerializerMessageConverter

这种转换器类似于SimpleMessageConverter,只是它可以配置与其它 Spring 框架SerializerDeserializer实现的application/x-java-serialized-object转换。

有关重要信息,请参见Java 反序列化

# Jackson2jsonmessageconverter

本节介绍使用Jackson2JsonMessageConverter转换到Message和从Message转换的情况。它有以下几个部分:

  • [转换为Message](#Jackson2jsonMessageConverter-to-Message)

  • [转换自Message](#Jackson2jsonmessageconverter-from-message)

# 转换为Message

正如上一节中提到的,通常不建议依赖 Java 序列化。JSON(JavaScript Object Notation,JavaScript Object Notation,JavaScript Object Notation)是一种比较常见的替代方法,它在不同的语言和平台上更灵活、更可移植。转换器可以在任何RabbitTemplate实例上配置,以覆盖其对SimpleMessageConverter默认值的使用。Jackson2JsonMessageConverter使用com.fasterxml.jackson2.x 库。下面的示例配置Jackson2JsonMessageConverter:

<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <property name="connectionFactory" ref="rabbitConnectionFactory"/>
    <property name="messageConverter">
        <bean class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
            <!-- if necessary, override the DefaultClassMapper -->
            <property name="classMapper" ref="customClassMapper"/>
        </bean>
    </property>
</bean>

如上面所示,Jackson2JsonMessageConverter默认使用DefaultClassMapper。类型信息被添加到(并从)MessageProperties。如果入站消息在MessageProperties中不包含类型信息,但你知道期望的类型,则可以使用defaultType属性配置静态类型,如下例所示:

<bean id="jsonConverterWithDefaultType"
      class="o.s.amqp.support.converter.Jackson2JsonMessageConverter">
    <property name="classMapper">
        <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
            <property name="defaultType" value="thing1.PurchaseOrder"/>
        </bean>
    </property>
</bean>

此外,你还可以从*TypeId*标头中的值提供自定义映射。下面的示例展示了如何做到这一点:

@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
    Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
    jsonConverter.setClassMapper(classMapper());
    return jsonConverter;
}

@Bean
public DefaultClassMapper classMapper() {
    DefaultClassMapper classMapper = new DefaultClassMapper();
    Map<String, Class<?>> idClassMapping = new HashMap<>();
    idClassMapping.put("thing1", Thing1.class);
    idClassMapping.put("thing2", Thing2.class);
    classMapper.setIdClassMapping(idClassMapping);
    return classMapper;
}

现在,如果发送系统将头设置为thing1,转换器将创建一个Thing1对象,依此类推。有关从非 Spring 应用程序转换消息的完整讨论,请参见Receiving JSON from Non-Spring Applications示例应用程序。

#Message转换

根据发送系统添加到头部的类型信息,将入站消息转换为对象。

在 1.6 之前的版本中,如果不存在类型信息,则转换将失败。从版本 1.6 开始,如果缺少类型信息,转换器将使用 Jackson 默认值(通常是映射)来转换 JSON。

此外,从版本 1.6 开始,当使用@RabbitListener注释(在方法上)时,推断的类型信息将添加到MessageProperties中。这使得转换器可以转换为目标方法的参数类型。这仅在存在一个没有注释的参数或一个带有@Payload注释的参数时才适用。在分析过程中忽略类型Message的参数。

默认情况下,推断的类型信息将覆盖由发送系统创建的入站*TypeId*和相关标题

这将使接收系统自动转换为不同的域对象。
这仅适用于此。如果参数类型是具体的(不是抽象的或接口的),或者它来自java.util包。
在所有其他情况下,使用*TypeId*和相关的标题。
在某些情况下,你可能希望重写缺省行为,并始终使用*TypeId*信息。,例如,
,假设你有一个@RabbitListener,它接受一个Thing1参数,但是消息包含一个Thing2,该
Thing1的子类(这是具体的)。
推断出的类型将是不正确的。
来处理这种情况,将TYPE_ID上的TypePrecedence属性设置为TYPE_ID,而不是默认INFERRED

(该属性实际上位于转换器的DefaultJackson2JavaTypeMapper上,但转换器
上提供了一个设置器,以方便使用。)
如果你注入一个自定义的类型映射器,你应该在映射器上设置属性。
当从Message转换时,传入的MessageProperties.getContentType()必须是符合 JSON 的(contentType.contains("json")用于检查)。
从版本 2.2 开始,如果没有contentType属性,则假定application/json,或者它具有默认值application/octet-stream
以恢复到以前的行为(返回一个未转换的byte[]),将转换器的assumeSupportedContentType属性设置为false
如果不支持内容类型,则生成WARN日志消息Could not convert incoming message with content-type […​],是发出的,并且message.getBody()按原样返回-作为byte[]
因此,为了满足Jackson2JsonMessageConverter在消费者方面的要求,生产者必须添加contentType消息属性——例如,作为application/jsontext/x-json,或者通过使用Jackson2JsonMessageConverter,它会自动设置标题。
下面的清单显示了许多转换器调用:
@RabbitListener
public void thing1(Thing1 thing1) {...}

@RabbitListener
public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<Foo> message) {...}

@RabbitListener
public void thing1(Thing1 thing1, String bar) {...}

@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<?> message) {...}

在前面清单中的前四种情况中,转换器尝试转换为Thing1类型。第五个示例是无效的,因为我们无法确定哪个参数应该接收消息负载。在第六个示例中,由于泛型类型是WildcardType,所以 Jackson 默认应用。

但是,你可以创建一个自定义转换器,并使用targetMethod消息属性来决定将 JSON 转换为哪种类型。

只有在方法级别声明@RabbitListener注释时,才能实现这种类型推断。
对于类级别@RabbitListener,转换后的类型用于选择调用哪个@RabbitHandler方法。
由于这个原因,基础设施提供了targetObject属性,你可以在自定义的
转换器中使用它来确定类型。
从版本 1.6.11 开始,Jackson2JsonMessageConverter,因此,DefaultJackson2JavaTypeMapperDefaultClassMapper)提供trustedPackages选项,以克服序列化小工具 (opens new window)漏洞。
默认情况下,对于向后兼容,Jackson2JsonMessageConverter信任所有包——也就是说,它使用*作为选项。
# 反序列化抽象类

在版本 2.2.8 之前,如果@RabbitListener的推断类型是抽象类(包括接口),则转换器将返回到头部中查找类型信息,如果存在,则使用该信息;如果不存在,则尝试创建抽象类。当使用自定义反序列化器来处理抽象类的自定义ObjectMapper时,但传入的消息具有无效的类型标头,这会导致一个问题。

从版本 2.2.8 开始,默认情况下保留以前的行为。如果你有这样的自定义ObjectMapper,并且希望忽略类型头,并且总是使用推断类型进行转换,那么将alwaysConvertToInferredType设置为true。这是向后兼容性所必需的,并且可以避免尝试转换失败时的开销(使用标准ObjectMapper)。

# 使用 Spring 数据投影接口

从版本 2.2 开始,你可以将 JSON 转换为 Spring 数据投影接口,而不是具体的类型。这允许对数据进行非常有选择性的、低耦合的绑定,包括从 JSON 文档中的多个位置查找值。例如,以下接口可以定义为消息有效负载类型:

interface SomeSample {

  @JsonPath({ "$.username", "$.user.name" })
  String getUsername();

}
@RabbitListener(queues = "projection")
public void projection(SomeSample in) {
    String username = in.getUsername();
    ...
}

默认情况下,访问器方法将用于在接收的 JSON 文档中查找属性名称 AS 字段。@JsonPath表达式允许定制值查找,甚至可以定义多个 JSON 路径表达式,从多个位置查找值,直到表达式返回实际值。

要启用此功能,请将消息转换器上的useProjectionForInterfaces设置为true。你还必须将spring-data:spring-data-commonscom.jayway.jsonpath:json-path添加到类路径。

当用作@RabbitListener方法的参数时,接口类型将作为正常类型自动传递给转换器。

#Message转换为RabbitTemplate

如前所述,类型信息在消息头中传递,以在从消息转换时协助转换器。这在大多数情况下都行得通。然而,当使用泛型类型时,它只能转换简单的对象和已知的“容器”对象(列表、数组和映射)。从版本 2.0 开始,Jackson2JsonMessageConverter实现了SmartMessageConverter,这使得它可以与新的RabbitTemplate方法一起使用,该方法接受ParameterizedTypeReference参数。这允许转换复杂的泛型类型,如下例所示:

Thing1<Thing2<Cat, Hat>> thing1 =
    rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
从版本 2.1 开始,AbstractJsonMessageConverter类已被删除。
它不再是Jackson2JsonMessageConverter的基类。
它已被AbstractJackson2MessageConverter取代。
# MarshallingMessageConverter

还有一个选择是MarshallingMessageConverter。它委托给 Spring OXM 库的MarshallerUnmarshaller策略接口的实现。你可以阅读有关该库的更多信息here (opens new window)。在配置方面,最常见的是仅提供构造函数参数,因为Marshaller的大多数实现也实现Unmarshaller。下面的示例展示了如何配置MarshallingMessageConverter:

<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <property name="connectionFactory" ref="rabbitConnectionFactory"/>
    <property name="messageConverter">
        <bean class="org.springframework.amqp.support.converter.MarshallingMessageConverter">
            <constructor-arg ref="someImplemenationOfMarshallerAndUnmarshaller"/>
        </bean>
    </property>
</bean>
# Jackson2XmlMessageConverter

这个类是在版本 2.1 中引入的,可以用于将消息从 XML 转换为 XML。

Jackson2XmlMessageConverterJackson2JsonMessageConverter都具有相同的基类:AbstractJackson2MessageConverter

引入AbstractJackson2MessageConverter类来替换已删除的类:AbstractJsonMessageConverter

Jackson2XmlMessageConverter使用com.fasterxml.jackson2.x 库。

你可以用与Jackson2JsonMessageConverter相同的方式使用它,只是它支持 XML 而不是 JSON。下面的示例配置Jackson2JsonMessageConverter:

<bean id="xmlConverterWithDefaultType"
        class="org.springframework.amqp.support.converter.Jackson2XmlMessageConverter">
    <property name="classMapper">
        <bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
            <property name="defaultType" value="foo.PurchaseOrder"/>
        </bean>
    </property>
</bean>

有关更多信息,请参见Jackson2JSONMessageConverter

从版本 2.2 开始,如果不存在contentType属性,或者它具有默认值application/octet-stream,则假定application/xml
恢复到以前的行为(返回未转换的byte[]),将转换器的assumeSupportedContentType属性设置为false
# ContentTypeDelegatingMessageConverter

这个类是在版本 1.4.2 中引入的,允许基于MessageProperties中的 Content type 属性将任务委托给特定的MessageConverter。默认情况下,如果没有contentType属性,或者有一个值与所有配置的转换器都不匹配,那么它将委托给SimpleMessageConverter。下面的示例配置ContentTypeDelegatingMessageConverter:

<bean id="contentTypeConverter" class="ContentTypeDelegatingMessageConverter">
    <property name="delegates">
        <map>
            <entry key="application/json" value-ref="jsonMessageConverter" />
            <entry key="application/xml" value-ref="xmlMessageConverter" />
        </map>
    </property>
</bean>
# Java 反序列化

本节介绍如何反序列化 Java 对象。

当从不受信任的源反序列化 Java 对象时,可能存在一个漏洞,

如果你接受来自不受信任的源的消息,并且content-type的值为application/x-java-serialized-object,你应该
考虑配置哪些包和类被允许进行反序列化。
这适用于SimpleMessageConverterSerializerMessageConverter,当它被配置为隐式或通过配置使用DefaultDeserializer时。
默认情况下,允许的列表为空,这意味着所有的类都是反序列化的。

你可以设置一个模式列表,例如thing1.**,thing1.thing2.Cat.MySafeClass
在找到匹配之前,将按顺序检查模式。
如果不匹配,则将不匹配,抛出一个SecurityException

可以在这些转换器上使用allowedListPatterns属性设置模式。
# 消息属性转换器

MessagePropertiesConverter策略接口用于在兔子客户端BasicProperties和 Spring AMQPMessageProperties之间进行转换。默认的实现(DefaultMessagePropertiesConverter)对于大多数目的来说通常是足够的,但是如果需要,你可以实现自己的实现。当大小不大于1024字节时,默认属性转换器将类型BasicProperties的元素转换为String实例。较大的LongString实例未被转换(请参见下一段)。可以使用构造函数参数重写此限制。

从版本 1.6 开始,长于长字符串限制(默认值:1024)的头现在由DefaultMessagePropertiesConverter默认设置为LongString实例。你可以通过getBytes[]toString()getStream()方法访问内容。

以前,DefaultMessagePropertiesConverter将这样的标题“转换”为DataInputStream(实际上,它只引用了LongString实例的DataInputStream)。在输出时,这个头不会被转换(除了转换为字符串——例如,通过在流上调用toString()[[email protected]](/cdn-cgi/l/email-protection))。

大型传入LongString头现在也可以在输出上正确地“转换”(默认情况下)。

提供了一个新的构造函数,使你可以将转换器配置为像以前一样工作。下面的清单显示了该方法的 Javadoc 注释和声明:

/**
 * Construct an instance where LongStrings will be returned
 * unconverted or as a java.io.DataInputStream when longer than this limit.
 * Use this constructor with 'true' to restore pre-1.6 behavior.
 * @param longStringLimit the limit.
 * @param convertLongLongStrings LongString when false,
 * DataInputStream when true.
 * @since 1.6
 */
public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... }

同样从版本 1.6 开始,在MessageProperties中添加了一个名为correlationIdString的新属性。以前,当从 RabbitMQ 客户端使用的BasicProperties转换到和转换时,执行一个不必要的byte[] <→ String转换,因为MessageProperties.correlationId是一个byte[],但是BasicProperties使用一个String。(最终,RabbitMQ 客户机使用 UTF-8 将String转换为字节以放入协议消息)。

为了提供最大的向后兼容性,在DefaultMessagePropertiesConverter中添加了一个名为correlationIdPolicy的新属性。这需要一个DefaultMessagePropertiesConverter.CorrelationIdPolicy枚举参数。默认情况下,它被设置为BYTES,它复制了之前的行为。

对于入站消息:

  • STRING:仅映射correlationIdString属性

  • BYTES:仅映射correlationId属性

  • BOTH:两个属性都映射了

对于出站消息:

  • STRING:仅映射correlationIdString属性

  • BYTES:仅映射correlationId属性

  • BOTH:这两个属性都被考虑,String属性优先。

同样从版本 1.6 开始,入站deliveryMode属性不再映射到MessageProperties.deliveryMode。它被映射到MessageProperties.receivedDeliveryMode。此外,入站userId属性不再映射到MessageProperties.userId。它被映射到MessageProperties.receivedUserId。如果出站消息使用相同的MessageProperties对象,则这些更改是为了避免这些属性的意外传播。

从版本 2.2 开始,DefaultMessagePropertiesConverter使用getName()而不是toString()转换类型为Class<?>的任何自定义标头;这避免了使用toString()表示来解析类名称的应用程序。对于滚动升级,你可能需要更改你的消费者来理解这两种格式,直到所有的生产者都升级了。

# 4.1.9.修改消息-压缩和更多

存在一些扩展点。它们允许你对消息执行一些处理,可以在消息发送到 RabbitMQ 之前,也可以在收到消息之后立即进行处理。

正如可以在消息转换器中看到的那样,这样的一个扩展点是在AmqpTemplate``convertAndReceive操作中,其中可以提供一个MessagePostProcessor。例如,在你的 POJO 被转换之后,MessagePostProcessor允许你在Message上设置自定义标题或属性。

从版本 1.4.2 开始,在RabbitTemplate-setBeforePublishPostProcessors()setAfterReceivePostProcessors()中添加了额外的扩展点。第一个使后处理器能够在发送到 RabbitMQ 之前立即运行。在使用批处理时(参见Batching),这是在组装批处理之后和发送批处理之前调用的。第二个是在接收到消息后立即调用的。

这些扩展点用于诸如压缩的特征,并且为此目的,提供了几个MessagePostProcessor实现。GZipPostProcessorZipPostProcessorDeflaterPostProcessor在发送消息之前压缩消息,并且GUnzipPostProcessorUnzipPostProcessorInflaterPostProcessor解压接收到的消息。

从版本 2.1.5 开始,GZipPostProcessor可以配置copyProperties = true选项,以复制原始消息属性,默认情况下,由于性能原因,这些属性可以重用,并使用压缩内容编码和可选的MessageProperties.SPRING_AUTO_DECOMPRESS报头进行修改。
如果保留对原始出站消息的引用,其属性也会发生变化。
因此,如果你的应用程序使用这些消息后处理程序保留出站消息的副本,考虑打开copyProperties选项。
从版本 2.2.12 开始,你可以配置压缩后置处理器在内容编码元素之间使用的分隔符。
在版本 2.2.11 和之前,这是硬编码为:,现在默认设置为,
解压程序将同时使用这两个分隔符。
但是,如果你使用 2.3 或更高版本发布消息,而使用 2.2.11 或更高版本,则必须将压缩器上的encodingDelimiter属性设置为:
当你的用户升级到 2.2.11 或更高版本时,你可以恢复到,的默认值。

类似地,SimpleMessageListenerContainer也有一个setAfterReceivePostProcessors()方法,允许在容器接收到消息后执行解压缩。

从版本 2.1.4 开始,addBeforePublishPostProcessors()addAfterReceivePostProcessors()已添加到RabbitTemplate中,以允许将新的后处理程序分别附加到发布前和接收后处理程序的列表中。还提供了删除后置处理器的方法。类似地,AbstractMessageListenerContainer还添加了addAfterReceivePostProcessors()removeAfterReceivePostProcessor()方法。有关更多详细信息,请参见RabbitTemplateAbstractMessageListenerContainer的 javadoc。

# 4.1.10.请求/回复消息

AmqpTemplate还提供了各种sendAndReceive方法,这些方法接受前面描述的用于单向发送操作的相同参数选项(exchangeroutingKeyMessage)。这些方法在请求-回复场景中非常有用,因为它们在发送之前处理必要的reply-to属性的配置,并且可以在内部为此目的创建的独占队列中侦听应答消息。

在将MessageConverter应用于请求和答复时,也可以使用类似的请求-答复方法。这些方法被命名为convertSendAndReceive。有关更多详细信息,请参见AmqpTemplate的[javadoc](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/amqptemplate.html)。

从版本 1.5.0 开始,每个sendAndReceive方法变体都有一个重载版本,它接受CorrelationData。与正确配置的连接工厂一起,这将为发送端的操作启用发行者确认的接收。有关更多信息,请参见相关发布者确认并返回和[javadoc forRabbitOperations](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/rabbit/core/rabbitoperations.html)。

从版本 2.0 开始,这些方法有一些变体(convertSendAndReceiveAsType),它们接受一个额外的ParameterizedTypeReference参数来转换复杂的返回类型。模板必须配置为SmartMessageConverter。有关更多信息,请参见[从Message转换为RabbitTemplate]。

从版本 2.1 开始,你可以使用noLocalReplyConsumer选项配置RabbitTemplate,以控制用于回复消费者的noLocal标志。默认情况下,这是false

# 回复超时

默认情况下,发送和接收方法在 5 秒后超时并返回 null。你可以通过设置replyTimeout属性来修改此行为。从版本 1.5 开始,如果你将mandatory属性设置为true(或者对于特定的消息,mandatory-expression计算为true),如果无法将消息传递到队列,则将抛出AmqpMessageReturnedException。此异常具有returnedMessagereplyCodereplyText属性,以及用于发送的exchangeroutingKey属性。

此功能使用 Publisher Returns。
你可以通过在CachingConnectionFactory上将true设置为true(参见发布者确认并返回)来启用它。
此外,你还必须没有用ReturnCallback注册你自己的ReturnCallback

从版本 2.1.2 开始,添加了一个replyTimedOut方法,让子类被告知超时,以便它们可以清理任何保留的状态。

从版本 2.0.11 和 2.1.3 开始,当你使用默认的DirectReplyToMessageListenerContainer时,你可以通过设置模板的replyErrorHandler属性来添加错误处理程序。对于任何失败的交付,例如在没有相关标头的情况下收到的延迟回复和消息,都会调用此错误处理程序。传入的异常是ListenerExecutionFailedException,它具有failedMessage属性。

# RabbitMQ 直接回复-回复
从 3.4.0 版本开始,RabbitMQ 服务器支持直接回复 (opens new window)
这消除了固定回复队列的主要原因(以避免需要创建临时队列)对于每个请求)。
以 Spring AMQP 版本 1.4.1 开始的直接回复默认情况下使用(如果服务器支持的话),而不是创建临时回复队列。
当没有replyQueue被提供时(或者它的名称设置为amq.rabbitmq.reply-to),RabbitTemplate自动检测是否支持直接回复,并使用它或退回到使用临时回复队列。
当使用直接回复时,不需要reply-listener,也不应进行配置。

响应侦听器仍然支持命名队列(amq.rabbitmq.reply-to除外),允许控制响应并发等。

从版本 1.6 开始,如果你希望对每个回复使用一个临时的、排他的、自动删除队列,请将useTemporaryReplyQueues属性设置为true。如果设置replyAddress,则忽略此属性。

你可以通过子类化RabbitTemplate并重写useDirectReplyTo()来检查不同的条件,从而更改决定是否使用直接回复的条件。该方法仅在发送第一个请求时调用一次。

在版本 2.0 之前,RabbitTemplate为每个请求创建一个新的使用者,并在收到答复(或超时)时取消该使用者。现在,模板使用DirectReplyToMessageListenerContainer代替,让消费者被重用。该模板仍然负责将回复进行关联,因此不存在将延迟的回复发送给其他发件人的风险。如果要恢复到以前的行为,请将useDirectReplyToContainer(使用 XML 配置时direct-reply-to-container)属性设置为 false。

AsyncRabbitTemplate没有这样的选项。当使用直接回复时,它总是使用DirectReplyToContainer作为回复。

从版本 2.3.7 开始,模板有一个新的属性useChannelForCorrelation。当这是true时,服务器不必将相关 ID 从请求消息头复制到回复消息。相反,用于发送请求的通道用于将答复与请求关联起来。

# 与回复队列的消息相关性

当使用固定的应答队列(amq.rabbitmq.reply-to除外)时,必须提供相关数据,以便能够将应答与请求关联起来。见RabbitMQ 远程过程调用 (opens new window)。默认情况下,标准correlationId属性用于保存相关数据。但是,如果希望使用自定义属性来保存相关数据,则可以在 <rabbit-template/>上设置correlation-key属性。显式地将属性设置为correlationId与省略该属性相同。对于相关数据,客户机和服务器必须使用相同的报头。

Spring AMQP 版本 1.1 对此数据使用了一个名为spring_reply_correlation的自定义属性。
如果你希望用当前版本恢复到此行为(也许是为了与使用 1.1 的另一个应用程序保持兼容性),则必须将该属性设置为spring_reply_correlation

默认情况下,模板会生成自己的相关 ID(忽略用户提供的任何值)。如果你希望使用自己的相关 ID,请将RabbitTemplate实例的userCorrelationId属性设置为true

相关 ID 必须是唯一的,以避免对请求返回错误答复的可能性。
# 回复侦听器容器

当使用 3.4.0 之前的 RabbitMQ 版本时,将为每个回复使用一个新的临时队列。但是,可以在模板上配置单个回复队列,这样效率更高,还可以让你在该队列上设置参数。但是,在这种情况下,你还必须提供一个 <reply-listener/>子元素。这个元素为应答队列提供了一个侦听器容器,模板就是侦听器。在 <listener-container/>上允许的所有消息侦听器容器配置属性在元素上都是允许的,但connection-factorymessage-converter除外,它们是从模板的配置中继承而来的。

如果你运行应用程序的多个实例或使用多个RabbitTemplate实例,则MUST对每个实例使用唯一的回复队列,
RabbitMQ 无法从队列中选择消息,因此,如果它们都使用相同的队列,每个实例都会竞争回复,而不一定会收到自己的回复。

下面的示例定义了一个带有连接工厂的 Rabbit 模板:

<rabbit:template id="amqpTemplate"
        connection-factory="connectionFactory"
        reply-queue="replies"
        reply-address="replyEx/routeReply">
    <rabbit:reply-listener/>
</rabbit:template>

虽然容器和模板共享一个连接工厂,但它们不共享一个通道。因此,请求和响应不是在同一个事务(如果是事务的话)中执行的。

在 1.5.0 版本之前,reply-address属性是不可用的,
回复总是通过使用默认的 Exchange 和reply-queue名称作为路由密钥来路由的,
仍然是默认的,但是你现在可以指定新的reply-address属性。
reply-address中的reply-address可以包含一个具有<exchange>/<routingKey>表单的地址,并且将回复路由到指定的交换并被路由到与路由密钥绑定的队列。
reply-address的优先权高于reply-queue
当仅使用reply-address时,<reply-listener>必须配置为单独的<listener-container>组件。
reply-addressreply-queue(或queues属性在<listener-container>上)必须在逻辑上引用相同的队列。

在这种配置下,使用SimpleListenerContainer来接收回复,而RabbitTemplateMessageListener。当使用<rabbit:template/>名称空间元素定义模板时,如前面的示例所示,解析器将模板中的容器和线定义为侦听器。

当模板不使用固定的replyQueue(或正在使用直接回复—参见RabbitMQ 直接回复)时,不需要侦听器容器。
直接reply-to是使用 RabbitMQ3.4.0 或更高版本时的首选机制。

如果你将RabbitTemplate定义为<bean/>,或者使用@Configuration类将其定义为@Bean,或者当你以编程方式创建模板时,你需要自己定义并连接应答侦听器容器。如果没有做到这一点,模板将永远不会收到回复,最终会超时并返回 null 作为对sendAndReceive方法的调用的回复。

从版本 1.5 开始,RabbitTemplate检测是否已将其配置为MessageListener以接收回复。如果不是,则尝试用IllegalStateException发送和接收带有回复地址的消息失败(因为这些回复永远不会收到)。

此外,如果使用了简单的replyAddress(队列名称),则应答侦听器容器将验证它正在侦听具有相同名称的队列。如果回复地址是 Exchange 和 Routing Key,并且写入了调试日志消息,则无法执行此检查。

在连接应答侦听器和模板时,重要的是要确保模板的replyAddress和容器的queues(或queueNames)属性指向相同的队列。
模板将应答地址插入到出站消息replyTo属性中。

下面的清单展示了如何手动连接 bean 的示例:

<bean id="amqpTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
    <constructor-arg ref="connectionFactory" />
    <property name="exchange" value="foo.exchange" />
    <property name="routingKey" value="foo" />
    <property name="replyQueue" ref="replyQ" />
    <property name="replyTimeout" value="600000" />
    <property name="useDirectReplyToContainer" value="false" />
</bean>

<bean class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
    <constructor-arg ref="connectionFactory" />
    <property name="queues" ref="replyQ" />
    <property name="messageListener" ref="amqpTemplate" />
</bean>

<rabbit:queue id="replyQ" name="my.reply.queue" />
    @Bean
    public RabbitTemplate amqpTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        rabbitTemplate.setMessageConverter(msgConv());
        rabbitTemplate.setReplyAddress(replyQueue().getName());
        rabbitTemplate.setReplyTimeout(60000);
        rabbitTemplate.setUseDirectReplyToContainer(false);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer replyListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        container.setQueues(replyQueue());
        container.setMessageListener(amqpTemplate());
        return container;
    }

    @Bean
    public Queue replyQueue() {
        return new Queue("my.reply.queue");
    }

RabbitTemplate中显示了一个完整的这个测试用例 (opens new window)示例,该示例连接了一个固定的应答队列,以及一个处理请求并返回应答的“远程”侦听器容器。

当回复超时(replyTimeout)时,sendAndReceive()方法返回 null。

在版本 1.3.6 之前,对超时邮件的延迟回复只会被记录。现在,如果收到了一个延迟的回复,它将被拒绝(模板抛出一个AmqpRejectAndDontRequeueException)。如果回复队列被配置为将被拒绝的消息发送到死信交换,则可以检索该回复以供以后进行分析。要做到这一点,需要将一个队列与配置的死信交换绑定,其路由密钥与回复队列的名称相等。

有关配置死字的更多信息,请参见RabbitMQ 死信文档 (opens new window)。你还可以查看FixedReplyQueueDeadLetterTests测试用例。

# 异步兔子模板

1.6 版引入了AsyncRabbitTemplate。它具有与[AmqpTemplate](#amqp-template)上的方法类似的sendAndReceive(和convertSendAndReceive)方法。然而,它们返回的不是阻塞,而是ListenableFuture

sendAndReceive方法返回一个RabbitMessageFutureconvertSendAndReceive方法返回一个RabbitConverterFuture

你可以稍后通过在 Future 上调用get()来同步检索结果,也可以注册一个与结果异步调用的回调。下面的清单显示了这两种方法:

@Autowired
private AsyncRabbitTemplate template;

...

public void doSomeWorkAndGetResultLater() {

    ...

    ListenableFuture<String> future = this.template.convertSendAndReceive("foo");

    // do some more work

    String reply = null;
    try {
        reply = future.get();
    }
    catch (ExecutionException e) {
        ...
    }

    ...

}

public void doSomeWorkAndGetResultAsync() {

    ...

    RabbitConverterFuture<String> future = this.template.convertSendAndReceive("foo");
    future.addCallback(new ListenableFutureCallback<String>() {

        @Override
        public void onSuccess(String result) {
            ...
        }

        @Override
        public void onFailure(Throwable ex) {
            ...
        }

    });

    ...

}

如果设置了mandatory并且消息无法传递,则 Future 抛出一个ExecutionException,原因为AmqpMessageReturnedException,该原因封装了返回的消息和有关返回的信息。

如果设置了enableConfirms,则 Future 有一个名为confirm的属性,它本身是一个ListenableFuture<Boolean>,带有true,表示成功发布。如果确认的 future 是false,则RabbitFuture还具有一个名为nackCause的属性,如果可用,则该属性包含失败的原因。

如果在回复之后收到了发布者确认,则该发布者确认将被丢弃,因为该回复意味着成功发布。

你可以将模板上的receiveTimeout属性设置为超时回复(默认设置为30000-30 秒)。如果发生超时,则使用AmqpReplyTimeoutException完成 future。

模板实现SmartLifecycle。在存在挂起的回复时停止模板,将导致挂起的Future实例被取消。

从版本 2.0 开始,异步模板现在支持直接回复 (opens new window),而不是配置的回复队列。要启用此功能,请使用以下构造函数之一:

public AsyncRabbitTemplate(ConnectionFactory connectionFactory, String exchange, String routingKey)

public AsyncRabbitTemplate(RabbitTemplate template)

参见RabbitMQ 直接回复,以使用直接回复与同步RabbitTemplate

版本 2.0 引入了这些方法的变体(convertSendAndReceiveAsType),这些方法接受一个额外的ParameterizedTypeReference参数来转换复杂的返回类型。你必须使用SmartMessageConverter配置底层RabbitTemplate。有关更多信息,请参见[从Message转换为RabbitTemplate]。

# Spring 带有 AMQP 的远程控制
该功能已被弃用,将在 3.0 中删除。
它已被处理异常取代了很长一段时间,returnExceptions被设置为 true,并在发送端配置了RemoteInvocationAwareMessageConverterAdapter
有关更多信息,请参见处理异常

Spring 框架具有一般的远程处理能力,允许使用各种传输的远程过程调用 (opens new window)。 Spring-AMQP 支持类似的机制,在客户端上是AmqpProxyFactoryBean,在服务器上是AmqpInvokerServiceExporter。这为 AMQP 提供了 RPC。在客户端,使用RabbitTemplate作为对earlier的描述。在服务器端,调用者(配置为MessageListener)接收消息,调用配置的服务,并使用入站消息的replyTo信息返回回复。

你可以将客户端工厂 Bean 注入任何 Bean(通过使用其serviceInterface)。然后,客户机可以调用代理上的方法,从而在 AMQP 上进行远程执行。

对于默认的MessageConverter实例,方法参数和返回的值必须是Serializable的实例。

在服务器端,AmqpInvokerServiceExporter同时具有AmqpTemplateMessageConverter属性。目前,没有使用模板的MessageConverter。如果需要提供自定义消息转换器,则应该通过设置messageConverter属性来提供它。在客户端,你可以向AmqpTemplate添加自定义消息转换器,该消息转换器通过使用其amqpTemplate属性提供给AmqpProxyFactoryBean

下面的清单显示了示例客户机和服务器配置:

<bean id="client"
    class="org.springframework.amqp.remoting.client.AmqpProxyFactoryBean">
    <property name="amqpTemplate" ref="template" />
    <property name="serviceInterface" value="foo.ServiceInterface" />
</bean>

<rabbit:connection-factory id="connectionFactory" />

<rabbit:template id="template" connection-factory="connectionFactory" reply-timeout="2000"
    routing-key="remoting.binding" exchange="remoting.exchange" />

<rabbit:admin connection-factory="connectionFactory" />

<rabbit:queue name="remoting.queue" />

<rabbit:direct-exchange name="remoting.exchange">
    <rabbit:bindings>
        <rabbit:binding queue="remoting.queue" key="remoting.binding" />
    </rabbit:bindings>
</rabbit:direct-exchange>
<bean id="listener"
    class="org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter">
    <property name="serviceInterface" value="foo.ServiceInterface" />
    <property name="service" ref="service" />
    <property name="amqpTemplate" ref="template" />
</bean>

<bean id="service" class="foo.ServiceImpl" />

<rabbit:connection-factory id="connectionFactory" />

<rabbit:template id="template" connection-factory="connectionFactory" />

<rabbit:queue name="remoting.queue" />

<rabbit:listener-container connection-factory="connectionFactory">
    <rabbit:listener ref="listener" queue-names="remoting.queue" />
</rabbit:listener-container>
AmqpInvokerServiceExporter只能处理格式正确的消息,例如从AmqpProxyFactoryBean发送的消息。
如果收到无法解释的消息,将发送序列化的RuntimeException作为回复。
如果消息没有replyToAddress属性,如果没有配置死信交换,则消息将被拒绝并永久丢失。
默认情况下,如果无法传递请求消息,则调用线程最终超时,并抛出一个RemoteProxyFailureException
默认情况下,超时为 5 秒。
你可以通过在RabbitTemplate上设置replyTimeout属性来修改持续时间,
从 1.5 版本开始,通过将mandatory属性设置为true并在连接工厂上启用返回(参见发布者确认并返回),调用线程抛出一个AmqpMessageReturnedException
查看回复超时以获取更多信息。

# 4.1.11.配置代理

AMQP 规范描述了如何使用该协议在代理上配置队列、交换和绑定。在org.springframework.amqp.core包中的AmqpAdmin接口中存在这些操作(可从 0.8 规范和更高版本移植)。该类的 RabbitMQ 实现RabbitAdmin位于org.springframework.amqp.rabbit.core包中。

AmqpAdmin接口基于使用 Spring AMQP 域抽象,如以下清单所示:

public interface AmqpAdmin {

    // Exchange Operations

    void declareExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();

    String declareQueue(Queue queue);

    void deleteQueue(String queueName);

    void deleteQueue(String queueName, boolean unused, boolean empty);

    void purgeQueue(String queueName, boolean noWait);

    // Binding Operations

    void declareBinding(Binding binding);

    void removeBinding(Binding binding);

    Properties getQueueProperties(String queueName);

}

另见作用域操作

getQueueProperties()方法返回一些关于队列的有限信息(消息计数和消费者计数)。返回的属性的键在RabbitTemplateQUEUE_NAMEQUEUE_MESSAGE_COUNTQUEUE_CONSUMER_COUNT)中作为常量可用。RabbitMQ REST APIQueueInfo对象中提供了更多的信息。

no-argdeclareQueue()方法在代理上定义了一个具有自动生成的名称的队列。这个自动生成队列的附加属性是exclusive=trueautoDelete=truedurable=false

declareQueue(Queue queue)方法接受一个Queue对象,并返回声明的队列的名称。如果所提供的nameQueue属性是空的String,则代理将使用生成的名称声明队列。该名称将返回给调用者。该名称也被添加到QueueactualName属性中。你只能通过直接调用RabbitAdmin以编程方式使用此功能。当在应用程序上下文中以声明方式定义队列时,管理员使用自动声明时,可以将 name 属性设置为""(空字符串)。然后,代理创建名称。从版本 2.1 开始,侦听器容器可以使用这种类型的队列。有关更多信息,请参见容器和以代理命名的队列

这与AnonymousQueue相反,该框架生成唯一的(UUID)名称,并将durable设置为falseexclusiveautoDelete设置为true。带有空(或缺少)<rabbit:queue/>属性的name总是创建AnonymousQueue

参见[AnonymousQueue](#anonymous-queue)以了解为什么AnonymousQueue比代理生成的队列名称更受欢迎,以及如何控制名称的格式。从版本 2.1 开始,默认情况下,匿名队列的声明参数Queue.X_QUEUE_LEADER_LOCATOR设置为client-local。这确保了队列是在应用程序连接的节点上声明的。声明式队列必须具有固定的名称,因为它们可能在上下文的其他地方被引用——例如在以下示例中显示的侦听器中:

<rabbit:listener-container>
    <rabbit:listener ref="listener" queue-names="#{someQueue.name}" />
</rabbit:listener-container>

交换、队列和绑定的自动声明

这个接口的 RabbitMQ 实现是RabbitAdmin,当使用 Spring XML 进行配置时,它类似于以下示例:

<rabbit:connection-factory id="connectionFactory"/>

<rabbit:admin id="amqpAdmin" connection-factory="connectionFactory"/>

CachingConnectionFactory缓存模式是CHANNEL(默认值)时,RabbitAdmin实现对在同一ApplicationContext中声明的队列、交换和绑定进行自动延迟声明。一旦向代理打开Connection,就会声明这些组件。有一些名称空间特性使其非常方便——例如,在 Stocks 示例应用程序中,我们有以下内容:

<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

<fanout-exchange name="broadcast.responses"
                 xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="tradeQueue"/>
    </bindings>
</fanout-exchange>

<topic-exchange name="app.stock.marketdata"
                xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
    </bindings>
</topic-exchange>

在前面的示例中,我们使用匿名队列(实际上,在内部,只使用由框架(而不是由代理)生成的名称的队列),并通过 ID 引用它们。我们还可以使用显式名称来声明队列,这些名称也可以作为上下文中其 Bean 定义的标识符。下面的示例使用显式名称配置队列:

<rabbit:queue name="stocks.trade.queue"/>
你可以同时提供idname属性。
这允许你引用队列(例如,
它还允许标准 Spring 特性(例如用于队列名称的属性占位符和 SPEL 表达式)。
当你使用名称作为 Bean 标识符时,这些特性是不可用的。

队列可以配置额外的参数——例如,x-message-ttl。当你使用名称空间支持时,它们以参数-名称/参数-值对的Map形式提供,这是通过使用<rabbit:queue-arguments>元素定义的。下面的示例展示了如何做到这一点:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>

默认情况下,参数被假定为字符串。对于其他类型的参数,你必须提供该类型。下面的示例展示了如何指定类型:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments value-type="java.lang.Long">
        <entry key="x-message-ttl" value="100"/>
    </rabbit:queue-arguments>
</rabbit:queue>

当提供混合类型的参数时,你必须为每个条目元素提供类型。下面的示例展示了如何做到这一点:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl">
            <value type="java.lang.Long">100</value>
        </entry>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>

在 Spring Framework3.2 及更高版本中,可以更简洁地声明这一点,如下所示:

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-message-ttl" value="100" value-type="java.lang.Long"/>
        <entry key="x-ha-policy" value="all"/>
    </rabbit:queue-arguments>
</rabbit:queue>

在使用 Java 配置时,Queue.X_QUEUE_LEADER_LOCATOR类上的setLeaderLocator()方法支持Queue参数作为第一类属性。从版本 2.1 开始,匿名队列的声明默认设置为client-local。这确保了队列是在应用程序连接到的节点上声明的。

RabbitMQ 代理不允许声明具有不匹配参数的队列。
例如,如果queue已经存在一个不带time to live参数的queue队列,并且尝试使用(例如)key="x-message-ttl" value="100"声明它,则抛出一个异常。

默认情况下,当发生异常时,RabbitAdmin立即停止处理所有声明。这可能会导致下游问题,例如由于未声明另一个队列(在错误队列之后定义),侦听器容器无法初始化。

可以通过在RabbitAdmin实例上将ignore-declaration-exceptions属性设置为true来修改此行为。此选项指示RabbitAdmin记录异常并继续声明其他元素。当使用 Java 配置RabbitAdmin时,此属性称为ignoreDeclarationExceptions。这是一个适用于所有元素的全局设置。队列、交换和绑定具有类似的属性,仅适用于这些元素。

在版本 1.6 之前,此属性仅在通道上发生IOException时才生效,例如当前属性与期望属性之间存在不匹配时。现在,此属性对任何异常都生效,包括TimeoutException和其他异常。

此外,任何声明异常都会导致DeclarationExceptionEvent的发布,这是一个ApplicationEvent,可以由上下文中的任何ApplicationListener使用。该事件包含对管理、正在声明的元素和Throwable的引用。

# headers exchange

从版本 1.3 开始,你可以将HeadersExchange配置为在多个头上匹配。你还可以指定是否必须匹配任何或所有标题。下面的示例展示了如何做到这一点:

<rabbit:headers-exchange name="headers-test">
    <rabbit:bindings>
        <rabbit:binding queue="bucket">
            <rabbit:binding-arguments>
                <entry key="foo" value="bar"/>
                <entry key="baz" value="qux"/>
                <entry key="x-match" value="all"/>
            </rabbit:binding-arguments>
        </rabbit:binding>
    </rabbit:bindings>
</rabbit:headers-exchange>

从版本 1.6 开始,你可以使用internal标志(默认为false)配置Exchanges,并且通过RabbitAdmin在代理上正确配置这样的Exchange(如果在应用程序上下文中存在一个)。如果用于交换的internal标志是true,则 RabbitMQ 不允许客户机使用该交换。这对于死信交换或交换到交换绑定非常有用,在这种情况下,你不希望发布者直接使用该交换。

要查看如何使用 Java 来配置 AMQP 基础架构,请查看股票示例应用程序,其中有@ConfigurationAbstractStockRabbitConfiguration,它依次具有RabbitClientConfigurationRabbitServerConfiguration子类。下面的清单显示了AbstractStockRabbitConfiguration的代码:

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}

在股票应用程序中,通过使用以下@Configuration类来配置服务器:

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

    @Bean
    public Queue stockRequestQueue() {
        return new Queue("app.stock.request");
    }
}

这是@Configuration类的整个继承链的结束。最终结果是在应用程序启动时向代理声明TopicExchangeQueue。在服务器配置中,没有将TopicExchange绑定到队列,这是在客户机应用程序中完成的。但是,股票请求队列会自动绑定到 AMQP 默认交换。此行为由规范定义。

客户机@Configuration类更有趣一些。其声明如下:

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;

    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}

客户机通过AmqpAdmin上的declareQueue()方法声明另一个队列。它使用一种路由模式将队列绑定到市场数据交换,该路由模式在属性文件中具体化。

# 用于队列和交换的 Builder API

版本 1.6 引入了一个方便的 Fluent API,用于在使用 Java 配置时配置QueueExchange对象。下面的示例展示了如何使用它:

@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}

@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}

参见[org.springframework.amqp.core.QueueBuilder](https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframework/amqp/core/queuebuilder.html)和[org.springframework.amqp.core.ExchangeBuilder(https://DOCS. Spring.io/ Spring-amqp/DOCS/latest-ga/api/org/springframf/amqp/core/exchangebuilder.html)以获取更多信息。

从版本 2.0 开始,ExchangeBuilder现在默认情况下创建持久交换,以与单个AbstractExchange类上的简单构造函数保持一致。要与构建器进行非持久交换,在调用.build()之前使用.durable(false)。不再提供不带参数的durable()方法。

2.2 版引入了 Fluent API,以添加“众所周知的”交换和队列参数。

@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}
# 声明交换、队列和绑定的集合

你可以在Declarable对象(QueueExchange,和Binding)的集合中包装Declarables对象。RabbitAdmin在应用程序上下文中检测此类 bean(以及离散Declarablebean),并在每次建立连接时(最初和连接失败后)在代理上声明所包含的对象。下面的示例展示了如何做到这一点:

@Configuration
public static class Config {

    @Bean
    public CachingConnectionFactory cf() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

    @Bean
    public Queue q1() {
        return new Queue("q1", false, false, true);
    }

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}
在 2.1 之前的版本中,你可以通过定义类型为Collection<Declarable>的 bean 来声明多个Declarable实例,
在某些情况下,这可能会导致不良的副作用,因为管理员必须迭代所有Collection<?>bean。,
现在禁用此功能,以支持Declarables,正如前面在这一节中所讨论的,
你可以通过将RabbitAdmin属性设置为declareCollections来恢复到以前的行为。

版本 2.2 将getDeclarablesByType方法添加到Declarables中;这可以作为一种方便,例如,在声明侦听器容器 Bean 时使用。

public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}
# 条件声明

默认情况下,所有队列、交换和绑定都由应用程序上下文中的所有RabbitAdmin实例声明(假设它们具有auto-startup="true")。

从版本 2.1.9 开始,RabbitAdmin有一个新的属性explicitDeclarationsOnly(默认情况下是false);当将其设置为true时,管理员将只声明显式配置为由该管理员声明的 bean。

从 1.2 版本开始,你可以有条件地声明这些元素。
当应用程序连接到多个代理并且需要指定应该用哪些代理声明特定元素时,这一点特别有用。

表示这些元素的类实现Declarable,它有两个方法:shouldDeclare()getDeclaringAdmins()RabbitAdmin使用这些方法来确定特定实例是否应该实际处理其Connection上的声明。

这些属性可以作为名称空间中的属性使用,如以下示例所示:

<rabbit:admin id="admin1" connection-factory="CF1" />

<rabbit:admin id="admin2" connection-factory="CF2" />

<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />

<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />

<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />

<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />

<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />

<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
    <rabbit:bindings>
        <rabbit:binding key="foo" queue="bar"/>
    </rabbit:bindings>
</rabbit:direct-exchange>
默认情况下,auto-declare属性是true,并且,如果declared-by没有提供(或者是空的),那么所有RabbitAdmin实例都会声明对象(只要管理员的auto-startup属性是true,默认的,并且管理员的explicit-declarations-only属性是假的)。

类似地,你可以使用基于 Java 的@Configuration来实现相同的效果。在下面的示例中,组件由admin1声明,但不是由admin2声明:

@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}

@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}

@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}

@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}

@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}
# 关于idname属性的注释

<rabbit:queue/><rabbit:exchange/>元素上的name属性反映了代理中实体的名称。对于队列,如果省略name,则创建一个匿名队列(参见[AnonymousQueue](#anonymous-queue))。

在 2.0 之前的版本中,name也被注册为 Bean 名称别名(类似于name上的<bean/>元素)。

这造成了两个问题:

  • 它阻止了使用相同名称的队列和交换的声明。

  • 如果别名包含 SPEL 表达式(#{…​}),则该别名将不会解析。

从版本 2.0 开始,如果你声明其中一个元素,同时带有id一个name属性,则该名称将不再声明为 Bean name 别名。如果你希望声明一个队列并使用相同的name进行交换,则必须提供一个id

如果元素仅具有name属性,则不会发生更改。 Bean 仍然可以被name引用——例如,在绑定声明中。但是,如果名称包含 SPEL,则仍然不能引用它——你必须提供id以供引用。

# AnonymousQueue

通常,当你需要一个唯一命名的、排他的、自动删除的队列时,我们建议你使用AnonymousQueue而不是代理定义的队列名称(使用""作为Queue名称,使代理生成队列名称)。

这是因为:

  1. 队列实际上是在建立到代理的连接时声明的。这是在豆子被创建并连接在一起之后很久的事情。使用队列的 bean 需要知道它的名称。实际上,在启动应用程序时,代理程序甚至可能不在运行。

  2. 如果由于某种原因丢失了与代理的连接,则管理员将重新声明同名的AnonymousQueue。如果我们使用代理声明的队列,队列名称将会更改。

你可以控制AnonymousQueue实例使用的队列名称的格式。

默认情况下,队列名称的前缀是spring.gen-,后面跟着UUID的 base64 表示形式——例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g

可以在构造函数参数中提供AnonymousQueue.NamingStrategy实现。下面的示例展示了如何做到这一点:

@Bean
public Queue anon1() {
    return new AnonymousQueue();
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}

@Bean
public Queue anon3() {
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}

第一个 Bean 生成一个以spring.gen-为前缀的队列名称,后面跟着UUID的 base64 表示——例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g。第二 Bean 生成以something-为前缀的队列名称,后跟UUID的 base64 表示。第三个 Bean 只使用 UUID(不使用 base64 转换)生成一个名称——例如,f20c818a-006b-4416-bf91-643590fedb0e

Base64 编码使用了 RFC4648 中的“URL 和文件名安全字母表”。删除尾随填充字符(=)。

你可以提供自己的命名策略,从而可以在队列名称中包括其他信息(例如应用程序名称或客户端主机)。

在使用 XML 配置时,可以指定命名策略。对于实现AnonymousQueue.NamingStrategy的 Bean 引用,在<rabbit:queue>元素上存在naming-strategy属性。以下示例展示了如何以各种方式指定命名策略:

<rabbit:queue id="uuidAnon" />

<rabbit:queue id="springAnon" naming-strategy="uuidNamer" />

<rabbit:queue id="customAnon" naming-strategy="customNamer" />

<bean id="uuidNamer" class="org.springframework.amqp.core.AnonymousQueue.UUIDNamingStrategy" />

<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
    <constructor-arg value="custom.gen-" />
</bean>

第一个示例创建了spring.gen-MRBv9sqISkuCiPfOYfpo4g之类的名称。第二个示例使用 UUID 的字符串表示形式创建名称。第三个示例创建了custom.gen-MRBv9sqISkuCiPfOYfpo4g之类的名称。

你还可以提供自己的命名策略 Bean。

从版本 2.1 开始,默认情况下,匿名队列的声明参数Queue.X_QUEUE_LEADER_LOCATOR设置为client-local。这确保了队列是在应用程序连接的节点上声明的。在构造实例之后,你可以通过调用queue.setLeaderLocator(null)来恢复到以前的行为。

# 恢复自动删除声明

通常,RabbitAdmin(s)只恢复在应用程序上下文中声明为 bean 的队列/交换/绑定;如果任何此类声明是自动删除的,则如果连接丢失,代理将删除它们。当重新建立连接时,管理员将重新声明这些实体。通常,通过调用admin.declareQueue(…​)admin.declareExchange(…​)admin.declareBinding(…​)创建的实体将不会被恢复。

从版本 2.4 开始,管理人员有一个新的属性redeclareManualDeclarations;如果为真,管理人员将恢复这些实体以及应用程序上下文中的 bean。

如果调用deleteQueue(…​)deleteExchange(…​)removeBinding(…​),则不会执行单个声明的恢复。删除队列和交换时,将从可恢复实体中删除相关的绑定。

最后,调用resetAllManualDeclarations()将阻止恢复任何先前声明的实体。

# 4.1.12.代理事件监听器

当启用事件交换插件 Name (opens new window)时,如果将类型BrokerEventListener的 Bean 添加到应用程序上下文中,则它将所选的代理事件发布为BrokerEvent实例,该实例可以通过正常的 Spring ApplicationListener@EventListener方法来使用。事件由代理发布到主题交换amq.rabbitmq.event,每个事件类型都有不同的路由密钥。侦听器使用事件键,用于将AnonymousQueue绑定到交换,以便侦听器仅接收选定的事件。由于这是一个主题交换,所以可以使用通配符(以及显式地请求特定事件),如下例所示:

@Bean
public BrokerEventListener eventListener() {
    return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#");
}

通过使用普通 Spring 技术,可以进一步缩小单个事件侦听器中接收到的事件的范围,如下例所示:

@EventListener(condition = "event.eventType == 'queue.created'")
public void listener(BrokerEvent event) {
    ...
}

# 4.1.13.延迟的消息交换

版本 1.6 引入了对延迟消息交换插件 Name (opens new window)的支持

该插件目前被标记为试验性的,但已经有一年多的时间可以使用了(在撰写本文时),
如果需要对该插件进行更改,我们计划尽快添加对此类更改的支持,由于这个原因,<gt r=“2337”/, Spring AMQP 中的这种支持也应该被认为是实验性的。
此功能已在 RabbitMQ3.6.0 和版本 0.0.1 的插件中进行了测试。

要使用RabbitAdmin来声明一个 Exchange 为延迟,可以将 Exchange Bean 上的delayed属性设置为trueRabbitAdmin使用交换类型(DirectFanout,以此类推)来设置x-delayed-type参数,并用类型x-delayed-message声明交换。

当使用 XML 配置 Exchange bean 时,delayed属性(默认:false)也可用。下面的示例展示了如何使用它:

<rabbit:topic-exchange name="topic" delayed="true" />

要发送延迟消息,可以通过MessageProperties设置x-delay头,如下例所示:

MessageProperties properties = new MessageProperties();
properties.setDelay(15000);
template.send(exchange, routingKey,
        MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());
rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() {

    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setDelay(15000);
        return message;
    }

});

要检查消息是否延迟,请在MessageProperties上使用getReceivedDelay()方法。它是一个单独的属性,以避免意外传播到由输入消息生成的输出消息。

# 4.1.14.RabbitMQ REST API

启用管理插件后,RabbitMQ 服务器公开一个 REST API 来监视和配置代理。aAPI 的 Java 绑定 (opens new window)现已提供。com.rabbitmq.http.client.Client是一个标准的、直接的 API,因此是阻塞的 API。它是基于Spring Web (opens new window)模块及其RestTemplate实现的。另一方面,com.rabbitmq.http.client.ReactorNettyClient是一个基于反应堆网状结构 (opens new window)项目的反应式、非阻塞实现。

跳依赖项(com.rabbitmq:http-client)现在也是optional

有关更多信息,请访问他们的 Javadoc。

# 4.1.15.异常处理

使用 RabbitMQ Java 客户机的许多操作都可以抛出检查过的异常。例如,在很多情况下IOException实例可能会被抛出。RabbitTemplateSimpleMessageListenerContainer和其他 Spring AMQP 组件捕获这些异常,并将它们转换为AmqpException层次结构中的一个异常。这些在’org.springframework.amqp’包中定义,而AmqpException是层次结构的基础。

当侦听器抛出异常时,它被包装在ListenerExecutionFailedException中。通常情况下,代理会拒绝并重新请求消息。将defaultRequeueRejected设置为false会导致消息被丢弃(或路由到死信交换)。如消息侦听器和异步情况中所讨论的,侦听器可以抛出一个AmqpRejectAndDontRequeueException(或ImmediateRequeueAmqpException)来有条件地控制此行为。

但是,有一类错误是侦听器无法控制行为的。当遇到无法转换的消息(例如,无效的content_encoding报头)时,在消息到达用户代码之前会抛出一些异常。将defaultRequeueRejected设置为true(默认)(或抛出ImmediateRequeueAmqpException),这样的消息将被一遍又一遍地重新传递。在版本 1.3.2 之前,用户需要编写一个自定义ErrorHandler,如异常处理中所讨论的,以避免这种情况。

从版本 1.3.2 开始,默认的ErrorHandler现在是一个ConditionalRejectingErrorHandler,它拒绝(并且不请求)带有不可恢复错误的失败消息。具体地说,它拒绝具有以下错误的失败消息:

  • o.s.amqp…​MessageConversionException:可以在使用MessageConverter转换传入消息有效负载时抛出。

  • o.s.messaging…​MessageConversionException:如果在映射到@RabbitListener方法时需要额外的转换,则可以由转换服务抛出。

  • o.s.messaging…​MethodArgumentNotValidException:如果在侦听器中使用验证(例如,@Valid)并且验证失败,则可以抛出。

  • o.s.messaging…​MethodArgumentTypeMismatchException:如果入站消息被转换为针对目标方法不正确的类型,则可以抛出该消息。例如,参数被声明为Message<Foo>,但是Message<Bar>被接收。

  • java.lang.NoSuchMethodException:在版本 1.6.3 中添加。

  • java.lang.ClassCastException:在版本 1.6.3 中添加。

你可以使用FatalExceptionStrategy配置此错误处理程序的实例,以便用户可以为条件消息拒绝提供自己的规则——例如,来自 Spring Retry(消息侦听器和异步情况)的BinaryExceptionClassifier的委托实现。此外,ListenerExecutionFailedException现在有一个failedMessage属性,你可以在决策中使用它。如果FatalExceptionStrategy.isFatal()方法返回true,则错误处理程序抛出一个AmqpRejectAndDontRequeueException。当异常被确定为致命异常时,默认FatalExceptionStrategy会记录一条警告消息。

自版本 1.6.3 以来,将用户异常添加到致命异常列表的一种方便的方法是子类ConditionalRejectingErrorHandler.DefaultExceptionStrategy并覆盖isUserCauseFatal(Throwable cause)方法,以返回true的致命异常。

处理 DLQ 消息的一种常见模式是在这些消息上设置time-to-live以及附加的 DLQ 配置,以便这些消息过期并路由回主队列进行重试。这种技术的问题在于,导致致命异常的消息会永远循环。从版本 2.1 开始,ConditionalRejectingErrorHandler检测消息上的x-death头,该头将导致抛出一个致命的异常。该消息已被记录并丢弃。通过将ConditionalRejectingErrorHandler上的discardFatalsWithXDeath属性设置为false,可以恢复到以前的行为。

从版本 2.1.9 开始,具有这些致命异常的消息将被拒绝,并且默认情况下不会重新请求,即使容器确认模式是手动的。
这些异常通常发生在调用侦听器之前,因此侦听器没有机会对消息进行 ACK 或 NACK,因此消息仍处于未 ACKED 状态。
以恢复到先前的行为,将ConditionalRejectingErrorHandler上的rejectManual属性设置为false

# 4.1.16.交易

Spring Rabbit Framework 具有对同步和异步用例中的自动事务管理的支持,其具有许多不同的语义,这些语义可以通过声明方式进行选择,这是 Spring 事务的现有用户所熟悉的。这使得许多即使不是最常见的消息传递模式也很容易实现。

有两种方法可以向框架发出所需的事务语义的信号。在RabbitTemplateSimpleMessageListenerContainer中,都有一个标志channelTransacted,如果true,它告诉框架使用事务通道,并以提交或回滚(取决于结果)结束所有操作(发送或接收),并发出回滚的异常信号。另一个信号是提供具有 Spring 的PlatformTransactionManager实现之一的外部事务作为正在进行的操作的上下文。如果在框架发送或接收消息时已经有一个事务在进行中,并且channelTransacted标志是true,则消息事务的提交或回滚将推迟到当前事务结束时进行。如果channelTransacted标志是false,则消息传递操作不会应用事务语义(它是自动 ACKED 的)。

channelTransacted标志是一个配置时间设置。在创建 AMQP 组件时(通常是在应用程序启动时),对它进行一次声明和处理。外部事务在原则上是更动态的,因为系统在运行时响应当前线程状态。然而,在实践中,当事务以声明方式分层到应用程序上时,它通常也是一个配置设置。

对于使用RabbitTemplate的同步用例,外部事务由调用方提供,可以是声明式的,也可以是命令式的(通常的 Spring 事务模型)。下面的示例展示了一种声明式方法(通常更受欢迎,因为它是非侵入性的),其中模板已配置为channelTransacted=true:

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}

在前面的示例中,在标记为@Transactional的方法中,作为消息体接收、转换和发送String有效负载。如果数据库处理出现异常而失败,则传入消息将返回给代理,而传出消息将不会发送。这适用于事务性方法链中带有RabbitTemplate的任何操作(例如,除非直接对Channel进行操作以尽早提交事务)。

对于带有SimpleMessageListenerContainer的异步用例,如果需要一个外部事务,则容器在设置侦听器时必须请求它。为了表示需要外部事务,用户在配置容器时向容器提供PlatformTransactionManager的实现。下面的示例展示了如何做到这一点:

@Configuration
public class ExampleExternalTransactionAmqpConfiguration {

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setTransactionManager(transactionManager());
        container.setChannelTransacted(true);
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }

}

在前面的示例中,事务管理器被添加为从另一个 Bean 定义注入的依赖项(未示出),并且channelTransacted标志也被设置为true。其结果是,如果侦听器发生异常而失败,事务将被回滚,消息也将返回给代理。值得注意的是,如果事务未能提交(例如,由于数据库约束错误或连接问题),AMQP 事务也将回滚,并将消息返回给代理。这有时被称为“尽最大努力 1 阶段提交”,是可靠消息传递的一个非常强大的模式。如果在前面的示例中将channelTransacted标志设置为false(默认值),则仍将为侦听器提供外部事务,但是所有消息传递操作都将被自动 ACK,因此其效果是即使在业务操作的回滚时也提交消息传递操作。

# 条件回滚

在 1.6.6 版本之前,在使用外部事务管理器(例如 JDBC)时,向容器的transactionAttribute添加回滚规则不会产生任何效果。异常总是回滚事务。

此外,当在容器的建议链中使用交易建议 (opens new window)时,条件回滚不是很有用,因为所有侦听器异常都包装在ListenerExecutionFailedException中。

第一个问题已经得到纠正,规则现在得到了适当的应用。而且,现在提供了ListenerFailedRuleBasedTransactionAttribute。它是RuleBasedTransactionAttribute的一个子类,唯一的区别是它知道ListenerExecutionFailedException,并将这种异常的原因用于规则。这个事务属性可以直接在容器中使用,也可以通过事务通知使用。

下面的示例使用了这个规则:

@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =
        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(
        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}
# 关于回滚接收消息的说明

AMQP 事务只适用于发送给代理的消息和 ACK。因此,当对 Spring 事务进行回滚并且已经接收到消息时, Spring AMQP 不仅必须回滚该事务,而且还必须手动拒绝该消息(有点像 nack,但这不是规范所称的那样)。对消息拒绝所采取的操作独立于事务,并且依赖于defaultRequeueRejected属性(默认值:true)。有关拒绝失败消息的更多信息,请参见消息侦听器和异步情况

有关 RabbitMQ 事务及其限制的更多信息,请参见RabbitMQ 代理语义 (opens new window)

在 RabbitMQ2.7.0 之前,这样的消息(以及在通道关闭或中止时未加控制的消息)会被发送到 Rabbit Broker 上的队列的后面。
自 2.7.0 以来,被拒绝的消息会被发送到队列的前面,其方式与 JMS 回滚消息类似。
以前,在本地事务回滚和提供TransactionManager时,对事务回滚的消息请求是不一致的。
在前一种情况下,应用正常的请求逻辑(AmqpRejectAndDontRequeueExceptiondefaultRequeueRejected=false)(参见消息侦听器和异步情况)。
与事务管理器,从版本 2.0 开始,该行为是一致的,并且在这两种情况下都应用了正常的请求逻辑。
要恢复到以前的行为,你可以将容器的alwaysRequeueWithTxManagerRollback属性设置为true
参见消息侦听器容器配置
# 使用RabbitTransactionManager

RabbitTransactionManager (opens new window)是在外部事务中执行 Rabbit 操作并与之同步的一种替代方法。此事务管理器是[PlatformTransactionManager](https://DOCS. Spring.io/ Spring/DOCS/current/javadoc-api/org/springframework/transactionmanager.html)接口的实现,应该与单个 RabbitConnectionFactory一起使用。

这种策略不能提供 XA 事务——例如,为了在消息传递和数据库访问之间共享事务。

需要应用程序代码来通过ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)检索事务性 Rabbit 资源,而不是随后创建通道的标准Connection.createChannel()调用。当使用 Spring AMQP 的RabbitTemplate (opens new window)时,它将自动检测线程绑定通道并自动参与其事务。

使用 Java 配置,你可以通过使用以下 Bean 设置一个新的 RabbitTransActionManager:

@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    return new RabbitTransactionManager(connectionFactory);
}

如果你更喜欢 XML 配置,那么可以在 XML 应用程序上下文文件中声明以下内容 Bean:

<bean id="rabbitTxManager"
      class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>
# 事务同步

将 RabbitMQ 事务与其他事务(例如 DBMS)同步提供了“Best Effort One Phase Commit”语义。在事务同步的完成后阶段,RabbitMQ 事务可能无法提交。这是由spring-tx基础架构作为错误记录的,但不会向调用代码抛出异常。从版本 2.3.10 开始,你可以在事务在处理该事务的同一线程上提交后调用ConnectionUtils.checkAfterCompletion()。如果没有发生异常,它将简单地返回;否则它将抛出一个AfterCompletionFailedException,该属性将具有表示完成的同步状态的属性。

通过调用ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true)来启用此功能;这是一个全局标志,适用于所有线程。

# 4.1.17.消息侦听器容器配置

对于配置与事务和服务质量相关的SimpleMessageListenerContainerDirectMessageListenerContainer有相当多的选项,其中一些选项相互交互。适用于 SMLC、DMLC 或StreamListenerContainer(见使用 RabbitMQ 流插件)的属性由相应列中的复选标记指示。有关帮助你决定哪个容器适合你的应用程序的信息,请参见选择容器

下表显示了使用名称空间配置<rabbit:listener-container/>时的容器属性名称及其等效属性名称(在括号中)。该元素上的type属性可以是simple(默认)或direct,以分别指定SMLCDMLC。名称空间不公开某些属性。这些由属性的N/A表示。

Property
(Attribute)
说明 SMLC DMLC StLC
在版本 1.6 之前,如果上下文中有一个以上的管理员,容器将随机选择一个。
如果没有管理员,它将在内部创建一个。
在这两种情况下,这都可能导致意外的结果。
从版本 1.6 开始,对于autoDeclare来说,上下文中必须正好有一个RabbitAdmin,或者必须使用rabbitAdmin属性在容器上配置对特定实例的引用。
(group) 这仅在使用名称空间时可用。
当指定时,类型Collection<MessageListenerContainer>的 Bean 被注册为该名称,并且每个
元素的
容器被添加到集合中。
例如,这允许,通过迭代集合来启动和停止容器组。
如果多个<listener-container/>元素具有相同的组值,集合形式中的容器
是如此指定的所有容器的集合。
tickmark tickmark
如果在初始启动期间代理不可用,则容器将启动,并在建立连接时检查条件。
检查是针对上下文中的所有队列进行的,而不仅仅是特定侦听器被配置使用的队列,
如果你希望将检查仅限于容器使用的那些队列,那么你应该为容器配置一个单独的RabbitAdmin,并使用rabbitAdmin属性提供对它的引用。
有关更多信息,请参见有条件声明
在 Bean 中为@RabbitListener启动容器时,禁用不匹配的队列参数检测这被标记为@Lazy
这是为了避免潜在的死锁,这可能会将此类容器的启动延迟长达 60 秒。
使用 lazy Listener bean 的应用程序应该在获得对 lazy 的引用之前检查队列参数 Bean。
在 Bean 中为@RabbitListener启动容器时,将禁用丢失的队列检测这被标记为@Lazy
这是为了避免潜在的死锁,这可能会将此类容器的启动延迟长达 60 秒。
使用 Lazy Listener Bean 的应用程序应该在获得对 Lazy 的引用之前检查队列 Bean。
在某些情况下,预取取值应该
较低,例如,对于较大的消息,尤其是在处理速度较慢的情况下(消息可能会将
累加到客户端进程中的大量内存中),如果需要严格的消息排序
(在这种情况下,预取值应该设置为 1),
还可以使用低容量消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以使消息在消费者之间的分布更加均匀。

# 4.1.18.监听器并发

# SimpleMessageListenerContainer

默认情况下,侦听器容器启动一个从队列接收消息的使用者。

在检查上一节中的表时,你可以看到许多控制并发性的属性和属性。最简单的是concurrentConsumers,它创建了并发处理消息的(固定的)消费者数量。

在 1.3.0 版本之前,这是唯一可用的设置,容器必须停止并重新启动才能更改设置。

自版本 1.3.0 以来,你现在可以动态调整concurrentConsumers属性。如果在容器运行时对其进行了更改,则会根据需要添加或删除消费者,以适应新的设置。

此外,还添加了一个名为maxConcurrentConsumers的新属性,并且容器根据工作负载动态地调整并发。这与四个附加属性一起工作:consecutiveActiveTriggerstartConsumerMinIntervalconsecutiveIdleTriggerstopConsumerMinInterval。在默认设置下,增加消费者的算法工作如下:

如果maxConcurrentConsumers尚未到达,并且一个现有的使用者连续十个周期处于活动状态,并且自上一个使用者启动以来至少已经过了 10 秒,则启动一个新的使用者。如果使用者在batchSize*receiveTimeout毫秒内至少接收到一条消息,则被认为是活动的。

在默认设置下,减少消费者的算法工作如下:

如果有超过concurrentConsumers的运行并且一个消费者检测到连续十个超时(空闲)并且最后一个消费者在至少 60 秒前被停止,则消费者被停止。超时取决于receiveTimeoutbatchSize属性。如果使用者在batchSize*receiveTimeout毫秒内没有收到消息,则被认为是空闲的。因此,使用默认的超时(一秒)和batchSize的四个超时,在 40 秒的空闲时间(四个超时对应一个空闲检测)后考虑停止消费者。

实际上,只有当整个容器闲置一段时间时,才可以停止使用消费者。
这是因为代理在所有活动消费者之间共享其工作。

无论配置队列的数量如何,每个使用者都使用一个通道。

从版本 2.0 开始,concurrentConsumersmaxConcurrentConsumers属性可以设置为concurrency属性——例如,2-4

# 使用DirectMessageListenerContainer

在这个容器中,并发是基于配置的队列和consumersPerQueue。每个队列的每个使用者都使用一个单独的通道,并且并发性由 Rabbit 客户端库控制。默认情况下,在编写时,它使用DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2线程池。

你可以配置taskExecutor以提供所需的最大并发性。

# 4.1.19.独家消费者

从版本 1.3 开始,你可以使用一个独占使用者来配置侦听器容器。这可以防止其他容器从队列中消费,直到当前使用者被取消为止。这样的容器的并发性必须是1

当使用独占消费者时,其他容器尝试根据recoveryInterval属性从队列中消费,如果尝试失败,则记录WARN消息。

# 4.1.20.监听器容器队列

版本 1.3 为处理侦听器容器中的多个队列引入了许多改进。

容器必须被配置为至少监听一个队列。以前也是这样,但现在可以在运行时添加和删除队列。当处理了任何预取的消息时,容器回收(取消和重新创建)消费者。对于addQueuesaddQueueNamesremoveQueuesremoveQueueNames方法,请参见Javadoc (opens new window)。在删除队列时,必须保留至少一个队列。

现在,如果消费者的队列中有任何一个可用,他就会启动。以前,如果有任何队列不可用,容器就会停止。现在,只有在没有队列可用的情况下才会出现这种情况。如果不是所有的队列都是可用的,那么容器将尝试每隔 60 秒被动地声明(并消耗)丢失的队列。

此外,如果使用者从代理接收到取消(例如,如果队列被删除),则使用者将尝试恢复,并且恢复的使用者将继续处理来自任何其他配置队列的消息。以前,一个队列上的取消会取消整个消费者,最终,由于缺少队列,容器将停止。

如果希望永久删除队列,则应在删除到队列之前或之后更新容器,以避免将来尝试使用它。

# 4.1.21.弹性:从错误和代理失败中恢复

Spring AMQP 提供的一些关键(也是最流行的)高级特性与在协议错误或代理失败的情况下的恢复和自动重新连接有关。我们已经在本指南中看到了所有相关的组件,但是在这里将它们集合在一起并单独列出特性和恢复场景应该会有所帮助。

主要的重新连接功能由CachingConnectionFactory本身启用。使用RabbitAdmin自动声明功能通常也是有益的。此外,如果你关心保证的交付,你可能还需要在RabbitTemplateSimpleMessageListenerContainer中使用channelTransacted标志,在AcknowledgeMode.AUTO中使用AcknowledgeMode.AUTO(如果你自己进行 ACK,则使用手动)标志。

# 交换、队列和绑定的自动声明

RabbitAdmin组件可以在启动时声明交换、队列和绑定。它通过ConnectionListener懒洋洋地做到了这一点。因此,如果代理在启动时不存在,这并不重要。第一次使用Connection(例如,通过发送消息)时,侦听器将触发并应用管理功能。在侦听器中执行自动声明的另一个好处是,如果由于任何原因(例如,代理死亡,网络故障和其他原因)而丢失连接,则在重新建立连接时再次应用这些声明。

以这种方式声明的队列必须具有固定的名称——要么是显式声明的,要么是由AnonymousQueue实例的框架生成的。
匿名队列是不可持久的、排他的和自动删除的。
只有当CachingConnectionFactory缓存模式为CHANNEL(默认)时,才会执行自动声明。
存在此限制,因为独占和自动删除队列绑定到连接。

从版本 2.2.2 开始,RabbitAdmin将检测类型为DeclarableCustomizer的 bean,并在实际处理声明之前应用该函数。这是有用的,例如,设置一个新的参数(属性)之前,它在框架内有第一类支持。

@Bean
public DeclarableCustomizer customizer() {
    return dec -> {
        if (dec instanceof Queue && ((Queue) dec).getName().equals("my.queue")) {
            dec.addArgument("some.new.queue.argument", true);
        }
        return dec;
    };
}

对于不提供直接访问Declarable Bean 定义的项目,它也很有用。

另见RabbitMQ 自动连接/拓扑恢复

# 同步操作失败和重试选项

如果在使用RabbitTemplate(例如)时,在同步序列中丢失了与代理的连接, Spring AMQP 将抛出一个AmqpException(通常但并非总是AmqpIOException)。我们不会试图掩盖存在问题的事实,因此你必须能够捕捉并响应异常。如果你怀疑连接丢失(而且这不是你的错误),最简单的方法是再次尝试该操作。你可以手动完成此操作,也可以使用 Spring Retry 来处理重试(强制地或声明地)。

Spring 重试提供了两个 AOP 拦截器和很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法和其他)。 Spring AMQP 还提供了一些方便的工厂 bean,用于为 AMQP 用例以方便的形式创建 Spring 重试拦截器,其具有可用于实现自定义恢复逻辑的强类型回调接口。有关更多详细信息,请参见StatefulRetryOperationsInterceptorStatelessRetryOperationsInterceptor的 Javadoc 和属性。如果没有事务或者在重试回调中启动了事务,则无状态重试是合适的。请注意,无状态重试比有状态重试更容易配置和分析,但是如果有一个正在进行的事务必须回滚或者肯定要回滚,那么它通常是不合适的。事务中间的断开连接应该具有与回滚相同的效果。因此,对于在堆栈更高的位置启动事务的重新连接,有状态重试通常是最佳选择。有状态重试需要一种机制来唯一地标识消息。最简单的方法是让发送方在MessageId消息属性中放置一个唯一的值。所提供的消息转换器提供了这样做的选项:你可以将createMessageIds设置为true。否则,可以将MessageKeyGenerator实现注入拦截器。密钥生成器必须为每条消息返回唯一的密钥。在版本 2.0 之前的版本中,提供了MissingMessageIdAdvice。它允许不带messageId属性的消息只重试一次(忽略重试设置)。不再提供此建议,因为与spring-retry版本 1.2 一起,其功能内置在拦截器和消息侦听器容器中。

对于向后兼容性,缺省情况下(在一次重试之后),带有空消息 ID 的消息被认为对使用者是致命的(使用者被停止),
以复制MissingMessageIdAdvice提供的功能,你可以在侦听器容器上将statefulRetryFatalWithNullMessageId属性设置为false
通过该设置,使用者将继续运行并拒绝消息(在一次重试之后)。
它将被丢弃或路由到死信队列(如果配置了死信队列)。

从版本 1.3 开始,提供了一个 Builder API,以通过使用 Java(在@Configuration类中)来帮助组装这些拦截器。下面的示例展示了如何做到这一点:

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}

只能以这种方式配置重试功能的一个子集。更高级的功能将需要将RetryTemplate配置为 Spring Bean。有关可用策略及其配置的完整信息,请参见Spring Retry Javadoc (opens new window)

# 使用批处理侦听器重试

不建议使用批处理侦听器配置重试,除非批处理是由生成器在单个记录中创建的。有关消费者和生产者创建的批的信息,请参见批处理消息。对于消费者创建的批处理,框架不知道批处理中的哪条消息导致了故障,因此不可能在重试结束后进行恢复。使用生产者创建的批处理,由于只有一条消息实际失败,因此可以恢复整个消息。应用程序可能想要通知自定义恢复程序在批处理中发生故障的位置,可能是通过设置抛出异常的索引属性。

批处理侦听器的重试恢复程序必须实现MessageBatchRecoverer

# 消息侦听器和异步情况

如果MessageListener由于业务异常而失败,则异常由消息侦听器容器处理,然后返回到侦听另一条消息。如果故障是由已删除的连接(而不是业务异常)引起的,则必须取消并重新启动为侦听器收集消息的使用者。SimpleMessageListenerContainer无缝地处理此问题,并留下一个日志来表示侦听器正在重新启动。事实上,它无休止地循环,试图重新启动消费者。只有当消费者表现得非常糟糕时,它才会放弃。一个副作用是,如果代理在容器启动时关闭,它会一直尝试,直到可以建立连接为止。

与协议错误和断开的连接相反,业务异常处理可能需要更多的考虑和一些自定义配置,尤其是在使用事务或容器 ACK 的情况下。在 2.8.x 之前,RabbitMQ 没有对死信行为的定义。因此,在默认情况下,由于业务异常而被拒绝或回滚的消息可以无休止地重新交付。要限制客户机的再交付次数,一个选择是侦听器的建议链中的StatefulRetryOperationsInterceptor。拦截器可以有一个实现自定义死信操作的恢复回调——任何适合你的特定环境的操作。

另一种选择是将容器的defaultRequeueRejected属性设置为false。这将导致丢弃所有失败的消息。当使用 RabbitMQ2.8.x 或更高版本时,这也有利于将消息传递给死信交换。

或者,你可以抛出AmqpRejectAndDontRequeueException。无论defaultRequeueRejected属性的设置如何,这样做都可以防止消息请求。

从版本 2.1 开始,引入了一个ImmediateRequeueAmqpException来执行完全相反的逻辑:无论defaultRequeueRejected属性的设置如何,消息都将被重新请求。

通常,这两种技术的组合被使用。你可以在建议链中使用StatefulRetryOperationsInterceptor,并使用MessageRecoverer抛出AmqpRejectAndDontRequeueException。当所有重试都已用尽时,将调用MessageRecoverRejectAndDontRequeueRecoverer就是这么做的。默认的MessageRecoverer消耗错误消息并发出WARN消息。

从版本 1.3 开始,提供了一个新的RepublishMessageRecoverer,允许在重试结束后发布失败的消息。

当回收者使用最后一个异常时,该消息将被 ACK’d,并且不会被发送到死信交换(如果有的话)。

RepublishMessageRecoverer在消费者侧使用时,接收到的消息在receivedDeliveryMode消息属性中具有deliveryMode
在这种情况下,deliveryModenull
这意味着在代理上具有NON_PERSISTENT交付模式,
从版本 2.0 开始,你可以将RepublishMessageRecovererdeliveryMode配置为设置到消息中以重新发布,如果它是null
默认情况下,它使用MessageProperties默认值-MessageDeliveryMode.PERSISTENT

下面的示例展示了如何将RepublishMessageRecoverer设置为回收器:

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

RepublishMessageRecoverer在消息头中发布带有附加信息的消息,例如异常消息、堆栈跟踪、原始交换和路由密钥。可以通过创建一个子类并覆盖additionalHeaders()来添加额外的标题。deliveryMode(或任何其他属性)也可以在additionalHeaders()中进行更改,如下例所示:

RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") {

    protected Map<? extends String, ? extends Object> additionalHeaders(Message message, Throwable cause) {
        message.getMessageProperties()
            .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
        return null;
    }

};

从版本 2.0.5 开始,如果堆栈跟踪太大,它可能会被截断;这是因为所有的标头都必须适合于单个框架。默认情况下,如果堆栈跟踪将导致其他头的可用字节数少于 20,000(“headroom”),那么它将被截断。如果你需要更多或更少的空间来放置其他头文件,可以通过设置 recoverer 的frameMaxHeadroom属性来对此进行调整。从版本 2.1.13、2.2.3 开始,异常消息将包含在此计算中,并且使用以下算法将堆栈跟踪的数量最大化:

  • 如果单独的堆栈跟踪将超过限制,则异常消息头将被截断为 97 字节加上…​,并且堆栈跟踪也将被截断。

  • 如果堆栈跟踪很小,消息将被截断(加上…​)以适应可用字节(但堆栈跟踪本身中的消息被截断为 97 字节加上…​)。

每当发生任何类型的截断时,将记录原始异常以保留完整的信息。

从版本 2.3.3 开始,提供了一个新的子类RepublishMessageRecovererWithConfirms;这支持两种类型的 Publisher 确认,并将在返回之前等待确认(或者如果未确认或消息返回,则抛出异常)。

如果确认类型是CORRELATED,则子类还将检测是否返回了消息并抛出AmqpMessageReturnedException;如果发布是否定的,则将抛出AmqpNackReceivedException

如果确认类型是SIMPLE,则子类将调用通道上的waitForConfirmsOrDie方法。

有关确认和返回的更多信息,请参见发布者确认并返回

从版本 2.1 开始,将添加ImmediateRequeueMessageRecoverer以抛出ImmediateRequeueAmqpException,该命令通知侦听器容器重新请求当前失败的消息。

# Spring 重试的异常分类

Spring 重试在确定哪些异常可以调用重试方面具有很大的灵活性。对于所有异常,默认配置都会重试。考虑到用户异常包装在ListenerExecutionFailedException中,我们需要确保分类检查异常原因。默认分类器只查看顶层异常。

由于 Spring 重试 1.0.3,BinaryExceptionClassifier具有一个名为traverseCauses的属性(默认:false)。当true时,它遍历异常原因,直到找到匹配的原因或没有原因为止。

要使用此分类器进行重试,你可以使用一个SimpleRetryPolicy,该构造函数创建了最大尝试次数,Exception实例的Map和布尔(traverseCauses),并将此策略注入RetryTemplate

# 4.1.22.多个代理(或集群)支持

在单个应用程序与多个代理或代理集群之间进行通信时,版本 2.3 增加了更多的便利。在消费者方面,主要的好处是基础设施可以自动将自动声明的队列与适当的代理关联起来。

用一个例子最好地说明了这一点:

@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    CachingConnectionFactory cf1() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    CachingConnectionFactory cf2() {
        return new CachingConnectionFactory("otherHost");
    }

    @Bean
    CachingConnectionFactory cf3() {
        return new CachingConnectionFactory("thirdHost");
    }

    @Bean
    SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1,
            CachingConnectionFactory cf2, CachingConnectionFactory cf3) {

        SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
        rcf.setDefaultTargetConnectionFactory(cf1);
        rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3));
        return rcf;
    }

    @Bean("factory1-admin")
    RabbitAdmin admin1(CachingConnectionFactory cf1) {
        return new RabbitAdmin(cf1);
    }

    @Bean("factory2-admin")
    RabbitAdmin admin2(CachingConnectionFactory cf2) {
        return new RabbitAdmin(cf2);
    }

    @Bean("factory3-admin")
    RabbitAdmin admin3(CachingConnectionFactory cf3) {
        return new RabbitAdmin(cf3);
    }

    @Bean
    public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
        return new RabbitListenerEndpointRegistry();
    }

    @Bean
    public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) {
        MultiRabbitListenerAnnotationBeanPostProcessor postProcessor
                = new MultiRabbitListenerAnnotationBeanPostProcessor();
        postProcessor.setEndpointRegistry(registry);
        postProcessor.setContainerFactoryBeanName("defaultContainerFactory");
        return postProcessor;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf1);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf2);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf3);
        return factory;
    }

    @Bean
    RabbitTemplate template(RoutingConnectionFactory rcf) {
        return new RabbitTemplate(rcf);
    }

    @Bean
    ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) {
        return new ConnectionFactoryContextWrapper(rcf);
    }

}

@Component
class Listeners {

    @RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1")
    public void listen1(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2")
    public void listen2(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3")
    public void listen3(String in) {

    }

}

正如你所看到的,我们已经声明了 3 组基础设施(连接工厂、管理员、容器工厂)。如前所述,@RabbitListener可以定义使用哪个容器工厂;在这种情况下,它们还使用queuesToDeclare,如果队列不存在,则该队列将在代理上声明。通过使用约定<container-factory-name>-admin命名RabbitAdminbean,基础结构能够确定哪个管理员应该声明队列。这也将与bindings = @QueueBinding(…​)一起工作,其中交换和绑定也将被声明。它将不能与queues一起工作,因为这期望队列已经存在。

在生产者方面,提供了一个方便的ConnectionFactoryContextWrapper类,以使使用RoutingConnectionFactory(参见路由连接工厂)变得更简单。

正如你在上面看到的,一个SimpleRoutingConnectionFactory Bean 已经添加了路由密钥onetwothree。还有一个RabbitTemplate使用了那个工厂。下面是一个使用该模板和包装器路由到代理集群之一的示例。

@Bean
public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) {
    return args -> {
        wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1"));
        wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2"));
        wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3"));
    };
}

# 4.1.23.调试

Spring AMQP 提供了广泛的日志记录,特别是在DEBUG级别。

如果希望监视应用程序和代理之间的 AMQP 协议,则可以使用 Wireshark 之类的工具,该工具具有一个插件来解码该协议。或者,RabbitMQ Java 客户机提供了一个非常有用的类Tracer。当作为main运行时,默认情况下,它会监听端口 5673 并连接到 LocalHost 上的端口 5672.你可以运行它并更改连接工厂配置以连接到 LocalHost 上的端口 5673.它在控制台上显示已解码的协议。有关更多信息,请参见TracerJavadoc。

# 4.2.使用 RabbitMQ 流插件

版本 2.4 为RabbitMQ 流插件 (opens new window)引入了对RabbitMQ 流插件 Java 客户端 (opens new window)的初始支持。

  • RabbitStreamTemplate

  • StreamListenerContainer

# 4.2.1.发送消息

RabbitStreamTemplate提供了RabbitTemplate功能的一个子集。

例 1.RabbitStreamOperations

public interface RabbitStreamOperations extends AutoCloseable {

	ListenableFuture<Boolean> send(Message message);

	ListenableFuture<Boolean> convertAndSend(Object message);

	ListenableFuture<Boolean> convertAndSend(Object message, @Nullable MessagePostProcessor mpp);

	ListenableFuture<Boolean> send(com.rabbitmq.stream.Message message);

	MessageBuilder messageBuilder();

	MessageConverter messageConverter();

	StreamMessageConverter streamMessageConverter();

	@Override
	void close() throws AmqpException;

}

RabbitStreamTemplate实现具有以下构造函数和属性:

例 2.RabbitStreamTemplate

public RabbitStreamTemplate(Environment environment, String streamName) {
}

public void setMessageConverter(MessageConverter messageConverter) {
}

public void setStreamConverter(StreamMessageConverter streamConverter) {
}

public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) {
}

MessageConverterconvertAndSend方法中用于将对象转换为 Spring amqpMessage

StreamMessageConverter用于将 Spring AMQPMessage转换为本机流Message

你还可以直接发送本机流Messages;使用messageBuilder()方法证明对Producer的消息生成器的访问。

ProducerCustomizer提供了一种机制,可以在生成生产者之前对其进行定制。

请参阅关于自定义Java 客户端文档 (opens new window)ProducerJava 客户端文档 (opens new window)

# 4.2.2.接收消息

异步消息接收由StreamListenerContainer提供(当使用@RabbitListener时,StreamRabbitListenerContainerFactory)。

侦听器容器需要Environment以及一个流名。

你可以使用经典的MessageListener接收 Spring AMQPMessages,也可以使用新的接口接收本机流Messages:

public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}

有关受支持的属性的信息,请参见消息侦听器容器配置

与模板类似,容器具有ConsumerCustomizer属性。

请参阅关于自定义Java 客户端文档 (opens new window)ConsumerJava 客户端文档 (opens new window)

当使用@RabbitListener时,配置一个StreamRabbitListenerContainerFactory;此时,大多数@RabbitListener属性(concurrency等)被忽略。只支持idqueuesautoStartupcontainerFactory。此外,queues只能包含一个流名。

# 4.2.3.例子

@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
    RabbitStreamTemplate template = new RabbitStreamTemplate(env, "test.stream.queue1");
    template.setProducerCustomizer((name, builder) -> builder.name("test"));
    return template;
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> rabbitListenerContainerFactory(Environment env) {
    return new StreamRabbitListenerContainerFactory(env);
}

@RabbitListener(queues = "test.stream.queue1")
void listen(String in) {
    ...
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> nativeFactory(Environment env) {
    StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(env);
    factory.setNativeListener(true);
    factory.setConsumerCustomizer((id, builder) -> {
        builder.name("myConsumer")
                .offset(OffsetSpecification.first())
                .manualTrackingStrategy();
    });
    return factory;
}

@RabbitListener(id = "test", queues = "test.stream.queue2", containerFactory = "nativeFactory")
void nativeMsg(Message in, Context context) {
    ...
    context.storeOffset();
}

# 4.3.日志记录子系统 AMQP 附录

该框架为一些流行的日志记录子系统提供了日志附录:

  • 注销(自 Spring AMQP 版本 1.4 起)

  • log4j2(自 Spring AMQP 版本 1.6 起)

通过使用日志记录子系统的常规机制来配置附录,可用的属性在下面的部分中指定。

# 4.3.1.共同属性

以下属性可与所有附录一起使用:

Property Default 说明
<br/> exchangeName<br/> <br/> logs<br/> 要向其发布日志事件的交易所的名称。
<br/> exchangeType<br/> <br/> topic<br/> 将日志事件发布到其中的交换类型—仅当 Appender 声明交换时才需要。
请参见declareExchange
<br/> routingKeyPattern<br/> <br/> %c.%p<br/> 用于生成路由密钥的记录子系统模式格式.
<br/> applicationId<br/> <br/><br/> 应用程序 ID——如果模式包含%X{applicationId},则将其添加到路由键中。
<br/> senderPoolSize<br/> <br/> 2<br/> 用于发布日志事件的线程数。
<br/> maxSenderRetries<br/> <br/> 30<br/> 如果代理不可用或存在其他错误,则重试发送消息的次数。
重试延迟如下:N ^ log(N),其中N是重试编号。
<br/> addresses<br/> <br/><br/> 以下列形式的以逗号分隔的代理地址列表:host:port[,host:port]*-覆盖hostport
<br/> host<br/> <br/> localhost<br/> 连接到哪个主机的 RabbitMQ。
<br/> port<br/> <br/> 5672<br/> 连接到的 RabbitMQ 端口。
<br/> virtualHost<br/> <br/> /<br/> 连接到的 RabbitMQ 虚拟主机。
<br/> username<br/> <br/> guest<br/> RabbitMQ 用户连接时使用.
<br/> password<br/> <br/> guest<br/> 此用户的 RabbitMQ 密码。
<br/> useSsl<br/> <br/> false<br/> 是否将 SSL 用于 RabbitMQ 连接。
参见[RabbitConnectionFactoryBean并配置 SSL]
<br/> verifyHostname<br/> <br/> true<br/> 启用 TLS 连接的服务器主机名验证。
参见[RabbitConnectionFactoryBean并配置 SSL]
<br/> sslAlgorithm<br/> <br/> null<br/> 使用的 SSL 算法。
<br/> sslPropertiesLocation<br/> <br/> null<br/> SSL 属性文件的位置。
<br/> keyStore<br/> <br/> null<br/> 密钥存储库的位置。
<br/> keyStorePassphrase<br/> <br/> null<br/> 密钥库的密码。
<br/> keyStoreType<br/> <br/> JKS<br/> keystore 类型。
<br/> trustStore<br/> <br/> null<br/> 信任库的位置。
<br/> trustStorePassphrase<br/> <br/> null<br/> 信任存储库的密码。
<br/> trustStoreType<br/> <br/> JKS<br/> 信任库类型。
<br/> saslConfig<br/> <br/> null (RabbitMQ client default applies)<br/> saslConfig-关于有效值,请参见RabbitUtils.stringToSaslConfig的 Javadoc。
<br/> ContentType<br/> <br/> text/plain<br/> 日志消息的content-type属性。
<br/> contentEncoding<br/> <br/><br/> content-encoding日志消息的属性。
<br/> declareExchange<br/> <br/> false<br/> 是否在此附录启动时声明已配置的交换。
另请参见durableautoDelete
<br/> durable<br/> <br/> true<br/> declareExchangetrue时,持久标志被设置为该值。
<br/> autoDelete<br/> <br/> false<br/> declareExchangetrue时,自动删除标志被设置为该值。
<br/> charset<br/> <br/> null<br/> String转换为byte[]时使用的字符集。
默认:null(使用的是系统默认字符集)。
如果当前平台不支持字符集,我们将退回使用系统字符集。
<br/> deliveryMode<br/> <br/> PERSISTENT<br/> PERSISTENTNON_PERSISTENT,以确定 RabbitMQ 是否应该持久化消息。
<br/> generateId<br/> <br/> false<br/> 用于确定messageId属性是否设置为唯一值。
<br/> clientConnectionProperties<br/> <br/> null<br/> 用于 RabbitMQ 连接的自定义客户端属性的key:value对的逗号分隔列表。
<br/> addMdcAsHeaders<br/> <br/> true<br/> 在引入此属性之前,MDC 属性总是被添加到 RabbitMQ 消息头中。
它可能会导致大 MDC 的问题,因为 RabbitMQ 对所有头都有有限的缓冲区大小而且这个缓冲区很小。
引入这个属性是为了避免在大 MDC 的情况下出现问题。
默认情况下,这个值设置为true,用于向后兼容。
false将序列化 MDC 关闭到头中,
请注意,默认情况下,JsonLayout将 MDC 添加到消息中。

# 4.3.2.log4j2 附录

下面的示例展示了如何配置 log4j2Appender:

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        addresses="foo:5672,bar:5672" user="guest" password="guest" virtualHost="/"
        exchange="log4j2" exchangeType="topic" declareExchange="true" durable="true" autoDelete="false"
        applicationId="myAppId" routingKeyPattern="%X{applicationId}.%c.%p"
        contentType="text/plain" contentEncoding="UTF-8" generateId="true" deliveryMode="NON_PERSISTENT"
        charset="UTF-8"
        senderPoolSize="3" maxSenderRetries="5"
        addMdcAsHeaders="false">
    </RabbitMQ>
</Appenders>
从 1.6.10 和 1.7.3 版本开始,默认情况下,log4j2Appender 将消息发布到调用线程上的 RabbitMQ。
这是因为 log4j2 默认情况下不会创建线程安全事件。
如果代理关闭,则使用maxSenderRetries进行重试,重试之间没有延迟。
如果你希望恢复以前在单独的线程上发布消息的行为(senderPoolSize),则可以将async属性设置为true,但是,
,你还需要配置 log4j2 来使用DefaultLogEventFactory而不是ReusableLogEventFactory
这样做的一种方法是设置系统属性-Dlog4j2.enable.threadlocals=false
如果你使用异步发布与ReusableLogEventFactory,由于相声,事件很有可能被破坏。

# 4.3.3.回录附录

下面的示例展示了如何配置一个 Logback Appender:

<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
    <layout>
        <pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
    </layout>
    <addresses>foo:5672,bar:5672</addresses>
    <abbreviation>36</abbreviation>
    <includeCallerData>false</includeCallerData>
    <applicationId>myApplication</applicationId>
    <routingKeyPattern>%property{applicationId}.%c.%p</routingKeyPattern>
    <generateId>true</generateId>
    <charset>UTF-8</charset>
    <durable>false</durable>
    <deliveryMode>NON_PERSISTENT</deliveryMode>
    <declareExchange>true</declareExchange>
    <addMdcAsHeaders>false</addMdcAsHeaders>
</appender>

从版本 1.7.1 开始,logbackAmqpAppender提供了一个includeCallerData选项,默认情况下是false。提取调用者数据可能会非常昂贵,因为日志事件必须创建一个可丢弃的数据,并对其进行检查以确定调用位置。因此,默认情况下,当事件被添加到事件队列时,不会提取与事件相关的调用方数据。通过将includeCallerData属性设置为true,可以将 Appender 配置为包括调用方数据。

从版本 2.0.0 开始,LogbackAmqpAppender使用encoder选项支持翻录编码器 (opens new window)encoderlayout选项是互斥的。

# 4.3.4.自定义消息

默认情况下,AMQP Appenders 填充以下消息属性:

  • deliveryMode

  • contentType

  • contentEncoding,如果已配置

  • messageId,如果generateId已配置

  • 日志事件的timestamp

  • appId,如果配置了 ApplicationID

此外,它们还用以下值填充标题:

  • 日志事件的categoryName

  • 日志事件的级别

  • thread:发生日志事件的线程的名称

  • 日志事件调用的堆栈跟踪的位置

  • 所有 MDC 属性的副本(除非addMdcAsHeaders被设置为false

每个附录都可以进行子类,这样你就可以在发布之前修改消息了。下面的示例展示了如何自定义日志消息:

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    public Message postProcessMessageBeforeSend(Message message, Event event) {
        message.getMessageProperties().setHeader("foo", "bar");
        return message;
    }

}

从 2.2.4 开始,log4j2AmqpAppender可以使用@PluginBuilderFactory进行扩展,也可以使用AmqpAppender.Builder进行扩展。

@Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true)
public class MyEnhancedAppender extends AmqpAppender {

	public MyEnhancedAppender(String name, Filter filter, Layout<? extends Serializable> layout,
			boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue, String foo, String bar) {
		super(name, filter, layout, ignoreExceptions, manager, eventQueue);

	@Override
	public Message postProcessMessageBeforeSend(Message message, Event event) {
			message.getMessageProperties().setHeader("foo", "bar");
		return message;
	}

	@PluginBuilderFactory
	public static Builder newBuilder() {
		return new Builder();
	}

	protected static class Builder extends AmqpAppender.Builder {

		@Override
		protected AmqpAppender buildInstance(String name, Filter filter, Layout<? extends Serializable> layout,
				boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue) {
			return new MyEnhancedAppender(name, filter, layout, ignoreExceptions, manager, eventQueue);
		}
	}

}

# 4.3.5.自定义客户端属性

你可以通过添加字符串属性或更复杂的属性来添加自定义客户机属性。

# 简单字符串属性

每个 Appender 都支持向 RabbitMQ 连接添加客户端属性。

下面的示例展示了如何为回登添加自定义客户机属性:

<appender name="AMQP" ...>
    ...
    <clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
    ...
</appender>

示例 3.log4j2

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        ...
        clientConnectionProperties="thing1:thing2,cat:hat"
        ...
    </RabbitMQ>
</Appenders>

这些属性是用逗号分隔的key:value对列表。键和值不能包含逗号或冒号。

当查看连接时,这些属性会出现在 RabbitMQ 管理 UI 上。

# 回传的高级技术

你可以对 Logback Appender 进行子类。这样做可以让你在建立连接之前修改客户机连接属性。下面的示例展示了如何做到这一点:

public class MyEnhancedAppender extends AmqpAppender {

    private String thing1;

    @Override
    protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
        clientProperties.put("thing1", this.thing1);
    }

    public void setThing1(String thing1) {
        this.thing1 = thing1;
    }

}

然后可以将<thing1>thing2</thing1>添加到 logback.xml。

对于字符串属性(如前面示例中所示的那些),可以使用前面的技术。子类允许添加更丰富的属性(例如添加Map或数字属性)。

# 4.3.6.提供自定义队列实现

AmqpAppenders使用BlockingQueue将日志事件异步发布到 RabbitMQ。默认情况下,使用LinkedBlockingQueue。但是,你可以提供任何类型的自定义BlockingQueue实现。

下面的示例展示了如何对注销执行此操作:

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}

log4j2Appender 支持使用[BlockingQueueFactory](https://logging. Apache.org/log4j/2.x/manual/appenders.html#BlockingQueueFactory),如下例所示:

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>

# 4.4.示例应用程序

项目包括两个示例应用程序。第一个是一个简单的“Hello World”示例,演示了同步和异步消息接收。它为获得对基本组件的理解提供了一个很好的起点。第二个示例基于股票交易用例,以演示在现实世界的应用程序中常见的交互类型。在这一章中,我们提供了每个示例的快速演练,以便你能够关注最重要的组件。这些示例都是基于 Maven 的,因此你应该能够将它们直接导入到任何 Maven 可感知的 IDE 中(例如SpringSource 工具套件 (opens new window))。

# 4.4.1.《Hello World》样本

“Hello World”示例演示了同步和异步消息接收。你可以将spring-rabbit-helloworld示例导入到 IDE 中,然后按照下面的讨论进行操作。

# 同步示例

src/main/java目录中,导航到org.springframework.amqp.helloworld包。打开HelloWorldConfiguration类,注意它包含类级的@Configuration注释,并注意方法级的一些@Bean注释。这是 Spring 基于 Java 的配置的一个示例。你可以阅读有关here (opens new window)的更多信息。

下面的清单显示了如何创建连接工厂:

@Bean
public CachingConnectionFactory connectionFactory() {
    CachingConnectionFactory connectionFactory =
        new CachingConnectionFactory("localhost");
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    return connectionFactory;
}

配置还包含RabbitAdmin的实例,默认情况下,该实例查找类型为 exchange、queue 或 binding 的任何 bean,然后在代理上声明它们。实际上,在HelloWorldConfiguration中生成的helloWorldQueue Bean 是一个示例,因为它是Queue的一个实例。

下面的清单显示了helloWorldQueue Bean 的定义:

@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}

回顾rabbitTemplate Bean 配置,可以看到它的名称helloWorldQueue设置为其queue属性(用于接收消息)和其routingKey属性(用于发送消息)。

既然我们已经研究了配置,我们就可以查看实际使用这些组件的代码了。首先,从同一个包中打开Producer类。它包含一个main()方法,其中创建了 Spring ApplicationContext

下面的清单显示了main方法:

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    amqpTemplate.convertAndSend("Hello World");
    System.out.println("Sent: Hello World");
}

在前面的示例中,检索AmqpTemplate Bean 并用于发送Message。由于客户端代码应该尽可能依赖于接口,所以类型是AmqpTemplate而不是RabbitTemplate。尽管在HelloWorldConfiguration中创建的 Bean 是RabbitTemplate的一个实例,但依赖于接口意味着此代码更可移植(你可以独立于代码来更改配置)。由于调用了convertAndSend()方法,模板将委托给它的MessageConverter实例。在这种情况下,它使用默认的SimpleMessageConverter,但是可以向rabbitTemplate Bean 提供不同的实现,如HelloWorldConfiguration中所定义的。

现在打开Consumer类。它实际上共享相同的配置基类,这意味着它共享rabbitTemplate Bean。这就是为什么我们将模板配置为routingKey(用于发送)和queue(用于接收)。正如我们在[AmqpTemplate]中描述的那样,你可以将’RoutingKey’参数传递给 send 方法,将’Queue’参数传递给 receive 方法。Consumer代码基本上是生产者的镜像,调用receiveAndConvert()而不是convertAndSend()

下面的清单显示了Consumer的主要方法:

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}

如果运行Producer,然后运行Consumer,则应该在控制台输出中看到Received: Hello World

# 异步示例

同步示例浏览了同步 Hello World 示例。这一部分描述了一个稍微更高级但功能更强大的选项。通过一些修改,Hello World 示例可以提供异步接收的示例,也称为消息驱动 POJO。实际上,有一个子包提供了以下内容:org.springframework.amqp.samples.helloworld.async

同样,我们从发送方开始。打开ProducerConfiguration类,注意它创建了一个connectionFactory和一个rabbitTemplate Bean。这一次,由于配置是专门用于消息发送端的,因此我们甚至不需要任何队列定义,并且RabbitTemplate仅具有’RoutingKey’属性集。记住,消息是发送到交易所的,而不是直接发送到队列的。AMQP 默认交换是一种没有名称的直接交换。所有队列都绑定到该默认交换,并以它们的名称作为路由密钥。这就是为什么我们只需要在这里提供路由密钥。

下面的清单显示了rabbitTemplate的定义:

public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}

由于此示例演示了异步消息接收,所以生产端被设计为连续发送消息(如果它是一个类似于同步版本的每次执行消息的模型,那么它实际上就不是一个消息驱动的消费者了)。负责持续发送消息的组件被定义为ProducerConfiguration中的内部类。它被配置为每三秒运行一次。

下面的清单显示了该组件:

static class ScheduledProducer {

    @Autowired
    private volatile RabbitTemplate rabbitTemplate;

    private final AtomicInteger counter = new AtomicInteger();

    @Scheduled(fixedRate = 3000)
    public void sendMessage() {
        rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
    }
}

你不需要了解所有的细节,因为真正的重点应该是接收端(我们接下来将讨论这一点)。但是,如果你还不熟悉 Spring 任务调度支持,则可以了解更多here (opens new window)。简而言之,postProcessor Bean 中的ProducerConfiguration使用调度程序注册任务。

现在我们可以转向接受方了。为了强调消息驱动的 POJO 行为,我们从对消息做出反应的组件开始。这个类被称为HelloWorldHandler,如以下清单所示:

public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}

那门课很难上。它不扩展任何基类,不实现任何接口,甚至不包含任何导入。它正被 Spring AMQPMessageListenerAdapter接口“适配”到MessageListener接口。然后可以在SimpleMessageListenerContainer上配置该适配器。对于这个示例,容器是在ConsumerConfiguration类中创建的。你可以看到 POJO 包装在适配器那里。

下面的清单显示了listenerContainer是如何定义的:

@Bean
public SimpleMessageListenerContainer listenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueName(this.helloWorldQueueName);
    container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
    return container;
}

SimpleMessageListenerContainer是一个 Spring 生命周期组件,默认情况下,它会自动启动。如果你在Consumer类中查找,你可以看到它的main()方法仅由一行引导程序组成,用于创建ApplicationContext。生产者的main()方法也是一行引导程序,因为其方法被注释为@Scheduled的组件也会自动启动。你可以以任何顺序启动ProducerConsumer,并且你应该每三秒钟就会看到正在发送和接收的消息。

# 4.4.2.股票交易

股票交易示例演示了比Hello World 样本更高级的消息传递场景。然而,配置是非常相似的,如果有一点更多的参与。由于我们详细介绍了 Hello World 配置,在此,我们将重点讨论使这个示例有所不同的原因。有一个服务器将市场数据(股票行情)推送到主题交换。然后,客户端可以通过绑定具有路由模式的队列来订阅市场数据提要(例如,app.stock.quotes.nasdaq.*)。这个演示的另一个主要功能是由客户端发起并由服务器处理的请求-回复“股票交易”交互。这涉及一个私有replyTo队列,该队列由客户端在订单请求消息本身内发送。

服务器的核心配置在RabbitServerConfiguration包中的org.springframework.amqp.rabbit.stocks.config.server类中。它扩展了AbstractStockAppRabbitConfiguration。这里定义了服务器和客户机的公共资源,包括市场数据主题交换(其名称为“app.stock.marketdata”)和服务器为股票交易公开的队列(其名称为“app.stock.request”)。在该公共配置文件中,你还可以看到在Jackson2JsonMessageConverter上配置了RabbitTemplate

特定于服务器的配置由两个部分组成。首先,它在RabbitTemplate上配置市场数据交换,这样它就不需要在每次调用发送Message时都提供该交换名称。它在基本配置类中定义的抽象回调方法中执行此操作。下面的列表显示了该方法:

public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}

其次,声明股票请求队列。在这种情况下,它不需要任何显式绑定,因为它绑定到默认的无名称交换,并以自己的名称作为路由密钥。如前所述,AMQP 规范定义了这种行为。下面的清单显示了stockRequestQueue Bean 的定义:

@Bean
public Queue stockRequestQueue() {
    return new Queue(STOCK_REQUEST_QUEUE_NAME);
}

现在你已经看到了服务器的 AMQP 资源的配置,请导航到org.springframework.amqp.rabbit.stocks目录下的src/test/java包。在这里,你可以看到实际的Server类,它提供了main()方法。它基于server-bootstrap.xml配置文件创建ApplicationContext。在这里,你可以看到发布虚拟市场数据的计划任务。这种配置依赖于 Spring 的task名称空间支持。引导程序配置文件还导入了其他一些文件。最有趣的是server-messaging.xml,它直接位于src/main/resources之下。在这里,你可以看到负责处理股票交易请求的messageListenerContainer Bean。最后,看看serverHandler Bean 中定义的server-handlers.xml(也在“SRC/main/resources”中)。 Bean 是ServerHandler类的一个实例,并且是消息驱动 POJO 的一个很好的示例,该 POJO 也可以发送回复消息。请注意,它本身并不耦合到框架或任何 AMQP 概念。它接受TradeRequest并返回TradeResponse。下面的清单显示了handleMessage方法的定义:

public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}

现在,我们已经了解了服务器最重要的配置和代码,我们可以转到客户机。最好的起点可能是RabbitClientConfiguration包中的org.springframework.amqp.rabbit.stocks.config.client。注意,它声明了两个队列,但没有提供显式的名称。下面的清单显示了这两个队列的 Bean 定义:

@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}

这些都是私有队列,并且会自动生成唯一的名称。客户端使用第一个生成的队列绑定到服务器公开的市场数据交换。回想一下,在 AMQP 中,消费者与队列交互,而生产者与交换器交互。队列与交易所的“绑定”是告诉代理将消息从给定的交易所传递(或路由)到队列的方法。由于市场数据交换是一个主题交换,因此绑定可以用路由模式表示。RabbitClientConfiguration使用Binding对象执行此操作,并且该对象是使用BindingBuilderFluent API 生成的。下面的清单显示了Binding:

@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;

@Bean
public Binding marketDataBinding() {
    return BindingBuilder.bind(
        marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}

请注意,实际值已在一个属性文件中外部化(client.properties``src/main/resources),并且我们使用 Spring 的@Value注释来注入该值。这通常是个好主意。否则,该值将在类中进行硬编码,并且在不重新编译的情况下不可修改。在这种情况下,在更改用于绑定的路由模式时,运行客户机的多个版本要容易得多。我们现在可以试试。

首先运行org.springframework.amqp.rabbit.stocks.Server,然后运行org.springframework.amqp.rabbit.stocks.Client。你应该会看到NASDAQ股票的虚拟报价,因为在 client.properties 中与“stocks.quote.pattern”键关联的当前值是“app.stock.quotes.nasdaq.”。现在,在保持现有ServerClient运行的同时,将该属性值更改为’app.stock.quotes.nyse.’,并启动第二个Client实例。你应该看到,第一个客户端仍然接收纳斯达克报价,而第二个客户端接收纽交所报价。相反,你可以改变模式,以获得所有的股票,甚至一个单独的股票代码。

我们探索的最后一个功能是从客户的角度进行请求-回复交互。回想一下,我们已经看到了接受ServerHandler对象并返回TradeResponse对象的ServerHandler对象。在Client包中,RabbitStockServiceGateway侧的对应代码是RabbitStockServiceGateway。它委托给RabbitTemplate以发送消息。下面的清单显示了send方法:

public void send(TradeRequest tradeRequest) {
    getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setReplyTo(new Address(defaultReplyToQueue));
            try {
                message.getMessageProperties().setCorrelationId(
                    UUID.randomUUID().toString().getBytes("UTF-8"));
            }
            catch (UnsupportedEncodingException e) {
                throw new AmqpException(e);
            }
            return message;
        }
    });
}

请注意,在发送消息之前,它设置了replyTo地址。它提供了由traderJoeQueue Bean 定义生成的队列(如前面所示)。下面的清单显示了@Bean类本身的StockServiceGateway定义:

@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}

如果你不再运行服务器和客户机,请立即启动它们。尝试发送格式为“100tckr”的请求。在模拟“处理”请求的短暂人为延迟之后,你应该会看到一个确认消息出现在客户机上。

# 4.4.3.从非 Spring 份申请中接收 JSON

Spring 应用程序,在发送 JSON 时,将*TypeId*头设置为完全限定的类名,以协助接收应用程序将 JSON 转换回 Java 对象。

spring-rabbit-json示例探索了几种从非 Spring 应用程序转换 JSON 的技术。

另见Jackson2JSONMessageConverter以及DefaultClassMapper的[javadoc](https://DOCS. Spring.io/ Spring-amqp/DOCS/current/api/index.html?org/springframework/amqp/support/converter/defaultclassmapper.html)。

# 4.5.测试支持

为异步应用程序编写集成一定比测试更简单的应用程序更复杂。当@RabbitListener注释之类的抽象出现在图片中时,这就变得更加复杂了。问题是如何验证在发送消息后,侦听器是否如预期的那样接收到了消息。

框架本身有许多单元和集成测试。一些使用模拟,而另一些则使用与实时 RabbitMQ 代理的集成测试。你可以参考这些测试来获得测试场景的一些想法。

Spring AMQP 版本 1.6 引入了spring-rabbit-testJAR,它为测试这些更复杂的场景中的一些提供了支持。预计该项目将随着时间的推移而扩展,但我们需要社区反馈来为帮助测试所需的功能提供建议。请使用JIRA (opens new window)GitHub 问题 (opens new window)提供此类反馈。

# 4.5.1.@SpringRabbitTest

使用此注释将基础设施 bean 添加到 Spring testApplicationContext。在使用@SpringBootTest时,这是不必要的,因为 Spring boot 的自动配置将添加 bean。

已注册的 bean 有:

  • CachingConnectionFactoryautoConnectionFactory)。如果存在@RabbitEnabled,则使用其连接工厂。

  • RabbitTemplate(autoRabbitTemplate)

  • RabbitAdmin(autoRabbitAdmin)

  • RabbitListenerContainerFactory(autoContainerFactory)

此外,还添加了与@EnableRabbit相关的 bean(以支持@RabbitListener)。

例 4.JUnit5 示例

@SpringJunitConfig
@SpringRabbitTest
public class MyRabbitTests {

	@Autowired
	private RabbitTemplate template;

	@Autowired
	private RabbitAdmin admin;

	@Autowired
	private RabbitListenerEndpointRegistry registry;

	@Test
	void test() {
        ...
	}

	@Configuration
	public static class Config {

        ...

	}

}

使用 JUnit4,将@SpringJunitConfig替换为@RunWith(SpringRunnner.class)

# 4.5.2.mockitoAnswer<?>实现

目前有两个Answer<?>实现来帮助测试。

第一个是LatchCountDownAndCallRealMethodAnswer,它提供一个Answer<Void>,返回null并对锁存器进行倒数。下面的示例展示了如何使用LatchCountDownAndCallRealMethodAnswer:

LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();

第二,LambdaAnswer<T>提供了一种可选地调用实际方法的机制,并提供了一种返回自定义结果的机会,该结果基于InvocationOnMock和结果(如果有的话)。

考虑以下 POJO:

public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}

下面的类测试ThingPOJO:

Thing thing = spy(new Thing());

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + r))
    .when(thing).thing(anyString());
assertEquals("THINGTHING", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + i.getArguments()[0]))
    .when(thing).thing(anyString());
assertEquals("THINGthing", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(false, (i, r) ->
    "" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString());
assertEquals("thingthing", thing.thing("thing"));

从版本 2.2.3 开始,答案将捕获由 Test 下的方法引发的任何异常。使用answer.getExceptions()获取对它们的引用。

当与[@RabbitListenerTestRabbitListenerTestHarness]结合使用时,使用harness.getLambdaAnswerFor("listenerId", true, …​)可以为侦听器获得正确构造的答案。

# 4.5.3.@RabbitListenerTestRabbitListenerTestHarness

将一个@Configuration类注释为@RabbitListenerTest,会导致框架将标准RabbitListenerAnnotationBeanPostProcessor替换为一个名为RabbitListenerTestHarness的子类(它还通过@EnableRabbit启用@RabbitListener检测)。

RabbitListenerTestHarness通过两种方式增强了侦听器。首先,它将侦听器封装在Mockito Spy中,从而实现正常的Mockito存根和验证操作。它还可以向侦听器添加Advice,从而能够访问参数、结果和引发的任何异常。你可以通过@RabbitListenerTest上的属性来控制其中哪些(或两个)被启用。提供后者是为了访问关于调用的较低级别的数据。它还支持在调用异步监听器之前阻塞测试线程。

final``@RabbitListener方法不能被监视或通知。
此外,只有具有id属性的侦听器才能被监视或通知。

举几个例子。

下面的示例使用 SPY:

@Configuration
@RabbitListenerTest
public class Config {

    @Bean
    public Listener listener() {
        return new Listener();
    }

    ...

}

public class Listener {

    @RabbitListener(id="foo", queues="#{queue1.name}")
    public String foo(String foo) {
        return foo.toUpperCase();
    }

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        Listener listener = this.harness.getSpy("foo"); (2)
        assertNotNull(listener);
        verify(listener).foo("foo");
    }

    @Test
    public void testOneWay() throws Exception {
        Listener listener = this.harness.getSpy("bar");
        assertNotNull(listener);

        LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("bar", 2); (3)
        doAnswer(answer).when(listener).foo(anyString(), anyString()); (4)

        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");

        assertTrue(answer.await(10));
        verify(listener).foo("bar", this.queue2.getName());
        verify(listener).foo("baz", this.queue2.getName());
    }

}
1 将线束注入到测试用例中,这样我们就可以访问间谍。
2 获取对 SPY 的引用,这样我们就可以验证它是否如预期的那样被调用了。
由于这是一个发送和接收操作,因此不需要挂起测试线程,因为它已经
挂起在RabbitTemplate中等待回复。
3 在这种情况下,我们只使用一个发送操作,所以我们需要一个锁存器来等待对容器线程上的侦听器
的异步调用。
我们使用Answer<?>实现之一来帮助实现这一点。
重要:由于侦听器被监视的方式,使用harness.getLatchAnswerFor()为间谍获得正确配置的答案是很重要的。
4 将 SPY 配置为调用Answer

下面的示例使用了捕获建议:

@Configuration
@ComponentScan
@RabbitListenerTest(spy = false, capture = true)
public class Config {

}

@Service
public class Listener {

    private boolean failed;

    @RabbitListener(id="foo", queues="#{queue1.name}")
    public String foo(String foo) {
        return foo.toUpperCase();
    }

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        if (!failed && foo.equals("ex")) {
            failed = true;
            throw new RuntimeException(foo);
        }
        failed = false;
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("foo", 0, TimeUnit.SECONDS); (2)
        assertThat(invocationData.getArguments()[0], equalTo("foo"));     (3)
        assertThat((String) invocationData.getResult(), equalTo("FOO"));
    }

    @Test
    public void testOneWay() throws Exception {
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "ex");

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS); (4)
        Object[] args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("bar"));
        assertThat((String) args[1], equalTo(queue2.getName()));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("baz"));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("ex"));
        assertEquals("ex", invocationData.getThrowable().getMessage()); (5)
    }

}
1 将线束注入到测试用例中,这样我们就可以访问间谍。
2 使用harness.getNextInvocationDataFor()来检索调用数据-在这种情况下,因为这是一个请求/回复
场景,因此不需要等待任何时间,因为测试线程被挂起在RabbitTemplate中等待
结果。
3 然后,我们可以验证该论证和结果是否如预期的那样。
4 这一次我们需要一些时间来等待数据,因为这是容器线程上的异步操作,我们需要
来挂起测试线程。
5 当侦听器抛出异常时,它在调用数据的throwable属性中可用。
当将自定义Answer<?>s 与线束一起使用时,为了正确地进行操作,这样的答案应该是子类ForwardsInvocation并从线束中获得实际的侦听器(而不是间谍)(getDelegate("myListener"))并调用

参见所提供的[mockitoAnswer<?>实现](#mockito-answer)源代码的示例。

# 4.5.4.使用TestRabbitTemplate

TestRabbitTemplate用于执行一些基本的集成测试,而不需要代理。在测试用例中将其添加为@Bean时,它会发现上下文中的所有侦听器容器,无论是声明为@Bean还是<bean/>还是使用@RabbitListener注释。它目前仅支持按队列名进行路由。模板从容器中提取消息侦听器,并直接在测试线程上调用它。返回回复的侦听器支持请求-回复消息(sendAndReceivemethods)。

下面的测试用例使用了模板:

@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {

    @Autowired
    private TestRabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void testSimpleSends() {
        this.template.convertAndSend("foo", "hello1");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello2");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:"));
        this.template.convertAndSend("foo", "hello3");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello4");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4"));

        this.template.setBroadcast(true);
        this.template.convertAndSend("foo", "hello5");
        assertThat(this.config.fooIn, equalTo("foo:hello1foo:hello5"));
        this.template.convertAndSend("bar", "hello6");
        assertThat(this.config.barIn, equalTo("bar:hello2bar:hello6"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4hello5hello6"));
    }

    @Test
    public void testSendAndReceive() {
        assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
    }
    @Configuration
    @EnableRabbit
    public static class Config {

        public String fooIn = "";

        public String barIn = "";

        public String smlc1In = "smlc1:";

        @Bean
        public TestRabbitTemplate template() throws IOException {
            return new TestRabbitTemplate(connectionFactory());
        }

        @Bean
        public ConnectionFactory connectionFactory() throws IOException {
            ConnectionFactory factory = mock(ConnectionFactory.class);
            Connection connection = mock(Connection.class);
            Channel channel = mock(Channel.class);
            willReturn(connection).given(factory).createConnection();
            willReturn(channel).given(connection).createChannel(anyBoolean());
            given(channel.isOpen()).willReturn(true);
            return factory;
        }

        @Bean
        public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() throws IOException {
            SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory());
            return factory;
        }

        @RabbitListener(queues = "foo")
        public void foo(String in) {
            this.fooIn += "foo:" + in;
        }

        @RabbitListener(queues = "bar")
        public void bar(String in) {
            this.barIn += "bar:" + in;
        }

        @RabbitListener(queues = "baz")
        public String baz(String in) {
            return "baz:" + in;
        }

        @Bean
        public SimpleMessageListenerContainer smlc1() throws IOException {
            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
            container.setQueueNames("foo", "bar");
            container.setMessageListener(new MessageListenerAdapter(new Object() {

                @SuppressWarnings("unused")
                public void handleMessage(String in) {
                    smlc1In += in;
                }

            }));
            return container;
        }

    }

}

# 4.5.5.JUnit4@Rules

Spring AMQP1.7 版及以后版本提供了一个名为spring-rabbit-junit的附加 JAR。此 JAR 包含两个实用程序@Rule实例,用于运行 JUnit4 测试时使用。关于 JUnit5 测试,请参见JUnit5 条件

# 使用BrokerRunning

BrokerRunning提供了一种机制,当代理不运行时(默认情况下,在localhost上),让测试成功。

它还具有用于初始化和清空队列以及删除队列和交换的实用程序方法。

下面的示例展示了它的用法:

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

有几个isRunning…​静态方法,例如isBrokerAndManagementRunning(),它验证代理是否启用了管理插件。

# 配置规则

有时,如果没有代理,你希望测试失败,比如夜间的 CI 构建。要在运行时禁用该规则,请将一个名为RABBITMQ_SERVER_REQUIRED的环境变量设置为true

你可以使用 setter 或环境变量重写代理属性,例如 hostname:

下面的示例展示了如何使用 setter 重写属性:

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

static {
    brokerRunning.setHostName("10.0.0.1")
}

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

你还可以通过设置以下环境变量来覆盖属性:

public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI";
public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME";
public static final String BROKER_PORT = "RABBITMQ_TEST_PORT";
public static final String BROKER_USER = "RABBITMQ_TEST_USER";
public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD";
public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER";
public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD";

这些环境变量覆盖了默认设置(对于 AMQP 是localhost:5672,对于 Management REST API 是[localhost:15672/api/](http://localhost:15672/api/))。

更改主机名会同时影响amqpmanagementRESTAPI 连接(除非显式设置了管理 URI)。

BrokerRunning还提供了一个名为staticstatic方法,该方法允许你在包含这些变量的映射中进行传递。它们覆盖系统环境变量。如果你希望对多个测试套件中的测试使用不同的配置,这可能会很有用。重要事项:在调用创建规则实例的任何isRunning()静态方法之前,必须调用该方法。变量值应用于此调用后创建的所有实例。调用clearEnvironmentVariableOverrides()重置规则以使用默认值(包括任何实际的环境变量)。

在你的测试用例中,你可以在创建连接工厂时使用brokerRunninggetConnectionFactory()返回规则的 RabbitMQConnectionFactory。下面的示例展示了如何做到这一点:

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
# 使用LongRunningIntegrationTest

LongRunningIntegrationTest是一条禁用长时间运行测试的规则。你可能希望在开发人员系统上使用该规则,但要确保在例如 Nightly CI 构建上禁用该规则。

下面的示例展示了它的用法:

@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

要在运行时禁用该规则,请将一个名为RUN_LONG_INTEGRATION_TESTS的环境变量设置为true

# 4.5.6.JUnit5 条件

2.0.2 版引入了对 JUnit5 的支持。

# 使用@RabbitAvailable注释

这个类级注释类似于在[JUnit4@Rules]中讨论的BrokerRunning``@Rule(#JUnit-rules)。它由RabbitAvailableCondition处理。

注释有三个属性:

  • queues:在每次测试之前声明(和清除)并在所有测试完成后删除的队列数组。

  • management:如果你的测试还需要在代理上安装管理插件,则将其设置为true

  • purgeAfterEach:(自版本 2.2)当true(默认)时,queues将在测试之间被清除。

它用于检查代理是否可用,如果不可用,则跳过测试。正如配置规则中所讨论的,如果RABBITMQ_SERVER_REQUIRED没有代理,则称为true的环境变量会导致测试快速失败。可以通过使用配置规则中讨论的环境变量来配置条件。

此外,RabbitAvailableCondition支持参数化测试构造函数和方法的参数解析。支持两种参数类型:

  • BrokerRunningSupport:实例(在 2.2 之前,这是一个 JUnit4BrokerRunning实例)

  • ConnectionFactory:BrokerRunningSupport实例的 RabbitMQ 连接工厂

下面的示例展示了这两个方面:

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final ConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory = brokerRunning.getConnectionFactory();
    }

    @Test
    public void test(ConnectionFactory cf) throws Exception {
        assertSame(cf, this.connectionFactory);
        Connection conn = this.connectionFactory.newConnection();
        Channel channel = conn.createChannel();
        DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
        assertEquals(0, declareOk.getConsumerCount());
        channel.close();
        conn.close();
    }

}

前面的测试是在框架本身中进行的,并验证参数注入和条件是否正确地创建了队列。

一个实际的用户测试可能如下:

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final CachingConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory =
            new CachingConnectionFactory(brokerRunning.getConnectionFactory());
    }

    @Test
    public void test() throws Exception {
        RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
        ...
    }
}

在测试类中使用 Spring 注释应用程序上下文时,可以通过一个名为RabbitAvailableCondition.getBrokerRunning()的静态方法获得对条件的连接工厂的引用。

从版本 2.2 开始,getBrokerRunning()返回一个BrokerRunningSupport对象;以前,返回的是 JUnit4BrokerRunnning实例。
新类的 API 与BrokerRunning相同。

下面的测试来自该框架,并演示了它的用法:

@RabbitAvailable(queues = {
        RabbitTemplateMPPIntegrationTests.QUEUE,
        RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {

    public static final String QUEUE = "mpp.tests";

    public static final String REPLIES = "mpp.tests.replies";

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void test() {

        ...

    }

    @Configuration
    @EnableRabbit
    public static class Config {

        @Bean
        public CachingConnectionFactory cf() {
            return new CachingConnectionFactory(RabbitAvailableCondition
                    .getBrokerRunning()
                    .getConnectionFactory());
        }

        @Bean
        public RabbitTemplate template() {

            ...

        }

        @Bean
        public SimpleRabbitListenerContainerFactory
                            rabbitListenerContainerFactory() {

            ...

        }

        @RabbitListener(queues = QUEUE)
        public byte[] foo(byte[] in) {
            return in;
        }

    }

}
# 使用@LongRunning注释

LongRunningIntegrationTestJUnit4@Rule类似,除非将环境变量(或系统属性)设置为true,否则此注释将导致跳过测试。下面的示例展示了如何使用它:

@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}

默认情况下,变量是RUN_LONG_INTEGRATION_TESTS,但你可以在注释的value属性中指定变量名。

# 5. Spring 整合-参考

参考文档的这一部分快速介绍了 Spring 集成项目中的 AMQP 支持。

# 5.1. Spring 集成 AMQP 支持

这一简短的章节涵盖了 Spring 集成和 Spring AMQP 项目之间的关系。

# 5.1.1.导言

Spring Integration (opens new window)项目包括基于 Spring AMQP 项目的 AMQP 通道适配器和网关。这些适配器是在 Spring 集成项目中开发和发布的。在 Spring 集成中,“通道适配器”是单向的,而“网关”是双向的。我们提供了入站通道适配器、出站通道适配器、入站网关和出站网关。

由于 AMQP 适配器是 Spring 集成发行版的一部分,所以文档可以作为 Spring 集成发行版的一部分使用。我们在这里提供了主要功能的简要概述。有关更多详细信息,请参见Spring Integration Reference Guide (opens new window)

# 5.1.2.入站通道适配器

要从队列接收 AMQP 消息,可以配置<inbound-channel-adapter>。下面的示例展示了如何配置入站通道适配器:

<amqp:inbound-channel-adapter channel="fromAMQP"
                              queue-names="some.queue"
                              connection-factory="rabbitConnectionFactory"/>

# 5.1.3.出站通道适配器

要将 AMQP 消息发送到 Exchange,可以配置<outbound-channel-adapter>。你可以在 Exchange 名称之外提供一个“路由密钥”。下面的示例展示了如何定义出站通道适配器:

<amqp:outbound-channel-adapter channel="toAMQP"
                               exchange-name="some.exchange"
                               routing-key="foo"
                               amqp-template="rabbitTemplate"/>

# 5.1.4.入站网关

要从队列接收 AMQP 消息并响应其应答地址,可以配置<inbound-gateway>。下面的示例展示了如何定义入站网关:

<amqp:inbound-gateway request-channel="fromAMQP"
                      reply-channel="toAMQP"
                      queue-names="some.queue"
                      connection-factory="rabbitConnectionFactory"/>

# 5.1.5.出站网关

要将 AMQP 消息发送到 Exchange 并从远程客户端接收回响应,可以配置<outbound-gateway>。你可以在 Exchange 名称之外提供一个“路由密钥”。下面的示例展示了如何定义出站网关:

<amqp:outbound-gateway request-channel="toAMQP"
                       reply-channel="fromAMQP"
                       exchange-name="some.exchange"
                       routing-key="foo"
                       amqp-template="rabbitTemplate"/>

# 6.其他资源

除了这个参考文档,还有许多其他资源可以帮助你了解 AMQP。

# 6.1.进一步阅读

对于那些不熟悉 AMQP 的人来说,规格 (opens new window)实际上是很有可读性的。当然,它是权威的信息来源, Spring AMQP 代码对于任何熟悉该规范的人都应该很容易理解。我们目前对 RabbitMQ 支持的实现基于他们的 2.8.x 版本,它正式支持 AMQP0.8 和 0.9.1.我们建议阅读 0.9.1 文档。

在 RabbitMQ开始 (opens new window)页面上有很多很棒的文章、演示文稿和博客。由于这是 Spring AMQP 当前唯一受支持的实现,因此我们还建议将其作为所有与代理相关的关注的一般起点。

# 附录 A:变更历史

这一节描述了在版本发生变化时所做的更改。

# a.1.当前版本

What’s New

# a.2.以前的版本

# a.2.1.自 2.2 以来 2.3 的变化

本部分描述了版本 2.2 和版本 2.3 之间的更改。有关以前版本的更改,请参见变更历史

# 连接工厂变更

现在提供了两个额外的连接工厂。有关更多信息,请参见选择连接工厂

# @RabbitListener变化

现在可以指定回复内容类型。有关更多信息,请参见回复 ContentType

# 消息转换器更改

如果ObjectMapper配置了自定义反序列化器,则Jackson2JMessageConverters 现在可以反序列化抽象类(包括接口)。有关更多信息,请参见反序列化抽象类

# 测试更改

提供了一个新的注释@SpringRabbitTest,用于在不使用SpringBootTest时自动配置一些基础设施 bean。有关更多信息,请参见@SpringRabbitTest

# RabbitTemplate 更改

模板的ReturnCallback已被重构为ReturnsCallback,以便在 lambda 表达式中更简单地使用。有关更多信息,请参见相关发布者确认并返回

当使用 Returns 和相关确认时,CorrelationData现在需要唯一的id属性。有关更多信息,请参见相关发布者确认并返回

当使用直接回复时,你现在可以配置模板,这样服务器就不需要返回与回复相关的数据。有关更多信息,请参见RabbitMQ 直接回复

# 侦听器容器更改

现在可以使用一个新的侦听器容器属性consumeDelay;当使用RabbitMQ 分片插件 (opens new window)时,该属性很有帮助。

默认的JavaLangErrorHandler现在调用System.exit(99)。要恢复到以前的行为(什么都不做),请添加一个 no-ophandler。

容器现在支持globalQos属性,以便在全局范围内为通道而不是为通道上的每个消费者应用prefetchCount

有关更多信息,请参见消息侦听器容器配置

# MessagePostProcessor 更改

压缩MessagePostProcessors 现在使用逗号来分隔多个内容编码,而不是冒号。解压器可以处理这两种格式,但是,如果你使用这个版本生成的消息被 2.2.12 之前的版本使用,那么你应该将压缩器配置为使用旧的分隔符。有关更多信息,请参见修改消息-压缩和更多中的重要注释。

# 多个代理支持改进

有关更多信息,请参见多个代理(或集群)支持

# RepublishMessageRecoverer 更改

不提供此 recoverer 的支持发布服务器确认的新子类。有关更多信息,请参见消息侦听器和异步情况

# a.2.2.自 2.1 以来 2.2 的变化

本部分描述了版本 2.1 和版本 2.2 之间的更改。

# 软件包更改

以下类/接口已从org.springframework.amqp.rabbit.core.support移至org.springframework.amqp.rabbit.batch:

  • BatchingStrategy

  • MessageBatch

  • SimpleBatchingStrategy

此外,ListenerExecutionFailedException已从org.springframework.amqp.rabbit.listener.exception移至org.springframework.amqp.rabbit.support

# 依赖项更改

JUnit(4)现在是一个可选的依赖项,并且将不再以传递依赖项的形式出现。

spring-rabbit-junit模块现在是编译模块中的spring-rabbit-test依赖项,以便在仅使用单个spring-rabbit-test时获得更好的目标应用程序开发体验,我们获得了 AMQP 组件的完整测试实用程序堆栈。

# “breaking”API 变更

JUnit(5)RabbitAvailableCondition.getBrokerRunning()现在返回一个BrokerRunningSupport实例,而不是一个BrokerRunning实例,后者取决于 JUnit4.它有相同的 API,所以只需要更改任何引用的类名。有关更多信息,请参见JUnit5 条件

# ListenerContainer 更改

默认情况下,即使确认模式是手动的,具有致命异常的消息现在也会被拒绝,并且不会重新请求。有关更多信息,请参见异常处理

监听器的性能现在可以使用微米计Timers 进行监视。有关更多信息,请参见监视监听器性能

# @RabbitListener 更改

现在可以在每个侦听器上配置executor,覆盖工厂配置,以更容易地识别与侦听器关联的线程。现在可以使用注释的ackMode属性覆盖容器工厂的acknowledgeMode属性。有关更多信息,请参见覆盖集装箱工厂的属性

当使用batching时,@RabbitListener方法现在可以在一个调用中接收一批完整的消息,而不是一次获得它们。

当一次只接收一条批处理消息时,最后一条消息的isLastInBatch消息属性设置为 true。

此外,收到的批处理消息现在包含amqp_batchSize头。

侦听器也可以使用在SimpleMessageListenerContainer中创建的批处理,即使该批处理不是由生成器创建的。有关更多信息,请参见选择容器

Spring 数据投影接口现在由Jackson2JsonMessageConverter支持。有关更多信息,请参见Using Spring Data Projection Interfaces

如果不存在Jackson2JsonMessageConverter属性,或者它是默认的(application/octet-string),则Jackson2JsonMessageConverter现在假定内容是 JSON。有关更多信息,请参见[converting from aMessage](#Jackson2jsonMessageConverter-from-message)。

类似地,如果不存在contentType属性,或者它是默认的(application/octet-string),则Jackson2XmlMessageConverter现在假定内容是 XML。有关更多信息,请参见[Jackson2XmlMessageConverter](#Jackson2xml)。

@RabbitListener方法返回结果时, Bean 和Method现在在回复消息属性中可用。这允许beforeSendReplyMessagePostProcessor的配置,例如,在回复中设置一个头,以指示在服务器上调用了哪个方法。有关更多信息,请参见回复管理

现在可以配置ReplyPostProcessor,以便在发送回复消息之前对其进行修改。有关更多信息,请参见回复管理

# AMQP 日志附录更改

log4j 和 logbackAmqpAppenders 现在支持verifyHostnameSSL 选项。

现在还可以将这些附录配置为不将 MDC 条目添加为标题。引入了addMdcAsHeaders布尔选项来配置这样的行为。

Appenders 现在支持SaslConfig属性。

有关更多信息,请参见日志记录子系统 AMQP 附录

# MessageListenerAdapter 更改

MessageListenerAdapter现在提供了一个新的buildListenerArguments(Object, Channel, Message)方法来构建一个参数数组,这些参数将被传递到目标侦听器中,而一个旧的参数将被弃用。有关更多信息,请参见[MessageListenerAdapter]。

# 交换/队列声明更改

用于创建ExchangeBuilderQueueBuilder用于由Exchange声明的Queue对象的 fluent API 现在支持“众所周知”的参数。有关更多信息,请参见用于队列和交换的 Builder API

RabbitAdmin有一个新的属性explicitDeclarationsOnly。有关更多信息,请参见有条件声明

# 连接工厂变更

CachingConnectionFactory有一个新的属性shuffleAddresses。当提供代理节点地址列表时,将在创建连接之前对列表进行调整,以便尝试连接的顺序是随机的。有关更多信息,请参见连接到集群

当使用 Publisher 确认和返回时,回调现在在连接工厂的executor上调用。如果你在回调中执行 Rabbit 操作,这就避免了amqp-clients库中可能出现的死锁。有关更多信息,请参见相关发布者确认并返回

此外,现在使用ConfirmType枚举来指定发布服务器确认类型,而不是使用两个互斥的 setter 方法。

当启用 SSL 时,RabbitConnectionFactoryBean现在默认使用 TLS1.2.有关更多信息,请参见[RabbitConnectionFactoryBean和配置 SSL]。

# 新的 MessagePostProcessor 类

当消息内容编码设置为deflate时,分别添加了类DeflaterPostProcessorInflaterPostProcessor以支持压缩和解压。

# 其他更改

Declarables对象(用于声明多个队列、交换、绑定)现在为每个类型都有一个过滤 getter。有关更多信息,请参见声明交换、队列和绑定的集合

现在可以在RabbitAdmin处理其声明之前自定义每个Declarable Bean。有关更多信息,请参见交换、队列和绑定的自动声明

singleActiveConsumer()已被添加到QueueBuilder以设置x-single-active-consumer队列参数。有关更多信息,请参见用于队列和交换的 Builder API

类型为Class<?>的出站标头现在使用getName()而不是toString()进行映射。有关更多信息,请参见消息属性转换器

现在支持恢复失败的生产者创建的批处理。有关更多信息,请参见使用批处理侦听器重试

# a.2.3.自 2.0 以来 2.1 的变化

# AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的amqp-client库的 5.4.x 版本。默认情况下,此客户端配置了自动恢复功能。见RabbitMQ 自动连接/拓扑恢复

从版本 4.0 开始,客户端默认支持自动恢复。
虽然与此功能兼容, Spring AMQP 有自己的恢复机制,并且客户端恢复功能通常不需要。
我们建议禁用amqp-client自动恢复,为了避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException实例。
从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory
RabbitMQConnectionFactoryRabbitConnectionFactoryBean创建的实例还具有默认禁用的选项。
# 软件包更改

某些类已转移到不同的包。大多数是内部类,不会影响用户应用程序。两个例外是ChannelAwareMessageListenerRabbitListenerErrorHandler。这些接口现在在org.springframework.amqp.rabbit.listener.api中。

# Publisher 确认更改

当存在未完成的确认时,启用了发布者确认的通道不会返回到缓存中。有关更多信息,请参见相关发布者确认并返回

# 监听器容器工厂改进

现在,你可以使用侦听器容器工厂来创建任何侦听器容器,而不仅仅是用于@RabbitListener注释或@RabbitListenerEndpointRegistry注释的侦听器容器。有关更多信息,请参见使用集装箱工厂

ChannelAwareMessageListener现在继承自MessageListener

# Broker 事件侦听器

引入BrokerEventListener以将选定的代理事件发布为ApplicationEvent实例。有关更多信息,请参见代理事件监听器

# RabbitAdmin 更改

RabbitAdmin发现类型为Declarables的 bean(这是Declarable-QueueExchangeBinding对象的容器)并在代理上声明所包含的对象。不鼓励用户使用声明<Collection<Queue>>(和其他)的旧机制,而应该使用Declarablesbean。默认情况下,旧机制将被禁用。有关更多信息,请参见声明交换、队列和绑定的集合

AnonymousQueue实例现在声明为x-queue-master-locator,默认设置为client-local,以确保队列是在应用程序所连接的节点上创建的。有关更多信息,请参见配置代理

# RabbitTemplate 更改

现在,你可以使用noLocalReplyConsumer选项配置RabbitTemplate,以便在sendAndReceive()操作中控制用于回复消费者的noLocal标志。有关更多信息,请参见请求/回复消息

CorrelationData对于发布者确认现在有一个ListenableFuture,你可以使用它来获得确认,而不是使用回调。当启用返回和确认时,相关数据(如果提供)将填充返回的消息。有关更多信息,请参见相关发布者确认并返回

现在提供了一个名为replyTimedOut的方法,用于通知子类答复已超时,从而允许进行任何状态清理。有关更多信息,请参见回复超时

现在,你可以指定一个ErrorHandler,当发送回复时发生异常(例如,延迟回复)时,在使用DirectReplyToMessageListenerContainer(默认)请求/回复时要调用的ErrorHandler。参见setReplyErrorHandler上的RabbitTemplate。(也是从 2.0.11 开始)。

# 消息转换

我们引入了一个新的Jackson2XmlMessageConverter,以支持将消息从 XML 格式转换为 XML 格式。有关更多信息,请参见[Jackson2XmlMessageConverter](#Jackson2xml)。

# 管理 REST API

现在不赞成RabbitManagementTemplate,而赞成直接使用com.rabbitmq.http.client.Client(或com.rabbitmq.http.client.ReactorNettyClient)。有关更多信息,请参见RabbitMQ REST API

# @RabbitListener变化

现在可以将侦听器容器工厂配置为RetryTemplate,也可以在发送回复时使用RecoveryCallback。有关更多信息,请参见启用监听器端点注释

# 异步@RabbitListener返回

@RabbitListener方法现在可以返回ListenableFuture<?>Mono<?>。有关更多信息,请参见[异步@RabbitListener返回类型]。

# 连接工厂 Bean 变更

默认情况下,RabbitConnectionFactoryBean现在调用enableHostnameVerification()。要恢复到以前的行为,请将enableHostnameVerification属性设置为false

# 连接工厂变更

现在,CachingConnectionFactory无条件地禁用底层 RabbitMQConnectionFactory中的自动恢复,即使构造函数中提供了预先配置的实例。虽然已经采取措施使 Spring AMQP 与自动恢复兼容,但在仍然存在问题的情况下出现了某些情况。 Spring AMQP 自 1.0.0 以来已经有了自己的恢复机制,并且不需要使用由客户端提供的恢复。虽然仍然有可能在CachingConnectionFactory后启用该功能(使用cachingConnectionFactory.getRabbitConnectionFactory()``.setAutomaticRecoveryEnabled()),**我们强烈建议你不要这样做。**被构造。如果在直接使用客户端工厂(而不是使用 Spring AMQP 组件)时需要自动恢复连接,我们建议你使用单独的 RabbitMQ。

# 侦听器容器更改

如果存在x-death报头,那么默认的ConditionalRejectingErrorHandler现在将完全丢弃导致致命错误的消息。有关更多信息,请参见异常处理

# 立即请求

引入了一个新的ImmediateRequeueAmqpException来通知侦听器容器消息必须重新排队。要使用此功能,需要添加一个新的ImmediateRequeueMessageRecoverer实现。

有关更多信息,请参见消息侦听器和异步情况

# a.2.4.自 1.7 以来 2.0 的变化

# 使用CachingConnectionFactory

从版本 2.0.2 开始,你可以将RabbitTemplate配置为使用与侦听器容器使用的不同的连接。这一变化避免了当生产商因任何原因而受阻时,消费者陷入僵局。有关更多信息,请参见使用单独的连接

# AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的amqp-client库的新的 5.0.x 版本。默认情况下,此客户端配置了自动恢复。见RabbitMQ 自动连接/拓扑恢复

从版本 4.0 开始,客户端默认启用自动恢复。
虽然兼容此功能, Spring AMQP 有自己的恢复机制,并且客户端恢复功能一般不需要。
我们建议你禁用amqp-client自动恢复,为了避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException实例。
从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory
RabbitMQConnectionFactoryRabbitConnectionFactoryBean创建的实例还具有默认禁用的选项。
# 一般变化

现在ExchangeBuilder默认情况下构建持久交换。在@QeueueBinding中使用的@Exchange注释也默认声明持久交换。在@RabbitListener中使用的@Queue注释默认情况下声明持久队列(如果命名)和非持久队列(如果匿名)。有关更多信息,请参见用于队列和交换的 Builder API注释驱动的监听器端点

# 删除类

UniquelyNameQueue不再提供。使用唯一的名称创建持久的非自动删除队列是不常见的。这个类已被删除。如果你需要它的功能,请使用new Queue(UUID.randomUUID().toString())

# 新建侦听器容器

已在现有的SimpleMessageListenerContainer旁边添加了DirectMessageListenerContainer。有关选择使用哪个容器以及如何配置它们的信息,请参见选择容器消息侦听器容器配置

# log4j 附录

由于 log4j 的报废,此附录不再可用。有关可用日志附录的信息,请参见日志记录子系统 AMQP 附录

# RabbitTemplate变更
以前,如果一个非事务性事务RabbitTemplate运行在事务性侦听器容器线程上,那么它就参与了一个现有事务。,
这是一个严重的错误,
但是,用户可能依赖于这种行为,
从 1.6.2 版本开始,你必须在模板上设置channelTransacted布尔,才能让它参与容器事务。

RabbitTemplate现在使用DirectReplyToMessageListenerContainer(默认情况下),而不是为每个请求创建一个新的使用者。有关更多信息,请参见RabbitMQ 直接回复

AsyncRabbitTemplate现在支持直接回复。有关更多信息,请参见异步兔子模板

RabbitTemplateAsyncRabbitTemplate现在有receiveAndConvertconvertSendAndReceiveAsType方法,它们接受一个ParameterizedTypeReference<T>参数,让调用者指定将结果转换为哪个类型。这对于复杂类型或在消息头中未传递类型信息的情况特别有用。它需要一个SmartMessageConverter,如Jackson2JsonMessageConverter。有关更多信息,请参见接收消息请求/回复消息异步兔子模板,以及[从MessageRabbitTemplate转换](#json-complex)。

现在可以使用RabbitTemplate在专用通道上执行多个操作。有关更多信息,请参见作用域操作

# 侦听器适配器

一个方便的FunctionalInterface可用于使用带有MessageListenerAdapter的 lambdas。有关更多信息,请参见[MessageListenerAdapter](#message-listener-adapter)。

# 侦听器容器更改
# 预取默认值

预取默认值过去是 1,这可能导致有效消费者的利用率不足。默认的预取值现在是 250,这应该会使消费者在大多数常见的情况下都很忙,从而提高吞吐量。

在某些情况下,预取取值应该
较低,例如,对于较大的消息,尤其是在处理速度较慢的情况下(消息可能会将
累加到客户端进程中的大量内存中),如果需要严格的消息排序
(在这种情况下,预取值应该设置为 1),
还可以使用低容量消息传递和多个消费者(包括单个侦听器容器实例中的并发性),你可能希望减少预取,以使消息在消费者之间的分布更加均匀。

有关 Prefetch 的更多背景信息,请参见这篇关于RabbitMQ 中的消费者利用 (opens new window)的文章和这篇关于排队论 (opens new window)的文章。

# 消息计数

以前,对于容器发出的消息,MessageProperties.getMessageCount()返回0。此属性仅当你使用basicGet(例如,从RabbitTemplate.receive()方法)时才适用,并且现在对于容器消息初始化为null

# 事务回滚行为

无论是否配置了事务管理器,事务回滚上的消息重新队列现在都是一致的。有关更多信息,请参见关于回滚收到的消息的说明

# 关机行为

如果容器线程不响应shutdownTimeout内的关机,则默认情况下将强制关闭通道。有关更多信息,请参见消息侦听器容器配置

# 接收消息后处理程序

如果afterReceiveMessagePostProcessors属性中的MessagePostProcessor返回null,则该消息将被丢弃(并在适当的情况下进行确认)。

# 连接工厂变更

连接和通道侦听器接口现在提供了一种机制来获取有关异常的信息。有关更多信息,请参见连接和通道侦听器发布是异步的——如何检测成功和失败

现在提供了一个新的ConnectionNameStrategy,用于从AbstractConnectionFactory填充目标 RabbitMQ 连接的特定于应用程序的标识。有关更多信息,请参见连接和资源管理

# 重试更改

不再提供MissingMessageIdAdvice。它的功能现在是内置的。有关更多信息,请参见同步操作中的故障和重试选项

# 匿名队列命名

默认情况下,AnonymousQueues现在使用默认的Base64UrlNamingStrategy来命名,而不是简单的UUID字符串。有关更多信息,请参见[AnonymousQueue]。

# @RabbitListener变更

现在可以在@RabbitListener注释中提供简单的队列声明(仅绑定到默认的交换)。有关更多信息,请参见注释驱动的监听器端点

你现在可以配置@RabbitListener注释,以便将任何异常返回给发送者。你还可以配置RabbitListenerErrorHandler来处理异常。有关更多信息,请参见处理异常

使用@QueueBinding注释时,现在可以将队列与多个路由键绑定。另外@QueueBinding.exchange()现在支持自定义交换类型,并默认声明持久交换。

现在,你可以在注释级设置侦听器容器的concurrency,而不必为不同的并发设置配置不同的容器工厂。

现在可以在注释级设置侦听器容器的autoStartup属性,从而覆盖容器工厂中的默认设置。

你现在可以在RabbitListener容器工厂中设置接收之后和发送之前的MessagePostProcessor实例。

有关更多信息,请参见注释驱动的监听器端点

从版本 2.0.3 开始,类级@RabbitHandler上的一个@RabbitListener注释可以被指定为默认值。有关更多信息,请参见多方法侦听器

# 容器条件回滚

当使用外部事务管理器(例如 JDBC)时,当你为容器提供事务属性时,现在支持基于规则的回滚。现在,当你使用事务建议时,它也更加灵活。有关更多信息,请参见条件回滚

# 删除 Jackson1.x 支持

在以前的版本中不推荐的 Jackson1.x转换器和相关组件现在已被删除。你可以使用基于 Jackson2.x 的类似组件。有关更多信息,请参见Jackson2JSONMessageConverter

# JSON 消息转换器

当将入站 JSON 消息的*TypeId*设置为Hashtable时,现在默认的转换类型是LinkedHashMap。在此之前,它是Hashtable。要恢复到Hashtable,可以在DefaultClassMapper上使用setDefaultMapType

# XML 解析器

在解析QueueExchangeXML 组件时,如果存在id属性,则解析器不再将name属性值注册为 Bean 别名。有关更多信息,请参见[关于idname属性的注释]。

# 阻塞连接

现在可以将com.rabbitmq.client.BlockedListener注入org.springframework.amqp.rabbit.connection.Connection对象。此外,当连接被代理阻塞或解除锁定时,ConnectionBlockedEventConnectionUnblockedEvent事件由ConnectionFactory发出。

有关更多信息,请参见连接和资源管理

# a.2.5.1.7 自 1.6 以来的变化

# AMQP 客户库

Spring AMQP 现在使用由 RabbitMQ 团队提供的amqp-client库的新的 4.0.x 版本。默认情况下,此客户端配置了自动恢复功能。见RabbitMQ 自动连接/拓扑恢复

4.0.x 客户端默认支持自动恢复。
虽然与此功能兼容, Spring AMQP 有自己的恢复机制,并且客户端恢复功能通常不需要。
我们建议禁用amqp-client自动恢复,为了避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException实例。
从 1.7.1 版本开始, Spring AMQP 禁用它,除非你显式地创建自己的 RabbitMQ 连接工厂,并将其提供给CachingConnectionFactory
RabbitMQConnectionFactoryRabbitConnectionFactoryBean创建的实例还具有默认禁用的选项。
# log4j2 升级

最小的 log4j2 版本(对于AmqpAppender)现在是2.7。该框架不再与以前的版本兼容。有关更多信息,请参见日志记录子系统 AMQP 附录

# 翻录附录

默认情况下,此附录不再捕获调用方数据(方法、行号)。你可以通过设置includeCallerData配置选项重新启用它。有关可用日志附录的信息,请参见日志记录子系统 AMQP 附录

# Spring 重试升级

重试的最低版本现在是1.2。该框架不再与以前的版本兼容。

# 关机行为

你现在可以将forceCloseChannel设置为true,这样,如果容器线程在shutdownTimeout内没有响应关机,则将强制关闭通道,从而导致任何未加锁的消息重新排队。有关更多信息,请参见消息侦听器容器配置

# fasterxml Jackson 升级

Jackson 的最低版本现在是2.8。该框架不再与以前的版本兼容。

# JUnit@Rules

以前由框架内部使用的规则现在可以在一个名为spring-rabbit-junit的单独 JAR 中使用。有关更多信息,请参见[JUnit4@Rules]。

# 容器条件回滚

当你使用外部事务管理器(例如 JDBC)时,当你为容器提供事务属性时,现在支持基于规则的回滚。现在,当你使用事务建议时,它也更加灵活。

# 连接命名策略

现在提供了一个新的ConnectionNameStrategy,用于从AbstractConnectionFactory填充目标 RabbitMQ 连接的特定于应用程序的标识。有关更多信息,请参见连接和资源管理

# 侦听器容器更改
# 事务回滚行为

现在,无论是否配置了事务管理器,都可以将事务回滚上的消息重新排队配置为一致。有关更多信息,请参见关于回滚收到的消息的说明

# a.2.6.早期版本

有关以前版本的更改,请参见以前的版本

# a.2.7.自 1.5 以来 1.6 的变化

# 测试支持

现在提供了一个新的测试支持库。有关更多信息,请参见测试支持

# 建设者

现在可以使用为配置QueueExchange对象提供流畅 API 的构建器。有关更多信息,请参见用于队列和交换的 Builder API

# 名称空间变更
# 连接工厂

现在可以将thread-factory添加到连接工厂 Bean 声明中——例如,用于命名由amqp-client库创建的线程。有关更多信息,请参见连接和资源管理

当你使用CacheMode.CONNECTION时,现在可以限制允许的连接总数。有关更多信息,请参见连接和资源管理

# 队列定义

现在可以为匿名队列提供命名策略。有关更多信息,请参见[AnonymousQueue]。

# 侦听器容器更改
# 空闲消息侦听器检测

现在可以将侦听器容器配置为在空闲时发布ApplicationEvent实例。有关更多信息,请参见检测空闲异步消费者

# 不匹配队列检测

默认情况下,当侦听器容器启动时,如果检测到具有不匹配属性或参数的队列,则容器会记录异常,但会继续侦听。容器现在具有一个名为mismatchedQueuesFatal的属性,如果在启动过程中检测到问题,该属性将阻止容器(和上下文)启动。如果稍后检测到问题,例如从连接失败中恢复后,它还会停止容器。有关更多信息,请参见消息侦听器容器配置

# 侦听器容器日志记录

现在,侦听器容器将其beanName提供给内部SimpleAsyncTaskExecutor作为threadNamePrefix。它对日志分析很有用。

# 默认错误处理程序

默认的错误处理程序(ConditionalRejectingErrorHandler)现在认为不可恢复的@RabbitListener异常是致命的。有关更多信息,请参见异常处理

# AutoDeclareRabbitAdmin实例

有关在应用程序上下文中使用RabbitAdmin实例对该选项语义的一些更改,请参见消息侦听器容器配置autoDeclare)。

# AmqpTemplate:超时接收

已经为AmqpTemplate及其RabbitTemplate实现引入了许多带有timeout的新receive()方法。有关更多信息,请参见轮询消费者

# 使用AsyncRabbitTemplate

引入了一个新的AsyncRabbitTemplate。此模板提供了许多发送和接收方法,其中返回值为ListenableFuture,以后可以使用它同步或异步地获得结果。有关更多信息,请参见异步兔子模板

# RabbitTemplate变化

1.4.1 引入了在代理支持直接回复 (opens new window)时使用直接回复 (opens new window)的能力。这比为每个回复使用临时队列更有效。此版本允许你通过将useTemporaryReplyQueues属性设置为true来覆盖此默认行为并使用临时队列。有关更多信息,请参见RabbitMQ 直接回复

RabbitTemplate现在支持user-id-expression(在使用 Java 配置时userIdExpression)。有关更多信息,请参见经过验证的用户 ID RabbitMQ 文档 (opens new window)已验证的用户 ID

# 消息属性
# 使用CorrelationId

消息属性correlationId现在可以是String。有关更多信息,请参见消息属性转换器

# 长字符串标题

以前,DefaultMessagePropertiesConverter将比长字符串限制(缺省 1024)更长的头转换为DataInputStream(实际上,它引用了LongString实例的DataInputStream)。在输出时,这个头没有被转换(除了转换为字符串——例如,通过在流上调用toString()[[email protected]](/cdn-cgi/l/email-protection))。

在此版本中,长LongString实例现在默认保留为LongString实例。你可以使用getBytes[]toString()getStream()方法访问内容。大量输入的LongString现在也可以在输出上正确地“转换”。

有关更多信息,请参见消息属性转换器

# 入站交付模式

deliveryMode属性不再映射到MessageProperties.deliveryMode。如果使用相同的MessageProperties对象发送出站消息,则此更改将避免意外传播。相反,入站deliveryMode标头被映射到MessageProperties.receivedDeliveryMode

有关更多信息,请参见消息属性转换器

当使用带注释的端点时,标题提供在名为AmqpHeaders.RECEIVED_DELIVERY_MODE的标题中。

有关更多信息,请参见带注释的端点方法签名

# 入站用户 ID

user_id属性不再映射到MessageProperties.userId。如果使用相同的MessageProperties对象发送出站消息,则此更改将避免意外传播。相反,入站userId头被映射到MessageProperties.receivedUserId

有关更多信息,请参见消息属性转换器

当你使用带注释的端点时,标题将在名为AmqpHeaders.RECEIVED_USER_ID的标题中提供。

有关更多信息,请参见带注释的端点方法签名

# RabbitAdmin变化
# 宣告失败

以前,ignoreDeclarationFailures标志仅对通道上的IOException生效(例如不匹配的参数)。它现在对任何例外情况生效(例如TimeoutException)。此外,现在只要声明失败,就会发布DeclarationExceptionEventRabbitAdminLast declaration 事件也可以作为属性lastDeclarationExceptionEvent使用。有关更多信息,请参见配置代理

# @RabbitListener变更
# 每个容器有多个 Bean

当你使用 Java8 或更高版本时,你现在可以将多个@RabbitListener注释添加到@Bean类或它们的方法中。当使用 Java7 或更早版本时,你可以使用@RabbitListeners容器注释来提供相同的功能。有关更多信息,请参见[@Repeatable``@RabbitListener]。

# @SendTospel 表达式

@SendTo用于路由不带replyTo属性的回复,现在可以根据请求/回复计算 SPEL 表达式。有关更多信息,请参见回复管理

# @QueueBinding改进

现在可以在@QueueBinding注释中指定队列、交换和绑定的参数。现在@QueueBinding支持报头交换。有关更多信息,请参见注释驱动的监听器端点

# 延迟消息交换

Spring AMQP 现在拥有对 RabbitMQ 延迟消息交换插件的一流支持。有关更多信息,请参见延迟的消息交换

# 交易所内部标志

任何Exchange定义现在都可以标记为internal,并且RabbitAdmin在声明交易所时将该值传递给代理。有关更多信息,请参见配置代理

# CachingConnectionFactory变化
# CachingConnectionFactory缓存统计信息

CachingConnectionFactory现在在运行时和 JMX 上提供缓存属性。有关更多信息,请参见运行时缓存属性

# 访问底层 RabbitMQ 连接工厂

添加了一个新的 getter 以提供对底层工厂的访问。例如,你可以使用此 getter 添加自定义连接属性。有关更多信息,请参见添加自定义客户端连接属性

# 通道缓存

默认通道缓存大小已从 1 增加到 25.有关更多信息,请参见连接和资源管理

此外,SimpleMessageListenerContainer不再调整缓存大小,使其至少与concurrentConsumers的数量一样大——这是多余的,因为容器使用者通道永远不会被缓存。

# 使用RabbitConnectionFactoryBean

工厂 Bean 现在公开一个属性,以便将客户机连接属性添加到由结果工厂创建的连接中。

# Java 反序列化

现在,你可以在使用 Java 反序列化时配置一个允许类的“允许列表”。如果你接受来自不受信任的源的带有序列化 Java 对象的消息,那么你应该考虑创建一个允许的列表。有关更多信息,请参见Java 反序列化

# JSON MessageConverter

对 JSON 消息转换器的改进现在允许使用消息头中没有类型信息的消息。有关更多信息,请参见带注释方法的消息转换Jackson2JSONMessageConverter

# 日志附录
# log4j2

添加了一个 log4j2Appender,现在可以将 Appenders 配置为addresses属性,以连接到代理群集。

# 客户端连接属性

现在可以将自定义客户端连接属性添加到 RabbitMQ 连接中。

有关更多信息,请参见日志记录子系统 AMQP 附录

# a.2.8.1.5 自 1.4 以来的变化

# spring-erlang不再支持

spring-erlangJAR 不再包含在分发版中。用RabbitMQ REST API代替。

# CachingConnectionFactory变化
# 中的空地址属性CachingConnectionFactory

以前,如果连接工厂配置了主机和端口,但也为addresses提供了一个空字符串,则会忽略主机和端口。现在,一个空的addresses字符串被处理为与null相同的字符串,并且使用了主机和端口。

# URI 构造函数

CachingConnectionFactory有一个附加的构造函数,带有URI参数,用于配置代理连接。

# 连接重置

添加了一个名为resetConnection()的新方法,让用户重置连接(或连接)。例如,你可以使用它在失败转移到次要代理之后重新连接到主代理。这**是吗?**会影响进程内操作。现有的destroy()方法做的完全相同,但是新方法的名称不那么令人生畏。

# 控制容器队列声明行为的属性

当监听器容器使用者启动时,他们尝试被动地声明队列,以确保它们在代理上可用。在此之前,如果这些声明失败(例如,因为队列不存在),或者当一个 HA 队列被移动时,重试逻辑被固定为以五秒为间隔的三次重试尝试。如果队列仍然不存在,则行为由missingQueuesFatal属性控制(默认:true)。此外,对于配置为监听多个队列的容器,如果只有一个队列子集可用,那么使用者将在 60 秒的固定时间间隔内重试丢失的队列。

declarationRetriesfailedDeclarationRetryIntervalretryDeclarationInterval属性现在是可配置的。有关更多信息,请参见消息侦听器容器配置

# 类包更改

RabbitGatewaySupport类从o.s.amqp.rabbit.core.support移动到o.s.amqp.rabbit.core

# DefaultMessagePropertiesConverter变化

你现在可以配置DefaultMessagePropertiesConverter以确定将LongString转换为String而不是转换为DataInputStream的最大长度。转换器有一个替代的构造函数,它将值作为一个限制。以前,这个限制是以1024字节进行硬编码的。(也可在 1.4.4 中获得)。

# @RabbitListener改进
# @QueueBinding表示@RabbitListener

bindings属性已被添加到@RabbitListener注释中,作为与queues属性的互斥,以允许queue的规范,其exchangebinding用于代理上的RabbitAdmin声明。

# spel in@SendTo

@RabbitListener的默认回复地址(@SendTo)现在可以是 SPEL 表达式。

# 通过属性获得多个队列名称

现在可以使用 SPEL 和属性占位符的组合来为侦听器指定多个队列。

有关更多信息,请参见注释驱动的监听器端点

# 自动交换、队列和绑定声明

现在可以声明定义这些实体集合的 bean,并且RabbitAdmin将内容添加到它在建立连接时声明的实体列表中。有关更多信息,请参见声明交换、队列和绑定的集合

# RabbitTemplate变更
# reply-address已添加

已将reply-address属性添加到<rabbit-template>组件中,作为替代reply-queue。有关更多信息,请参见请求/回复消息。(也可以在 1.4.4 中作为RabbitTemplate上的 setter)。

# 阻塞receive方法

RabbitTemplate现在支持receiveconvertAndReceive方法中的阻塞。有关更多信息,请参见轮询消费者

# 强制使用sendAndReceive方法

当使用mandatoryconvertSendAndReceive方法设置mandatory标志时,如果无法传递请求消息,则调用线程将抛出AmqpMessageReturnedException。有关更多信息,请参见回复超时

# 不正确的应答侦听器配置

当使用命名的应答队列时,框架将尝试验证应答侦听器容器的正确配置。

有关更多信息,请参见回复监听器容器

# RabbitManagementTemplate已添加

引入了RabbitManagementTemplate,通过使用其管理插件 Name (opens new window)提供的 REST API 来监视和配置 RabbitMQ 代理。有关更多信息,请参见RabbitMQ REST API

# 侦听器容器 Bean 名称
<listener-container/>元素上的id属性已被删除。
从这个版本开始,单独使用<listener/>子元素上的id来命名为每个侦听器元素创建的侦听器容器 Bean。将应用

正常的 Spring Bean 名称重载。
如果稍后的<listener/>与现有的 Bean 相同的id进行解析,新的定义覆盖了现有的定义。
以前, Bean 名称是由id<listener-container/>元素的<listener/>属性组成的。
当迁移到此版本时,如果你的id元素上有id属性,删除它们并在子元素<listener/>上设置id

但是,为了支持作为一个组开始和停止容器,添加了一个新的group属性。定义此属性时,由此元素创建的容器将添加到具有此名称的 Bean,类型为Collection<SimpleMessageListenerContainer>。你可以迭代这个组来启动和停止容器。

# class-level@RabbitListener

现在可以在类级别应用@RabbitListener注释。与新的@RabbitHandler方法注释一起,这允许你基于有效负载类型选择处理程序方法。有关更多信息,请参见多方法侦听器

# SimpleMessageListenerContainer:Backoff 支持

现在可以为SimpleMessageListenerContainer启动恢复提供BackOff实例。有关更多信息,请参见消息侦听器容器配置

# 通道关闭日志记录

介绍了一种控制信道关闭日志级别的机制。见记录通道关闭事件

# 应用程序事件

现在,当使用者失败时,SimpleMessageListenerContainer将发出应用程序事件。有关更多信息,请参见消费者活动

# 消费者标签配置

以前,异步消费者的消费者标记是由代理生成的。通过这个版本,现在可以向侦听器容器提供命名策略。见消费者标签

# 使用MessageListenerAdapter

MessageListenerAdapter现在支持队列名称(或使用者标记)到方法名称的映射,以根据接收消息的队列确定调用哪个委托方法。

# LocalizedQueueConnectionFactory已添加

LocalizedQueueConnectionFactory是一个新的连接工厂,它连接到集群中实际驻留镜像队列的节点。

参见[Queue Affinity 和LocalizedQueueConnectionFactory]。

# 匿名队列命名

从版本 1.5.3 开始,你现在可以控制AnonymousQueue名称的生成方式。有关更多信息,请参见[AnonymousQueue]。

# a.2.9.自 1.3 以来 1.4 的变化

# @RabbitListener注释

POJO 侦听器可以使用@RabbitListener进行注释,也可以使用@EnableRabbit<rabbit:annotation-driven />。 Spring 此功能需要框架 4.1.有关更多信息,请参见注释驱动的监听器端点

# RabbitMessagingTemplate已添加

一个新的RabbitMessagingTemplate允许你通过使用spring-messaging``Message实例与 RabbitMQ 进行交互。在内部,它使用RabbitTemplate,你可以将其配置为常规配置。 Spring 此功能需要框架 4.1.有关更多信息,请参见消息传递集成

# 侦听器容器missingQueuesFatal属性

1.3.5 在SimpleMessageListenerContainer上引入了missingQueuesFatal属性。这现在在侦听器容器名称空间元素上可用。见消息侦听器容器配置

# RabbitTemplateConfirmCallback接口

这个接口上的confirm方法有一个额外的参数,称为cause。当可用时,此参数包含否定确认的原因。见相关发布者确认并返回

# RabbitConnectionFactoryBean已添加

RabbitConnectionFactoryBean创建由CachingConnectionFactory使用的底层 RabbitMQConnectionFactory。这允许使用 Spring 的依赖注入配置 SSL 选项。见配置底层客户机连接工厂

# 使用CachingConnectionFactory

现在,CachingConnectionFactory允许将connectionTimeout设置为名称空间中的一个属性或属性。它在底层 RabbitMQConnectionFactory上设置属性。见配置底层客户机连接工厂

# 日志附录

已引入了注销org.springframework.amqp.rabbit.logback.AmqpAppender。它提供了类似于org.springframework.amqp.rabbit.log4j.AmqpAppender的选项。有关更多信息,请参见这些类的 Javadoc。

log4jAmqpAppender现在支持deliveryMode属性(PERSISTENTNON_PERSISTENT,缺省:PERSISTENT)。以前,所有的 log4j 消息都是PERSISTENT

Appender 还支持在发送前修改Message——例如,允许添加自定义标头。子类应该覆盖postProcessMessageBeforeSend()

# 侦听器队列

现在,默认情况下,侦听器容器会在启动过程中重新声明任何丢失的队列。已向<rabbit:listener-container>添加了一个新的auto-declare属性,以防止这些重新声明。参见[auto-delete队列]。

# RabbitTemplate:mandatoryconnectionFactorySelector表达式

mandatoryExpressionsendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpressionspel 表达式s properties have been added toRabbitTemplate. Themandatoryexpressionis used to evaluate a强制boolean value against each request message when areturncallis in use. See [Correlated Publisher Confirms and Returns](#template-confirms). ThesendConnectionygt=“factorexpression<4214"/>factortortoryr=“actorpression<actorpression4215"/>mentfactortortorypresslausion<yprespress 见路由连接工厂

# 侦听器和路由连接工厂

你可以配置带有路由连接工厂的SimpleMessageListenerContainer,以启用基于队列名称的连接选择。见路由连接工厂

# RabbitTemplate:RecoveryCallback选项

已经添加了recoveryCallback属性,以便在retryTemplate.execute()中使用。见添加重试功能

# MessageConversionException变化

这个异常现在是AmqpException的子类。考虑以下代码:

try {
    template.convertAndSend("thing1", "thing2", "cat");
}
catch (AmqpException e) {
	...
}
catch (MessageConversionException e) {
	...
}

第二个捕获块不再可达,需要移动到捕获全部AmqpException的捕获块之上。

# RabbitMQ3.4 兼容性

Spring AMQP 现在与 RabbitMQ3.4 兼容,包括直接回复。有关更多信息,请参见相容性RabbitMQ 直接回复

# ContentTypeDelegatingMessageConverter已添加

基于contentType中的MessageProperties属性,引入了ContentTypeDelegatingMessageConverter来选择要使用的MessageConverter。有关更多信息,请参见消息转换器

# a.2.10.1.3 自 1.2 以来的变化

# 侦听器并发

侦听器容器现在支持基于工作负载的消费者数量的动态伸缩,或者你可以在不停止容器的情况下以编程方式更改并发性。见监听器并发

# 侦听器队列

侦听器容器现在允许在运行时修改它侦听的队列。此外,如果至少有一个配置好的队列可供使用,容器现在就会启动。见监听器容器队列

这个侦听器容器现在在启动期间重新声明任何自动删除队列。参见[auto-delete队列]。

# 消费者优先权

侦听器容器现在支持消费者参数,允许设置x-priority参数。见消费者优先权

# 独家消费者

你现在可以用一个SimpleMessageListenerContainer消费者配置exclusive,从而防止其他消费者监听队列。见独家消费者

# 兔子管理员

现在可以让代理生成队列名,而不考虑durableautoDeleteexclusive设置。见配置代理

# 直接交换绑定

以前,从direct-exchange配置的binding元素中省略key属性会导致队列或交换被绑定为一个空字符串作为路由键。现在它与所提供的名称QueueExchange绑定。如果希望使用空字符串路由键绑定,则需要指定key=""

# AmqpTemplate变化

AmqpTemplate现在提供了几个同步的receiveAndReply方法。这些都是由RabbitTemplate实现的。有关更多信息,请参见接收消息

RabbitTemplate现在支持配置RetryTemplate,以便在代理不可用时尝试重试(使用可选的后退策略)。有关更多信息,请参见添加重试功能

# 缓存连接工厂

现在,你可以将缓存连接工厂配置为缓存Connection实例及其Channel实例,而不是使用单个连接并只缓存Channel实例。见连接和资源管理

# 约束参数

<exchange><binding>现在支持解析<binding-arguments>子元素。现在,你可以使用key/value属性对配置<binding>中的<binding>(以便在单个标头上进行匹配)或使用<binding-arguments>子元素(允许在多个标头上进行匹配)。这些选择是相互排斥的。见头交换

# 路由连接工厂

引入了一个新的SimpleRoutingConnectionFactory。它允许配置ConnectionFactories映射,以确定在运行时使用的目标ConnectionFactory。见路由连接工厂

# MessageBuilderMessagePropertiesBuilder

现在提供了用于构建消息或消息属性的“Fluent API”。见Message Builder API

# RetryInterceptorBuilder变化

现在提供了用于构建侦听器容器重试拦截器的“Fluent API”。见同步操作中的故障和重试选项

# RepublishMessageRecoverer已添加

提供了这个新的MessageRecoverer,以允许在重试用完时将失败的消息发布到另一个队列(包括消息头中的堆栈跟踪信息)。见消息侦听器和异步情况

# 默认错误处理程序(自 1.3.2 起)

已将默认的ConditionalRejectingErrorHandler添加到侦听器容器中。此错误处理程序检测到致命的消息转换问题,并指示容器拒绝该消息,以防止代理继续重新交付不可转换的消息。见异常处理

# 侦听器容器“missingqueuesfatal”属性(自 1.3.5)

SimpleMessageListenerContainer现在有一个名为missingQueuesFatal的属性(默认值:true)。以前,排不上队总是致命的。见消息侦听器容器配置

# a.2.11.自 1.1 以来对 1.2 的更改

# RabbitMQ 版本

Spring AMQP 现在默认使用 RabbitMQ3.1.x(但保留了与早期版本的兼容性)。对于 RabbitMQ3.1.x——联邦交换和RabbitTemplate上的immediate属性不再支持的特性,已经添加了某些异议。

# 兔子管理员

RabbitAdmin现在提供了一个选项,可以在声明失败时让 Exchange、Queue 和 binding 声明继续。以前,所有的声明都是在失败时停止的。通过设置ignore-declaration-exceptions,将记录此类异常(在WARN级别),但会继续进行进一步的声明。这可能有用的一个例子是,当队列声明失败时,原因是一个稍微不同的ttl设置,该设置通常会阻止其他声明继续进行。

RabbitAdmin现在提供了一个名为getQueueProperties()的附加方法。你可以使用它来确定代理上是否存在队列(对于不存在的队列,返回null)。此外,它返回队列中当前消息的数量以及当前消费者的数量。

# 兔子模板

以前,当…​sendAndReceive()方法与固定的应答队列一起使用时,将使用两个自定义标头来进行相关数据以及保留和恢复应答队列信息。在此版本中,默认情况下使用标准消息属性(correlationId),尽管你可以指定要使用的自定义属性。此外,嵌套的replyTo信息现在被保留在模板内部,而不是使用自定义标头。

immediate属性已弃用。在使用 RabbitMQ3.0.x 或更高版本时,不能设置此属性。

# JSON 消息转换器

现在提供了一个 Jackson2.xMessageConverter,以及使用 Jackson1.x 的现有转换器。

# 队列和其他项的自动声明

以前,在声明队列、交换和绑定时,你无法定义声明使用哪个连接工厂。每个RabbitAdmin通过使用其连接声明所有组件。

从这个版本开始,你现在可以将声明限制为特定的RabbitAdmin实例。见有条件声明

# AMQP Remoting

现在提供了用于使用 Spring 远程技术的设施,使用 AMQP 作为 RPC 调用的传输。有关更多信息,请参见Spring Remoting with AMQP

# 请求心跳

一些用户要求在 Spring AMQPCachingConnectionFactory上公开底层客户机连接工厂的requestedHeartBeats属性。这是现在可用的。在此之前,有必要将 AMQP 客户端工厂配置为单独的 Bean,并在CachingConnectionFactory中提供对它的引用。

# a.2.12.自 1.0 以来对 1.1 的更改

# 一般

Spring-AMQP 现在是用 Gradle 构建的。

添加对 Publisher 确认和返回的支持。

添加对 HA 队列和代理故障转移的支持。

增加了对死信交换和死信队列的支持。

# AMQP log4j Appender

添加一个选项,以支持将消息 ID 添加到已记录的消息中。

添加一个选项,允许在将String转换为byte[]时使用Charset名称的规范。