4。参考信息

此部分参考文档详细介绍了组成 Spring AMQP 的各个组件。 主章 讲解了开发 AMQP 应用程序的核心类。 本部分还包括关于 示例应用程序 的章节。spring-doc.cadn.net.cn

4.1. 使用Spring AMQP

本章探讨了开发使用 Spring AMQP 的应用程序所必需的接口和类。spring-doc.cadn.net.cn

4.1.1. AMQP 抽象层

Spring AMQP 包含两个模块(每个模块在分发包中由一个 JAR 文件表示):spring-amqpspring-rabbit。'spring-amqp' 模块包含 org.springframework.amqp.core 包。在该包中,您可以找到代表核心 AMQP “模型”的类。我们的目标是提供通用抽象,这些抽象不依赖于任何特定的 AMQP 代理实现或客户端库。终端用户代码可更易于跨提供商实现进行移植,因为它只需针对抽象层进行开发即可。这些抽象随后由面向具体代理的模块实现,例如 'spring-rabbit'。目前仅提供 RabbitMQ 实现。然而,这些抽象已在 .NET 中通过 Apache Qpid 和 RabbitMQ 进行了验证。由于 AMQP 在协议层面运行,原则上您可使用 RabbitMQ 客户端与任何支持相同协议版本的代理配合使用,但目前我们并未对其他代理进行测试。spring-doc.cadn.net.cn

本概述假定你已经熟悉AMQP规范的基本知识。如果没有,看看列出的资源在其他资源spring-doc.cadn.net.cn

Message

0-9-1 AMQP 规范并未定义 Message 类或接口。相反,当执行诸如 basicPublish() 的操作时,内容以字节数组参数的形式传递,而附加属性则作为独立参数传入。Spring AMQP 将 Message 类定义为更通用的 AMQP 领域模型表示的一部分。Message 类的目的是将主体和属性封装到单一实例中,从而使 API 更加简洁。以下示例展示了 Message 类的定义:spring-doc.cadn.net.cn

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' 扩展这些属性。spring-doc.cadn.net.cn

从版本开始1.5.7, 1.6.11, 1.7.4,和2.0.0, 如果消息体是序列化后的SerializableJava 对象在执行时不再默认进行反序列化(deserialized)。toString()操作(例如在日志消息中)。<br/>这是为了防止不安全的反序列化。<br/>默认情况下,仅允许java.utiljava.lang类被反序列化。要恢复到之前的行为,您可以调用并添加允许的类/包模式。Message.addAllowedListPatterns(…​)一个简单的通配符是受支持的,例如 com.something., *.MyClass无法反序列化的主体以以下形式表示:byte[<size>]在日志消息中。
交换

接口 Exchange 表示一个 AMQP 交换机(Exchange),这是消息生产者发送消息的目标。
每个代理(Broker)的虚拟主机内的每个交换机都具有唯一的名称以及若干其他属性。
以下示例展示了 Exchange 接口:spring-doc.cadn.net.cn

public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}

如您所见,还用常量表示为“type”的“0”也具有类型。基本类型有:“2”,“3”,“4”,和“5”。在核心包中,您可以找到为每个这些类型实现的“6”接口。它们处理到队列的绑定的方式在这些“7”类型之间存在差异。例如,“8”交换机可以让队列被固定的路由键(通常是队列的名称)绑定。 "9"交换机支持带有通配符“*”和“#”的绑定,分别表示“ exactly-one ”和“ zero-or-more ”。 "10"交换机通过不考虑任何路由键即可将消息发布到绑定的所有队列。 有关这些和其他交换器类型的更多信息,请参阅“11”。spring-doc.cadn.net.cn

AMQP 规范还要求任何代理提供一个“默认”没有名字的 direct 交换。

spring-doc.cadn.net.cn

所有声明的队列都与该默认交换绑定,使用它们的名字作为路由键。spring-doc.cadn.net.cn

您可以在 2 中了解更多关于默认交换在 Spring AMQP 中的用法。spring-doc.cadn.net.cn

队列

Queue 表示消息消费者从中接收消息的组件。与各种 Exchange 类似,我们的实现旨在作为此核心 AMQP 类型的抽象表示。以下列表显示了 Queue 类:spring-doc.cadn.net.cn

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

}

请注意,构造函数接受队列名称。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

根据具体实现,管理模板可能会提供用于生成唯一命名队列的方法。spring-doc.cadn.net.cn

此类队列在作为“回复地址”或在其他临时场景中非常有用。spring-doc.cadn.net.cn

因此,自动生成队列的‘exclusive’(独占)和‘autoDelete’(自动删除)属性都将被设置为‘true’。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

配置代理中关于使用命名空间支持声明队列的部分,包括队列参数。spring-doc.cadn.net.cn

绑定

鉴于生产者向交换机发送消息,而消费者从队列接收消息,将队列与交换机连接起来的绑定(bindings)对于通过消息传递实现生产者与消费者之间的连接至关重要。在 Spring AMQP 中,我们定义了一个 Binding 类来表示这些连接。本节将回顾将队列绑定到交换机的基本选项。spring-doc.cadn.net.cn

您可以将队列绑定到一个 DirectExchange,并使用固定的路由键,如下例所示:spring-doc.cadn.net.cn

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

您可以将队列与一个 TopicExchange 绑定,使用路由模式,如下例所示:spring-doc.cadn.net.cn

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

您可以将队列绑定到一个 FanoutExchange,而无需路由键,如下例所示:spring-doc.cadn.net.cn

new Binding(someQueue, someFanoutExchange);

我们还提供一个 BindingBuilder,以方便实现“流畅式 API”风格,如下例所示:spring-doc.cadn.net.cn

Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
为了清晰起见,前面的示例展示了 BindingBuilder 类,但这种风格在使用静态导入 'bind()' 方法时效果很好。

By itself, an instance of the Binding class only holds the data about a connection. In other words, it is not an "active" component. However, as you will see later in Configuring the Broker, the AmqpAdmin class can use Binding instances to actually trigger the binding actions on the broker. Also, as you can see in that same section, you can define the Binding instances by using Spring's @Bean annotations within @Configuration classes. There is also a convenient base class that further simplifies that approach for generating AMQP-related bean definitions and recognizes the queues, exchanges, and bindings so that they are all declared on the AMQP broker upon application startup.spring-doc.cadn.net.cn

第 0 个也是在核心包中定义的。作为实际 AMQP 消息传递的主要组件之一,它在自己的章节(请参阅AmqpTemplate)中进行了详细讨论。spring-doc.cadn.net.cn

4.1.2. 连接与资源管理

尽管我们在前一节中描述的 AMQP 模型是通用的,适用于所有实现,但在资源管理方面,具体细节则取决于消息代理(broker)的实现。因此,在本节中,我们仅关注存在于我们的“spring-rabbit”模块中的代码,因为此时 RabbitMQ 是唯一受支持的实现。spring-doc.cadn.net.cn

管理与 RabbitMQ 代理连接的核心组件是 ConnectionFactory 接口。 ConnectionFactory 实现的职责是提供一个 org.springframework.amqp.rabbit.connection.Connection 的实例,该实例是对 com.rabbitmq.client.Connection 的封装。spring-doc.cadn.net.cn

选择连接工厂

有三种连接工厂可供选择spring-doc.cadn.net.cn

前两个是在版本 2.3 中添加的。spring-doc.cadn.net.cn

对于大多数用例,应使用CachingConnectionFactory。此值是默认设置。 如果要确保消息严格有序且不需要使用范围操作,则可以使用ThreadChannelConnectionFactoryPooledChannelConnectionFactoryCachingConnectionFactory相似,因为它使用单个连接和一组通道。 其实现较简单,但不支持相关的发布确认机制。spring-doc.cadn.net.cn

所有三种工厂均支持简单的发布者确认。spring-doc.cadn.net.cn

当配置一个RabbitTemplate使用单独连接时,从版本2.3.2开始,您可以现在,可以配置发布连接工厂为不同的类型。默认情况下,发布工厂与主工厂相同 类型和设置在主工厂上的任何属性也传播到发布工厂。spring-doc.cadn.net.cn

PooledChannelConnectionFactory

该工厂管理一个单一连接和两个通道池,基于 Apache Pool2。一个池用于事务性通道,另一个用于非事务性通道。这些池采用默认配置的 GenericObjectPool;提供了回调函数以配置这些池;更多详情请参阅 Apache 文档。spring-doc.cadn.net.cn

Apache commons-pool2 jar 必须在类路径中才能使用此工厂。spring-doc.cadn.net.cn

@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

此工厂管理单个连接以及两个代码为零的通道,其中一个用于事务性通道,另一个用于非事务性通道。此工厂确保同一线程上的所有操作使用相同的通道(只要它保持打开状态)。这有助于实现严格的消息顺序而不需使用范围操作。如果应用程序使用许多短生命周期的线程,则必须调用工厂的代码二来释放通道资源,否则可能会导致内存泄漏。从版本 2.3.7 开始,线程可以将其通道转移到另一个线程。有关更多信息,请参阅多线程环境中的严格消息排序。此页面显示了一个使用 JMS 的典型应用程序的类图。spring-doc.cadn.net.cn

CachingConnectionFactory

提供的第三种实现是 CachingConnectionFactory,它默认建立一个单连接代理,该代理可由应用程序共享。由于与 AMQP 进行消息通信的“工作单元”实际上是一个“通道”(在某些方面,这类似于 JMS 中连接与会话之间的关系),因此可以共享连接。连接实例提供了一个 createChannel 方法。CachingConnectionFactory 的实现支持缓存这些通道,并根据通道是否为事务性维护独立的缓存。在创建 CachingConnectionFactory 实例时,您可以通过构造函数提供 'hostname'。您还应提供 'username' 和 'password' 属性。要配置通道缓存的大小(默认为 25),您可以调用 setChannelCacheSize() 方法。spring-doc.cadn.net.cn

从版本 1.3 开始,您可以配置 CachingConnectionFactory 来缓存连接以及仅缓存通道。在这种情况下,每次调用 createConnection() 都会创建一个新的连接(或从缓存中获取一个空闲连接)。关闭连接时,会将其返回到缓存中(前提是缓存大小未达到上限)。在这些连接上创建的通道也会被缓存。使用独立连接在某些环境中可能很有用,例如:在高可用性(HA)集群中消费数据,结合负载均衡器以连接到不同的集群成员等。要缓存连接,请将 cacheMode 设置为 CacheMode.CONNECTIONspring-doc.cadn.net.cn

这并不限制连接的数量。相反,它指定了允许保持空闲状态的打开连接的最大数量。

从版本 1.5.5 开始,新增了一个名为 connectionLimit 的属性。
当设置此属性时,它会限制允许的总连接数。
当设置该限制后,若达到上限,则使用 channelCheckoutTimeLimit 来等待连接变为空闲状态。
如果超时,则抛出 AmqpTimeoutExceptionspring-doc.cadn.net.cn

当缓存模式设置为 0 时,不支持自动声明队列和其他内容(请参阅自动声明交换器、队列和绑定)。spring-doc.cadn.net.cn

此外,在本文撰写时,amqp-client 库默认为每个连接创建一个固定线程池(默认大小:Runtime.getRuntime().availableProcessors() * 2 个线程)。
当使用大量连接时,您应考虑在 CachingConnectionFactory 上设置自定义的 executor
之后,所有连接均可共用同一执行器,其线程也可被共享。
该执行器的线程池应为无限制的,或根据预期用途进行适当配置(通常情况下,每个连接至少需要一个线程)。
若每个连接上创建多个通道,则线程池大小将影响并发性,因此采用可变的(或简单的缓存)线程池执行器最为合适。spring-doc.cadn.net.cn

重要的是要理解,缓存大小(默认情况下)并非一个限制,而仅仅是可缓存的通道数量。</p><p>例如,若缓存大小为10,则实际可使用的通道数量可以超过10个。</p><p>如果使用了超过10个通道,并且所有这些通道都返回到缓存中,则最多有10个通道进入缓存;其余的通道则会被物理关闭。spring-doc.cadn.net.cn

从版本 1.6 开始,默认通道缓存大小已从 1 增加到 25。在高流量、多线程环境中,较小的缓存意味着通道会以较高的频率被创建和关闭。增大默认缓存大小可避免此类开销。您应通过 RabbitMQ 管理界面监控正在使用的通道,并在观察到大量通道被创建和关闭时,考虑进一步增加缓存大小。该缓存仅按需增长(以适应应用程序的并发需求),因此此更改不会影响现有低流量应用。spring-doc.cadn.net.cn

从版本 1.4.2 开始,CachingConnectionFactory 具有一个名为 channelCheckoutTimeout 的属性。当该属性的值大于零时,channelCacheSize 就成为连接上可创建通道数量的限制。如果达到该限制,调用线程将阻塞,直到有通道可用或此超时到期为止,此时将抛出 AmqpTimeoutExceptionspring-doc.cadn.net.cn

框架内使用的通道(例如,RabbitTemplate)会被可靠地返回到缓存中。如果您在框架外部创建通道(例如,通过直接访问连接并调用 createChannel()),则必须可靠地将其返回(例如,通过关闭操作),可能需在 finally 块中完成,以避免耗尽通道。

以下示例展示了如何创建一个新的 connectionspring-doc.cadn.net.cn

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

Connection connection = connectionFactory.createConnection();

在使用 XML 时,配置可能如下例所示:spring-doc.cadn.net.cn

<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 实现。

spring-doc.cadn.net.cn

该实现比 CachingConnectionFactory 更简单,因为它不缓存通道;但由于其性能和容错性较差,因此不适用于实际使用场景,仅限于简单的测试用途。spring-doc.cadn.net.cn

如果您因某种原因需要为某项功能自定义实现 ConnectionFactory,那么 AbstractConnectionFactory 基类可能为您提供一个良好的起点。spring-doc.cadn.net.cn

A ConnectionFactory 可以通过使用 rabbit 命名空间快速便捷地创建,如下所示:spring-doc.cadn.net.cn

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

在大多数情况下,这种做法更可取,因为框架可以为您选择最佳默认值。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

创建的实例是一个 CachingConnectionFactoryspring-doc.cadn.net.cn

请记住,通道的默认缓存大小为 25。spring-doc.cadn.net.cn

如果您希望缓存更多通道,请通过设置 'channelCacheSize' 属性来设定一个更大的值。spring-doc.cadn.net.cn

在 XML 中,其形式如下:spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

<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' 属性,如下所示:spring-doc.cadn.net.cn

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

默认缓存模式为 CHANNEL,但您可以将其配置为缓存连接。
在以下示例中,我们使用 connection-cache-sizespring-doc.cadn.net.cn

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

您可以使用命名空间提供主机和端口属性,如下所示:spring-doc.cadn.net.cn

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

或者,如果在集群环境中运行,可以使用 <code>addresses</code> 属性,如下所示:spring-doc.cadn.net.cn

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

请参阅 连接到集群 以获取有关 address-shuffle-mode 的信息。spring-doc.cadn.net.cn

以下是一个自定义线程工厂的示例,该工厂将线程名称前缀为 rabbitmq-spring-doc.cadn.net.cn

<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>
地址解析器

从版本 2.1.15 开始,现在可以使用 AddressResolver 来解析连接地址(或地址列表)。这将覆盖 addresseshost/port 属性的任何设置。spring-doc.cadn.net.cn

命名连接

从版本 1.7 开始,为注入到 AbstractionConnectionFactory 中提供了一个 ConnectionNameStrategyspring-doc.cadn.net.cn

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

参数 ConnectionFactory 可用于通过某些逻辑区分目标连接名称。
默认情况下,使用 AbstractConnectionFactorybeanName(一个表示对象的十六进制字符串)和内部计数器来生成 connection_name
命名空间组件 <rabbit:connection-factory> 还会提供 connection-name-strategy 属性。spring-doc.cadn.net.cn

一个 SimplePropertyValueConnectionNameStrategy 的实现将连接名称设置为应用程序属性。您可将其声明为 @Bean,并注入到连接工厂中,如下例所示:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

当使用 Spring Boot 及其自动配置的连接工厂时,您只需声明 ConnectionNameStrategy @Bean。Boot 自动检测该 Bean 并将其接入工厂中。
阻止的连接和资源限制

连接可能因与 内存警报 对应的代理(broker)阻止了交互而被阻断。从 2.0 版本开始,org.springframework.amqp.rabbit.connection.Connection 可以通过提供 com.rabbitmq.client.BlockedListener 个实例来接收连接被阻断和解除阻断事件的通知。此外,AbstractConnectionFactory 会通过其内部的 BlockedListener 实现分别发出 ConnectionBlockedEventConnectionUnblockedEvent。这些功能使您能够为代理端出现的问题提供应用程序逻辑,并(例如)采取一些纠正措施。spring-doc.cadn.net.cn

当应用程序配置为单个 CachingConnectionFactory 时(这正是 Spring Boot 自动配置的默认设置),一旦连接被 Broker 阻塞,应用程序将停止工作。当连接被 Broker 阻塞时,其所有客户端均会停止工作。

spring-doc.cadn.net.cn

如果我们在一个应用程序中同时拥有生产者和消费者,就可能在生产者阻塞连接(因为 Broker 上已无可用资源)而消费者无法释放这些资源(因为连接已被阻塞)的情况下陷入死锁。spring-doc.cadn.net.cn

为缓解此问题,我们建议再设置一个独立的 CachingConnectionFactory 实例,采用相同的选项——一个专门用于生产者,另一个专门用于消费者。spring-doc.cadn.net.cn

对于在消费者线程上执行的事务性生产者而言,单独设置 CachingConnectionFactory 是不可行的,因为它们需要复用与消费者事务关联的 Channelspring-doc.cadn.net.cn

从版本 2.0.2 开始,RabbitTemplate 具有配置选项,除非正在使用事务,否则会自动使用第二个连接工厂。有关更多信息,请参阅使用单独的连接ConnectionNameStrategy 作为发布者连接的策略与主策略相同,在调用该方法的结果中附加了 .publisherspring-doc.cadn.net.cn

从版本 1.7.7 开始,提供了一个 AmqpResourceNotAvailableException 异常,当 SimpleConnection.createChannel() 无法创建 Channel 时(例如,由于 channelMax 限制已达到,且缓存中没有可用通道)会抛出该异常。您可以在 RetryPolicy 中使用此异常,在经过一段退避时间后恢复操作。spring-doc.cadn.net.cn

配置底层客户端连接工厂

CachingConnectionFactory 使用了 Rabbit 客户端 ConnectionFactory 的一个实例。当在 CachingConnectionFactory 上设置等效属性时,会通过若干配置属性(例如 hostportuserNamepasswordrequestedHeartBeatconnectionTimeout)传递过去。要设置其他属性(例如 clientProperties),您可以定义一个 Rabbit 工厂的实例,并通过 CachingConnectionFactory 的适当构造函数提供对其的引用。在使用命名空间(如前所述)时,您需要在 connection-factory 属性中提供已配置工厂的引用。为方便起见,提供了一个工厂 Bean,以协助在 Spring 应用上下文中配置连接工厂,如 下一节 所讨论。spring-doc.cadn.net.cn

<rabbit:connection-factory
      id="connectionFactory" connection-factory="rabbitConnectionFactory"/>
4.0.x 客户端默认启用自动恢复功能。虽然与该功能兼容,但 Spring AMQP 自身也具有恢复机制,因此客户端的自动恢复功能通常并不需要。我们建议禁用 amqp-client 自动恢复,以避免在代理(broker)可用但连接尚未恢复时出现 AutoRecoverConnectionNotCurrentlyOpenException 个实例的情况。例如,当在 RabbitTemplate 中配置了 RetryTemplate,即使在集群中故障转移到另一个代理时,您也可能遇到此类异常。由于自动恢复连接是基于定时器进行恢复的,因此使用 Spring AMQP 的恢复机制可使连接更快地恢复。从版本 1.7.1 开始,Spring AMQP 默认禁用 amqp-client 自动恢复,除非您显式创建自己的 RabbitMQ 连接工厂并将其提供给 CachingConnectionFactory。由 RabbitConnectionFactoryBean 创建的 RabbitMQ ConnectionFactory 实例也默认禁用此选项。
RabbitConnectionFactoryBean和配置SSL

从版本 1.4 开始,提供了一个便捷的 RabbitConnectionFactoryBean,可通过依赖注入来方便地配置底层客户端连接工厂的 SSL 属性。其他设置器会委托给底层工厂。此前,您必须通过编程方式配置 SSL 选项。以下示例展示了如何配置一个 RabbitConnectionFactoryBeanspring-doc.cadn.net.cn

Java
@Bean
RabbitConnectionFactoryBean rabbitConnectionFactory() {
    RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean();
    factoryBean.setUseSSL(true);
    factoryBean.setSslPropertiesLocation(new ClassPathResource("secrets/rabbitSSL.properties"));
    return factoryBean;
}

@Bean
CachingConnectionFactory connectionFactory(ConnectionFactory rabbitConnectionFactory) {
    CachingConnectionFactory ccf = new CachingConnectionFactory(rabbitConnectionFactory);
    ccf.setHost("...");
    // ...
    return ccf;
}
Boot application.properties
spring.rabbitmq.ssl.enabled:true
spring.rabbitmq.ssl.keyStore=...
spring.rabbitmq.ssl.keyStoreType=jks
spring.rabbitmq.ssl.keyStorePassword=...
spring.rabbitmq.ssl.trustStore=...
spring.rabbitmq.ssl.trustStoreType=jks
spring.rabbitmq.ssl.trustStorePassword=...
spring.rabbitmq.host=...
...
XML
<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="classpath:secrets/rabbitSSL.properties"/>
</bean>

参见 RabbitMQ 文档,了解有关配置 SSL 的信息。
省略 keyStoretrustStore 配置项,即可在不验证证书的情况下通过 SSL 连接。
下一个示例展示了如何提供密钥库和信任库的配置。spring-doc.cadn.net.cn

属性 sslPropertiesLocation 是一个 Spring Resource,指向一个包含以下键值的属性文件:spring-doc.cadn.net.cn

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

代码 keyStoretruststore 是 Spring Resources 指向存储库的引用。
通常,该属性文件由操作系统保护,应用程序仅具有读取访问权限。spring-doc.cadn.net.cn

从 Spring AMQP 1.5 版本开始,您可以直接在工厂 bean 上设置这些属性。如果同时提供了离散属性和 sslPropertiesLocation,后者提供的属性将覆盖前者离散值。spring-doc.cadn.net.cn

从版本 2.0 开始,服务器证书默认进行验证,因为这样更安全。如果出于某些原因希望跳过此验证,请将工厂 Bean 的 skipServerCertificateValidation 属性设置为 true。从版本 2.1 开始,RabbitConnectionFactoryBean 现在默认调用 enableHostnameVerification()。若要恢复到以前的行为,请将 enableHostnameVerification 属性设置为 false
从版本 2.2.5 开始,工厂 Bean 将始终默认使用 TLS v1.2;此前,在某些情况下使用的是 v1.1,而在其他情况下则使用 v1.2(具体取决于其他属性)。

spring-doc.cadn.net.cn

如果您出于某种原因需要使用 v1.1,请设置 sslAlgorithm 属性: setSslAlgorithm("TLSv1.1")spring-doc.cadn.net.cn

连接到集群

要连接到集群,请在 addresses 上配置 CachingConnectionFactory 属性:spring-doc.cadn.net.cn

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

从版本 3.0 开始,底层连接工厂在建立新连接时,将尝试通过选择一个随机地址来连接到主机。要恢复之前的行为(即按从第一个到最后一个的顺序尝试连接),请将 addressShuffleMode 属性设置为 AddressShuffleMode.NONEspring-doc.cadn.net.cn

从版本 2.3 开始,添加了 INORDER 混洗模式,即在建立连接后,第一个地址会被移动到末尾。如果您希望在所有节点上从所有分片中消费消息,可以将此模式与 RabbitMQ 分片插件 配合 CacheMode.CONNECTION 和适当的并发设置一起使用。spring-doc.cadn.net.cn

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

从版本 1.3 开始,引入了 AbstractRoutingConnectionFactory。此工厂提供了一种机制,用于配置多个 ConnectionFactories 的映射,并在运行时通过某种 lookupKey 确定目标 ConnectionFactory。通常,该实现会检查线程绑定的上下文。为方便起见,Spring AMQP 提供了 SimpleRoutingConnectionFactory,它可从 SimpleResourceHolder 中获取当前线程绑定的 lookupKey。以下示例展示了如何在 XML 和 Java 中配置一个 SimpleRoutingConnectionFactoryspring-doc.cadn.net.cn

<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());
    }

}

使用后重要的是解除资源的绑定。有关更多信息,请参阅 JavaDocAbstractRoutingConnectionFactory 的相关内容。spring-doc.cadn.net.cn

从版本 1.4 开始,RabbitTemplate 支持 SpEL sendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpression 属性,这些属性会在每次 AMQP 协议交互操作(sendsendAndReceivereceivereceiveAndReply)中进行求值,从而为提供的 AbstractRoutingConnectionFactory 解析出一个 lookupKey 值。
您可以在表达式中使用 Bean 引用,例如 @vHostResolver.getVHost(#root)
对于 send 操作,待发送的消息是根求值对象。
对于 receive 操作,queueName 是根求值对象。spring-doc.cadn.net.cn

路由算法如下:如果选择器表达式为 null,或其计算结果为 null,或提供的 ConnectionFactory 不是 AbstractRoutingConnectionFactory 的实例,则一切照常进行,依赖于所提供的 ConnectionFactory 实现。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

若评估结果不是 null,但该 lookupKey 对应的目标 ConnectionFactory 不存在,且 AbstractRoutingConnectionFactory 配置为 lenientFallback = true,也会发生相同情况。spring-doc.cadn.net.cn

对于 AbstractRoutingConnectionFactory 的情况,它会根据 determineCurrentLookupKey() 回退到其 routing 实现。spring-doc.cadn.net.cn

然而,如果 lenientFallback = false 成立,则会抛出一个 IllegalStateExceptionspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

命名空间支持还为 <rabbit:template> 组件提供了 send-connection-factory-selector-expressionreceive-connection-factory-selector-expression 属性。spring-doc.cadn.net.cn

此外,从版本 1.4 开始,您可以在监听器容器中配置一个路由连接工厂。在这种情况下,队列名称列表用作查找键。例如,如果您将容器配置为 setQueueNames("thing1", "thing2"),则查找键为 [thing1,thing]"(注意,该键中没有空格)。spring-doc.cadn.net.cn

从版本 1.6.9 开始,您可以通过在监听器容器中使用 setLookupKeyQualifier 作为查找键的限定符来添加限定符。这样便可实现例如:监听具有相同名称但位于不同虚拟主机中的队列(此时您可为每个虚拟主机配置一个连接工厂)。spring-doc.cadn.net.cn

例如,使用查找键限定符 thing1 以及一个监听队列 thing2 的容器时,您可以将目标连接工厂注册的查找键设置为 thing1[thing2]spring-doc.cadn.net.cn

目标(如果提供则为默认值)连接工厂必须具有相同的发布确认和返回设置。请参阅 发布确认和返回

从版本 2.4.4 开始,此验证可以被禁用。如果您的场景中确认值与返回值需要不相等,您可以使用 AbstractRoutingConnectionFactory#setConsistentConfirmsReturns 来关闭该验证。注意:首先添加到 AbstractRoutingConnectionFactory 的连接工厂将决定 confirmsreturns 的通用值。spring-doc.cadn.net.cn

如果您的场景中某些消息需要检查确认/返回,而其他消息则不需要,这可能会很有用。例如:spring-doc.cadn.net.cn

@Bean
public RabbitTemplate rabbitTemplate() {
    final com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory();
    cf.setHost("localhost");
    cf.setPort(5672);

    CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(cf);
    cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);

    PooledChannelConnectionFactory pooledChannelConnectionFactory = new PooledChannelConnectionFactory(cf);

    final Map<Object, ConnectionFactory> connectionFactoryMap = new HashMap<>(2);
    connectionFactoryMap.put("true", cachingConnectionFactory);
    connectionFactoryMap.put("false", pooledChannelConnectionFactory);

    final AbstractRoutingConnectionFactory routingConnectionFactory = new SimpleRoutingConnectionFactory();
    routingConnectionFactory.setConsistentConfirmsReturns(false);
    routingConnectionFactory.setDefaultTargetConnectionFactory(pooledChannelConnectionFactory);
    routingConnectionFactory.setTargetConnectionFactories(connectionFactoryMap);

    final RabbitTemplate rabbitTemplate = new RabbitTemplate(routingConnectionFactory);

    final Expression sendExpression = new SpelExpressionParser().parseExpression(
            "messageProperties.headers['x-use-publisher-confirms'] ?: false");
    rabbitTemplate.setSendConnectionFactorySelectorExpression(sendExpression);
}

通过这种方式,带有标题x-use-publisher-confirms: true的消息将通过缓存连接发送,并且您可以确保消息的传递。有关确保消息传递的更多信息,请参阅发布确认和返回spring-doc.cadn.net.cn

队列亲和性与LocalizedQueueConnectionFactory

在集群中使用高可用(HA)队列时,为了获得最佳性能,您可能希望连接到实际的代理服务器(broker),该服务器上存放着主队列。CachingConnectionFactory 可以配置多个代理服务器地址。这是为了实现故障转移,客户端会按照配置的 AddressShuffleMode 顺序尝试连接。LocalizedQueueConnectionFactory 利用管理插件提供的 REST API 来确定哪个节点是该队列的主节点(lead node),然后创建(或从缓存中检索)一个 CachingConnectionFactory,使其仅连接到该节点。如果连接失败,则重新确定新的主节点,并由消费者连接至该节点。LocalizedQueueConnectionFactory 配置了一个默认的连接工厂,用于在无法确定队列物理位置时作为备用方案,此时它将正常连接到集群。spring-doc.cadn.net.cn

The LocalizedQueueConnectionFactory is a RoutingConnectionFactory and the SimpleMessageListenerContainer uses the queue names as the lookup key as discussed in Routing Connection Factory above.spring-doc.cadn.net.cn

出于这个原因(使用队列名称进行查找),LocalizedQueueConnectionFactory 只能在容器被配置为监听单个队列时使用。
每个节点上都必须启用 RabbitMQ 管理插件。
此连接工厂旨在用于长期连接,例如由 SimpleMessageListenerContainer 使用的连接。它不适用于短期连接使用(例如与 RabbitTemplate 配合使用),因为每次建立连接前调用 REST API 会带来额外开销。此外,对于发布操作,队列未知,且消息仍会被发布到所有集群成员,因此查找节点的逻辑价值有限。

以下示例配置展示了如何配置工厂:spring-doc.cadn.net.cn

@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 的数组。这些参数是按位置排列的:当容器尝试连接到队列时,它会使用管理 API 来确定该队列的主节点,并连接到与该节点在相同数组位置上的地址。spring-doc.cadn.net.cn

从版本 3.0 开始,RabbitMQ 的 http-client 已不再用于访问 Rest API。

spring-doc.cadn.net.cn

相反,如果类路径中存在 spring-webflux,则默认使用 Spring WebFlux 中的 WebClient;否则将使用 RestTemplatespring-doc.cadn.net.cn

WebFlux 添加到类路径中:spring-doc.cadn.net.cn

示例1。Maven
<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
</dependency>
例子 2. Gradle
compile 'org.springframework.amqp:spring-rabbit'

您还可以通过实现 LocalizedQueueConnectionFactory.NodeLocator 并重写其 createClient, ``restCall 方法,以及可选地重写 close 方法,来使用其他 REST 技术。spring-doc.cadn.net.cn

lqcf.setNodeLocator(new NodeLocator<MyClient>() {

    @Override
    public MyClient createClient(String userName, String password) {
        ...
    }

    @Override
    public HashMap<String, Object> restCall(MyClient client, URI uri) {
        ...
    });

});

该框架提供了 WebFluxNodeLocatorRestTemplateNodeLocator,默认值如上所述。spring-doc.cadn.net.cn

发布者确认和返回

通过设置 CachingConnectionFactory 属性 publisherConfirmTypeConfirmType.CORRELATED,并将 publisherReturns 属性设为 'true',可支持已确认(带关联)并返回的消息。spring-doc.cadn.net.cn

当设置这些选项时,工厂创建的 0 个实例将被包装在 1 中,该包装用于促进回调机制。当获取此类通道时,客户端可向 3 注册一个 2。4 实现包含将确认或返回路由至相应监听器的逻辑。这些功能将在以下各节中进一步解释。spring-doc.cadn.net.cn

另请参阅相关发布者确认和返回以及作用域操作中的simplePublisherConfirmsspring-doc.cadn.net.cn

有关更多背景信息,请参阅 RabbitMQ 团队撰写的博客文章《引入发布者确认》。
连接和通道监听器

连接工厂支持注册 ConnectionListenerChannelListener 实现。
这允许您接收有关连接和通道相关事件的通知。
ConnectionListener 用于在建立连接时执行声明 - 请参阅自动声明交换器、队列和绑定以了解更多信息)。
以下清单显示了 ConnectionListener 接口定义:
spring-doc.cadn.net.cn

@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 接口的定义:spring-doc.cadn.net.cn

@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}

有关何时可能需要注册ChannelListener的场景,请参阅发布是异步的——如何检测成功和失败spring-doc.cadn.net.cn

日志通道关闭事件

版本 1.5 引入了一种机制,使用户能够控制日志级别。spring-doc.cadn.net.cn

CachingConnectionFactory 使用默认策略记录通道关闭,具体如下:spring-doc.cadn.net.cn

要修改此行为,您可以将自定义的 ConditionalExceptionLogger 注入到 CachingConnectionFactorycloseExceptionLogger 属性中。spring-doc.cadn.net.cn

运行时缓存属性

从版本 1.6 开始,CachingConnectionFactory 现在通过 getCacheProperties() 方法提供缓存统计信息。这些统计信息可用于调整缓存,以优化其在生产环境中的性能。例如,高水位线可用于判断是否应增加缓存大小;若其等于缓存大小,您可能需要考虑进一步扩大缓存容量。以下表格描述了 CacheMode.CHANNEL 属性:spring-doc.cadn.net.cn

表 1. CacheMode.CHANNEL 的缓存属性
属性 含义
connectionName

ConnectionNameStrategy 生成的连接名称。spring-doc.cadn.net.cn

channelCacheSize

当前配置的最大空闲通道数。spring-doc.cadn.net.cn

localPort

连接的本地端口(如果可用)。<br/>此信息可用于与 RabbitMQ 管理界面中的连接和通道进行关联。spring-doc.cadn.net.cn

idleChannelsTx

当前空闲(缓存)的事务通道数量。spring-doc.cadn.net.cn

idleChannelsNotTx

当前空闲(缓存)的非事务性通道数量。spring-doc.cadn.net.cn

idleChannelsTxHighWater

已缓存的、当前处于空闲状态(闲置)的最大事务通道数量。spring-doc.cadn.net.cn

idleChannelsNotTxHighWater

非事务性通道的最大数量已同时处于空闲(缓存)状态。spring-doc.cadn.net.cn

以下表格描述了 CacheMode.CONNECTION 属性:spring-doc.cadn.net.cn

表2. CacheMode.CONNECTION 的缓存属性
属性 含义
connectionName:<localPort>

ConnectionNameStrategy 生成的连接名称。spring-doc.cadn.net.cn

openConnections

表示与代理服务器连接的连接对象的数量。spring-doc.cadn.net.cn

channelCacheSize

当前配置的最大空闲通道数。spring-doc.cadn.net.cn

connectionCacheSize

当前配置的最大空闲连接数。spring-doc.cadn.net.cn

idleConnections

当前空闲的连接数。spring-doc.cadn.net.cn

idleConnectionsHighWater

当前空闲的连接数最大值。spring-doc.cadn.net.cn

idleChannelsTx:<localPort>

当前为此连接处于空闲(缓存)状态的事务通道数量。您可使用属性名称中的 localPort 部分与 RabbitMQ 管理界面中的连接和通道进行关联。spring-doc.cadn.net.cn

idleChannelsNotTx:<localPort>

当前为此连接空闲(缓存)的非事务性通道数量。localPort 部分为属性名称,可用于与 RabbitMQ 管理界面中的连接和通道进行关联。spring-doc.cadn.net.cn

idleChannelsTxHighWater:<localPort>

当前处于空闲(缓存)状态的事务通道的最大数量。</p><p>属性名称中的 localPort 部分可用于与 RabbitMQ 管理界面中的连接和通道进行关联。spring-doc.cadn.net.cn

idleChannelsNotTxHighWater:<localPort>

非事务性通道的最大数量已同时处于空闲(缓存)状态。您可以使用属性名称中的 localPort 部分与 RabbitMQ 管理界面中的连接和通道进行关联。spring-doc.cadn.net.cn

属性 cacheModeCHANNELCONNECTION)也包含在内。spring-doc.cadn.net.cn

cacheStats
图 1. JVisualVM 示例
RabbitMQ 自动连接/拓扑恢复

自 Spring AMQP 的第一个版本以来,该框架在代理服务器发生故障时提供了自己的连接和通道恢复机制。
此外,如配置代理中所讨论的那样,RabbitAdmin会在重新建立连接时重新声明任何基础设施 bean(队列等)。
因此,它不依赖于现在由amqp-client库提供的自动恢复功能。
amqp-client默认启用了自动恢复功能。
这两种恢复机制之间存在一些不兼容性,因此,默认情况下 Spring 将底层 RabbitMQ connectionFactory 上的automaticRecoveryEnabled 属性设置为false
即使属性被设置为true,Spring 也会通过立即关闭任何已恢复的连接来实际禁用它。spring-doc.cadn.net.cn

默认情况下,仅在连接失败后重新声明定义为 bean 的元素(队列、交换器、绑定)。

spring-doc.cadn.net.cn

有关如何更改此行为,请参阅恢复自动删除声明spring-doc.cadn.net.cn

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

现在,CachingConnectionFactory 允许您访问底层的连接工厂,从而可以设置自定义客户端属性。
以下示例展示了如何实现此操作:spring-doc.cadn.net.cn

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

这些属性在查看连接时显示在 RabbitMQ 管理用户界面中。spring-doc.cadn.net.cn

4.1.4. AmqpTemplate

与 Spring Framework 及相关项目提供的许多其他高级抽象一样,Spring AMQP 也提供了一个“模板”,其在其中起着核心作用。定义主要操作的接口称为 AmqpTemplate。这些操作涵盖了发送和接收消息的一般行为。换句话说,它们并非任何特定实现所独有的——因此名称中包含“AMQP”。另一方面,该接口存在一些实现,这些实现与 AMQP 协议的实现相关联。与JMS不同,JMS本身是一个接口级别的API,而AMQP是一种网络协议层面的协议。该协议的实现提供了其自身的客户端库,因此每个模板接口的实现都依赖于特定的客户端库。目前,仅有一个实现:RabbitTemplate。在下面的示例中,我们经常使用 AmqpTemplate。然而,当你查看配置示例或任何涉及模板实例化或调用setter方法的代码片段时,你可以看到实现类型(例如,RabbitTemplate)。spring-doc.cadn.net.cn

如前所述,AmqpTemplate接口定义了所有基本操作来发送和接收消息。我们将分别在发送消息接收消息中探讨消息的发送和接收。spring-doc.cadn.net.cn

添加重试功能

从版本 1.3 开始,现在您可以将 RabbitTemplate 配置为使用 RetryTemplate,以帮助处理与代理(broker)连接相关的问题。有关完整信息,请参阅 spring-retry 项目。以下仅是一个示例,它使用指数退避策略和默认的 SimpleRetryPolicy,即在将异常抛给调用方之前尝试三次。spring-doc.cadn.net.cn

以下示例使用了 XML 命名空间:spring-doc.cadn.net.cn

<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 注解:spring-doc.cadn.net.cn

@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) 的第二个参数。spring-doc.cadn.net.cn

代码 RecoveryCallback 的功能 somewhat 有限,因为重试上下文仅包含 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 中。spring-doc.cadn.net.cn

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

发布消息是一种异步机制,默认情况下,RabbitMQ 会丢弃无法路由的消息。为了确保成功发布,您可以接收异步确认,如 关联的发布者确认和返回 所述。请考虑以下两种故障场景:spring-doc.cadn.net.cn

第一种情况由出版商退货涵盖,如关联出版商确认和退货中所述。spring-doc.cadn.net.cn

对于第二种情况,消息会被丢弃,且不会生成返回结果。
底层通道会因异常而关闭。
默认情况下,该异常会被记录,但您可以在 ChannelListener 中注册一个 CachingConnectionFactory,以获取此类事件的通知。
以下示例展示了如何添加一个 ConnectionListenerspring-doc.cadn.net.cn

this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

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

});

您可以检查信号的 reason 属性以确定发生的问题。spring-doc.cadn.net.cn

为了在发送线程上检测异常,您可以在 setChannelTransacted(true)RabbitTemplate,异常将在 txCommit() 处被检测到。然而,事务会显著影响性能,因此在为这一单一用例启用事务之前,请慎重考虑。spring-doc.cadn.net.cn

关联的发布者确认和返回

《0》实现的《1》支持发布者确认和返回。spring-doc.cadn.net.cn

对于返回的消息,模板的mandatory属性必须设置为true或该消息的mandatory-expression必须计算为true。此功能需要一个其publisherReturns属性设置为trueCachingConnectionFactory(参见发布者确认和返回)。通过调用setReturnsCallback(ReturnsCallback callback)注册一个RabbitTemplate.ReturnsCallback,由客户端发送返回。setReturnsCallback(ReturnsCallback callback)回调必须实现以下方法:spring-doc.cadn.net.cn

void returnedMessage(ReturnedMessage returned);

代码 ReturnedMessage 具有以下属性:spring-doc.cadn.net.cn

每个 RabbitTemplate 仅支持一个 ReturnsCallback
另请参阅 回复超时时间spring-doc.cadn.net.cn

对于发布者确认(也称为发布者确认),模板需要一个 CachingConnectionFactory,其 publisherConfirm 属性被设置为 ConfirmType.CORRELATED。确认信息由客户端通过调用 setConfirmCallback(ConfirmCallback callback) 注册一个 RabbitTemplate.ConfirmCallback 来发送给客户端。回调方法必须实现以下方法:spring-doc.cadn.net.cn

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

客户端在发送原始消息时提供的对象是 CorrelationDataack 表示为 ack(即“失败”)时为真,而为 nack(即“成功”)时为假。 对于 nack 实例,如果在生成 nack 时可获取关闭原因,则 nack 中可能包含导致 nack 的原因。例如,在向不存在的交换机发送消息时,代理会关闭通道。关闭的原因将包含在 cause 中。cause 是在 1.4 版本中新增的。spring-doc.cadn.net.cn

一个 ConfirmCallback 仅由一个 RabbitTemplate 支持。spring-doc.cadn.net.cn

当兔子模板发送操作完成时,通道将被关闭。

spring-doc.cadn.net.cn

这会阻止在连接工厂缓存已满时接收确认或返回(当缓存中有空间时,通道不会被物理关闭,确认和返回可正常进行)。spring-doc.cadn.net.cn

当缓存已满时,框架最多会延迟关闭5秒,以确保有足够时间接收确认和返回。spring-doc.cadn.net.cn

在使用确认机制时,通道会在收到最后一个确认时关闭。spring-doc.cadn.net.cn

仅使用返回机制时,通道将保持打开状态长达完整的5秒。spring-doc.cadn.net.cn

我们通常建议将连接工厂的 channelCacheSize 设置为一个足够大的值,以确保发布消息所用的通道能被返回到缓存中,而不是被关闭。spring-doc.cadn.net.cn

您可以通过使用 RabbitMQ 管理插件来监控通道使用情况。spring-doc.cadn.net.cn

如果您观察到通道被快速地打开和关闭,应考虑增大缓存大小,以减少对服务器的开销。spring-doc.cadn.net.cn

在 2.1 版本之前,启用发布者确认的通道会在收到确认前被放回缓存中。其他某个进程可能在此时获取该通道并执行某些操作,从而导致通道关闭——例如向一个不存在的交换机发送消息。这可能导致确认丢失。

spring-doc.cadn.net.cn

2.1 版本及更高版本不再在确认未完成时将通道放回缓存。spring-doc.cadn.net.cn

RabbitTemplate 在每次操作后对通道执行一次逻辑 close() 操作。spring-doc.cadn.net.cn

通常情况下,这意味着一个通道上同时仅有一个确认处于待处理状态。spring-doc.cadn.net.cn

从版本 2.2 开始,回调函数将在连接工厂的 executor 线程之一上调用。此举旨在避免在回调内部执行 Rabbit 操作时可能出现的死锁问题。在之前的版本中,回调函数直接在 amqp-client 连接 I/O 线程上被调用;如果在此线程中执行某些 RPC 操作(例如打开新通道),则会导致死锁,因为 I/O 线程会阻塞等待结果,而该结果又需要由 I/O 线程自身来处理。在那些版本中,必须在回调内部将工作(如发送消息)移交至其他线程执行。如今,由于框架已将回调调用交由执行器(executor)处理,此类操作已不再必要。
只要返回回调在60秒或更短时间内执行,仍会保持在确认(ack)之前收到返回消息的保证。</p><p>确认(confirm)将在返回回调执行完毕后或60秒后(以较早者为准)被调度发送。

对象 CorrelationData 具有一个 CompletableFuture,您可以使用它来获取结果,而无需在模板上使用 ConfirmCallback。以下示例展示了如何配置一个 CorrelationData 实例:spring-doc.cadn.net.cn

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

由于这是一个 CompletableFuture<Confirm>,您可以在准备好时 get() 结果,或使用 whenComplete() 进行异步回调。
Confirm 对象是一个简单的 JavaBean,包含两个属性:ackreason(用于 nack 实例)。
对于由代理生成的 nack 实例,该原因字段不会被填充。
但对于由框架生成的 nack 实例(例如,在存在未完成的 ack 实例时关闭连接),该字段会被填充。spring-doc.cadn.net.cn

此外,当同时启用确认和返回功能时,如果消息无法路由到任何队列,则 CorrelationData return 属性将填充返回的消息。可以保证在 Future 被设置为 ack 之前,已设置返回消息属性。CorrelationData.getReturn() 返回一个 ReturnMessage,其包含以下属性:spring-doc.cadn.net.cn

参见作用域操作,以获取一种更简单的机制来等待发布者确认。spring-doc.cadn.net.cn

作用域操作

通常,使用模板时,会从缓存中检出一个 Channel(或创建它),用于该操作,然后将其返回到缓存中以供重用。在多线程环境中,无法保证下一次操作会使用相同的通道。然而,有时您可能希望对通道的使用拥有更多控制权,并确保若干操作均在同一通道上执行。spring-doc.cadn.net.cn

从版本 2.0 开始,提供了一种名为 invoke 的新方法,该方法带有一个 OperationsCallback。在回调作用域内执行的所有操作以及对提供的 RabbitOperations 参数执行的操作均使用相同的专用 Channel,该通道将在最后被关闭(不会返回到缓存中)。如果通道是 PublisherCallbackChannel,则在所有确认均已收到后将其返回到缓存中(参见 关联的发布者确认与返回)。spring-doc.cadn.net.cn

@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}

一个可能需要此功能的例子是,如果您希望在底层的 waitForConfirms() 方法上进行操作。该方法此前未通过 Spring API 暴露,因为通道通常被缓存并共享(如前所述)。现在,RabbitTemplate 提供了 waitForConfirms(long timeout)waitForConfirmsOrDie(long timeout),它们会将调用委托给在 OperationsCallback 的作用域内使用的专用通道。这些方法仅限于该作用域内使用,原因显而易见。spring-doc.cadn.net.cn

请注意,更高层次的抽象(允许您将确认与请求关联)在其他地方提供(参见 关联发布者确认和返回)。
如果您只想等待直到代理确认已交付消息,您可以使用以下示例中所示的技术:spring-doc.cadn.net.cn

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

如果您希望在 RabbitAdmin 的作用域内,同一通道上不调用任何操作,则管理员必须使用与 invoke 操作相同的 RabbitTemplate 构建。spring-doc.cadn.net.cn

如果模板操作已经在现有事务的作用域内执行(例如,在已启用事务的监听容器线程上运行,并在已启用事务的模板上执行操作),那么前面的讨论就不再适用。在这种情况下,操作将在该通道上执行,并在该线程返回到容器时提交。在这种场景下,无需使用 invoke

以这种方式使用确认时,用于将确认与请求关联的大部分基础设施实际上并不需要(除非也启用了返回)。
从版本 2.2 开始,连接工厂支持一个名为 publisherConfirmType 的新属性。
当将其设置为 ConfirmType.SIMPLE 时,可避免相关基础设施,从而提升确认处理的效率。spring-doc.cadn.net.cn

此外,RabbitTemplate 会设置发送消息中的 publisherSequenceNumber 属性 MessageProperties。如果您希望检查(或记录或以其他方式使用)特定的确认信息,可以使用重载的 invoke 方法,如下例所示:spring-doc.cadn.net.cn

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

以下示例记录了 acknack 个实例:spring-doc.cadn.net.cn

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);
}));
作用域操作与线程绑定。多线程环境中的严格消息排序 一文讨论了多线程环境中的严格排序问题。
多线程环境中的严格消息排序

作用域操作中的讨论仅适用于在同一线程上执行的操作。spring-doc.cadn.net.cn

考虑以下情况:spring-doc.cadn.net.cn

由于 RabbitMQ 的异步特性和缓存通道的使用,无法保证始终使用同一通道,因此消息到达队列的顺序无法得到保障。(在大多数情况下,消息会按顺序到达,但出现乱序传递的概率并非为零)。为解决此用例,可使用大小为 1 的有界通道缓存(配合 channelCheckoutTimeout)来确保所有消息始终通过同一通道发布,从而保证消息顺序。若您的连接工厂还有其他用途(如消费者),则应为模板单独配置一个连接工厂,或配置模板以使用主连接工厂中嵌入的发布者连接工厂(详见 使用独立连接)。spring-doc.cadn.net.cn

这最好通过一个简单的 Spring Boot 应用程序来说明:spring-doc.cadn.net.cn

@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);
	}

}

尽管发布是在两个不同的线程上执行的,但它们都将使用同一个通道,因为缓存最多只允许一个通道。spring-doc.cadn.net.cn

从版本 2.3.7 开始,ThreadChannelConnectionFactory 支持将线程的通道(channel)转移给另一线程,方法是使用 prepareContextSwitchswitchContext 方法。
第一种方法返回一个上下文,该上下文传递给第二个线程,由其调用第二种方法。
一个线程可以绑定非事务性通道或事务性通道(或两者兼有);除非您使用两个连接工厂,否则无法单独转移它们。
示例如下:spring-doc.cadn.net.cn

@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,如果当前线程执行任何其他操作,它们都将在一个新的通道上执行。

spring-doc.cadn.net.cn

当不再需要时,务必关闭与线程绑定的通道。spring-doc.cadn.net.cn

消息集成

从版本 1.4 开始,RabbitMessagingTemplate(基于 RabbitTemplate 构建)提供了与 Spring Framework 消息抽象的集成——即,org.springframework.messaging.Message。这使您能够通过 spring-messaging Message<?> 抽象发送和接收消息。该抽象被其他 Spring 项目所使用,例如 Spring Integration 和 Spring 对 STOMP 的支持。涉及两种消息转换器:一种用于在 spring-messaging 的 Message<?> 与 Spring AMQP 的 Message 抽象之间进行转换;另一种用于在 Spring AMQP 的 Message 抽象与底层 RabbitMQ 客户端库所需的格式之间进行转换。默认情况下,消息负载由提供的 RabbitTemplate 实例的消息转换器进行转换。或者,您可以注入一个自定义的 MessagingMessageConverter 及其对应的其他负载转换器,如下例所示:spring-doc.cadn.net.cn

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

从版本 1.6 开始,模板现在支持 user-id-expression(在使用 Java 配置时为 userIdExpression)。
如果发送了一条消息,则在评估此表达式后,会设置用户 ID 属性(若尚未设置)。
评估的根对象是待发送的消息。spring-doc.cadn.net.cn

以下示例展示了如何使用 user-id-expression 属性:spring-doc.cadn.net.cn

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

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

第一个示例是字面量表达式。
第二个示例从应用上下文中的连接工厂bean中获取username属性。spring-doc.cadn.net.cn

使用独立的连接

从版本 2.0.2 开始,您可以将 usePublisherConnection 属性设置为 true,以在可能的情况下使用与监听器容器所用连接不同的连接。此举旨在避免当生产者因任何原因被阻塞时,消费者也被阻塞。连接工厂为此目的维护一个内部的第二个连接工厂;默认情况下,它与主工厂类型相同,但如果您希望为发布使用不同的工厂类型,也可以显式进行设置。如果 Rabbit 模板在由监听器容器启动的事务中运行,则无论此设置如何,均使用容器的通道。spring-doc.cadn.net.cn

通常情况下,您不应将 RabbitAdmin 与一个将此设置为 true 的模板一起使用。

spring-doc.cadn.net.cn

请使用接受连接工厂参数的 RabbitAdmin 构造函数。spring-doc.cadn.net.cn

如果您使用了另一个接受模板参数的构造函数,请确保该模板的属性设置为 falsespring-doc.cadn.net.cn

这是因为,通常管理员会用于为监听器容器声明队列。spring-doc.cadn.net.cn

如果使用了一个将该属性设置为 true 的模板,则意味着独占队列(例如 AnonymousQueue)将在与监听器容器所使用的连接不同的连接上被声明。spring-doc.cadn.net.cn

在这种情况下,这些队列将无法被容器使用。spring-doc.cadn.net.cn

4.1.5. 发送消息

发送消息时,您可以使用以下任意一种方法:spring-doc.cadn.net.cn

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 方法发送消息:spring-doc.cadn.net.cn

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

您可以将 exchange 属性设置在模板本身上,如果您计划经常或大部分时间使用该模板实例向同一交换发送消息。在这种情况下,您可以使用前述列表中的第二种方法。以下示例在功能上等同于前一个示例:spring-doc.cadn.net.cn

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

如果在模板上同时设置了 exchangeroutingKey 属性,您可以使用仅接受 Message 的方法。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

关于交换机和路由键属性的更好理解方式是:显式方法参数始终会覆盖模板的默认值。事实上,即使您没有在模板中显式设置这些属性,也始终存在默认值。在这两种情况下,默认值都是一个空的 String,但这个实际上是一个合理且明智的默认值。就路由键而言,起初并不总是必要的(例如,对于 Fanout 交换器)。此外,队列还可以与交换机绑定,且绑定键为空字符串 String。这两种情况都是合理场景,需要依赖模板的路由键属性的默认空值 String。就交换机名称而言,空字符串 String 通常被使用,因为 AMQP 规范定义了“默认交换机”没有名称。由于所有队列都自动绑定到该默认交换机(这是一个直接交换机),并使用其名称作为绑定值,因此可以使用前面列表中的第二种方法,通过默认交换机向任意队列发送简单的点对点消息。您可以将队列名称作为 routingKey 提供,方法是在运行时提供方法参数。下面的例子展示了如何做到这一点:spring-doc.cadn.net.cn

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

或者,您可以创建一个模板,该模板主要用于或 exclusively 发布到单个队列。以下示例展示了如何实现此操作:spring-doc.cadn.net.cn

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));
消息构建器API

从版本 1.3 开始,MessageBuilderMessagePropertiesBuilder 提供了一个消息构建器 API。这些方法提供了一种便捷的“流畅式”方式来创建消息或消息属性。以下示例展示了流畅式 API 的实际应用:spring-doc.cadn.net.cn

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 上定义的每个属性都可以进行设置。其他方法包括 setHeader(String key, String value)removeHeader(String key)removeHeaders()copyProperties(MessageProperties properties)。每个属性设置方法都有一个 set*IfAbsent() 变体。在存在默认初始值的情况下,该方法命名为 set*IfAbsentOrDefault()spring-doc.cadn.net.cn

提供了五种静态方法来创建初始消息构建器:spring-doc.cadn.net.cn

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() 以获取更多详细信息。
4 构建器创建的消息具有一个正文,该正文直接引用了参数的正文。

spring-doc.cadn.net.cn

参数的属性被复制到一个新的 MessageProperties 对象中。spring-doc.cadn.net.cn

5 构建器创建的消息具有一个正文,该正文是一个新数组,包含对参数正文的副本。参数的属性被复制到一个新的 MessageProperties 对象中。

提供了三种静态方法来创建一个 MessagePropertiesBuilder 实例:spring-doc.cadn.net.cn

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对象。当启用发布者确认时,在AmqpTemplate中描述的回调中会返回此对象。这使得发送方可以将确认(acknack)与已发送的消息相关联。spring-doc.cadn.net.cn

从版本 1.6.7 开始,引入了CorrelationAwareMessagePostProcessor接口,允许在消息转换后修改关联数据。
下面的例子展示了如何使用它:
spring-doc.cadn.net.cn

Message postProcessMessage(Message message, Correlation correlation);

在版本 2.0 中,此接口已过时。该方法已移到带有默认实现的方法中,该实现将调用。spring-doc.cadn.net.cn

从1.6.7版本开始,提供了一个新的回调接口CorrelationDataPostProcessor。此方法在所有MessagePostProcessor实例(包括通过send()方法提供的以及在setBeforePublishPostProcessors()中提供的)之后被调用。实现可以更新或替换提供给send()方法的相关数据(如有需要)。提供了原始的MessageCorrelationData(如有需要)。下面的例子展示了如何使用postProcess方法:spring-doc.cadn.net.cn

CorrelationData postProcess(Message message, CorrelationData correlationData);
发布者返回

当模板的mandatory属性为true时,返回的消息由AmqpTemplate中描述的回调提供。spring-doc.cadn.net.cn

从版本 1.4 开始,RabbitTemplate支持SpELmandatoryExpression属性,它将每个请求消息作为根评估对象进行评估,解析为boolean值。Bean引用,例如@myBean.isMandatory(#root),可以在表达式中使用。spring-doc.cadn.net.cn

出版社退回也可以由RabbitTemplate在发送和接收操作中内部使用。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

有关更多信息,请参阅回复超时spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

批次处理

版本 1.4.2 引入了 BatchingRabbitTemplatespring-doc.cadn.net.cn

spring-doc.cadn.net.cn

这是 RabbitTemplate 的子类,覆盖了 send 方法,该方法根据 BatchingStrategy 批处理消息。spring-doc.cadn.net.cn

只有当批处理完成时,消息才发送到 RabbitMQ。spring-doc.cadn.net.cn

下面的清单显示了 BatchingStrategy 接口定义:spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

public interface BatchingStrategy {

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

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

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

提供了一个SimpleBatchingStrategy。 它支持向单个交换机或路由密钥发送消息。 它有以下属性:spring-doc.cadn.net.cn

  • batchSize: 批量发送之前的消息数量。spring-doc.cadn.net.cn

  • bufferLimit: 批次消息的最大大小。 这会预取batchSize,如果超出则会导致发送部分批次。spring-doc.cadn.net.cn

  • timeout: A time after which a partial batch is sent when there is no new activity adding messages to the batch.spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

SimpleBatchingStrategy 格式化批次,通过在每个嵌入消息前加上一个四字节的二进制长度。这通过将 springBatchFormat 消息属性设置为 lengthHeader4 传达给接收系统。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

批量消息默认由侦听器容器自动解批(通过使用 0 消息标头)。 任何拒绝来自批的消息都会导致整个批被拒绝。

但是,请参阅@RabbitListener批量处理以获取更多信息。spring-doc.cadn.net.cn

4.1.6. 接收消息

消息接收总是比发送更复杂一些。有两种方式可以接收一个 Message。更简单的选择是通过轮询方法调用,逐个轮询获取一个 Message。而更复杂但更常见的做法是注册一个监听器,按需异步接收 Messages。接下来的两个小节中,我们将分别举例说明这两种方法。spring-doc.cadn.net.cn

轮询消费者

代码AmqpTemplate本身可用于轮询Message接收。
默认情况下,如果没有消息可用,则立即返回null
不会阻塞。
从版本1.5开始,您可以设置一个receiveTimeout(单位为毫秒),此时接收方法最多将阻塞该时长以等待消息到达。
负值表示无限期阻塞(或至少直到与代理的连接断开)。
版本1.6引入了receive方法的变体,允许在每次调用时传递超时参数。spring-doc.cadn.net.cn

由于接收操作为每条消息创建一个新的 QueueingConsumer,因此该技术在高流量环境中并不真正适用。对于这些用例,建议使用异步消费者或一个 receiveTimeout(零值)。

从版本 2.4.8 开始,当使用非零超时时间时,您可以指定传递给用于将消费者与通道关联的 basicConsume 方法的参数。例如:template.addConsumerArg("x-priority", 10)spring-doc.cadn.net.cn

有四种简单的 receive 方法可用。与发送端的 Exchange 类似,其中一种方法要求在模板本身上直接设置一个默认队列属性,另一种方法则在运行时接受一个队列参数。1.6 版本引入了变体,以接受 timeoutMillis 的形式,在每次请求的基础上覆盖 receiveTimeout。以下列表展示了这四种方法的定义:spring-doc.cadn.net.cn

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 实例;而实现类则提供了一种方式,用于自定义用于创建 MessageConverterObject:以下列表展示了这些方法:spring-doc.cadn.net.cn

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。有关更多信息,请参阅使用RabbitTemplateMessage进行转换spring-doc.cadn.net.cn

sendAndReceive 方法类似,从 1.3 版本开始,AmqpTemplate 提供了若干便捷的 receiveAndReply 方法,用于同步接收、处理并回复消息。
以下列表展示了这些方法定义:spring-doc.cadn.net.cn

<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 的实现,以执行针对接收到的消息的业务逻辑,并构建一个回复对象或消息(如需要)。
注意,ReceiveAndReplyCallback 可能返回 null
在此情况下,不会发送回复,且 receiveAndReply 的行为类似于 receive 方法。
这使得同一队列可同时用于混合类型的消息,其中部分消息可能不需要回复。spring-doc.cadn.net.cn

自动消息(请求与响应)转换仅在提供的回调不是 ReceiveAndReplyMessageCallback 的实例时应用,而 ReceiveAndReplyMessageCallback 提供了一个原始消息交换契约。spring-doc.cadn.net.cn

代码 ReplyToAddressCallback 在需要在运行时根据接收到的消息和来自 ReceiveAndReplyCallback 的回复,自定义逻辑来确定 replyTo 地址的场景中非常有用。默认情况下,会使用请求消息中的 replyTo 信息来路由回复。spring-doc.cadn.net.cn

以下列表显示了一个基于POJO的接收和回复示例:spring-doc.cadn.net.cn

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 还通过使用 @AmqpListener 注解支持带注解的监听端点,并提供了一个开放的基础架构,用于以编程方式注册端点。这是设置异步消费者最方便的方法。有关详细信息,请参阅基于注解的监听端点

预取的默认值曾为1,这可能导致高效消费者未被充分利用。从2.0版本开始,预取的默认值现已更改为250,这在大多数常见场景中应能保持消费者持续忙碌,从而提升吞吐量。spring-doc.cadn.net.cn

然而,在某些情况下,预取值应保持较低:spring-doc.cadn.net.cn

此外,对于低流量消息以及多个消费者(包括单个监听容器实例内的并发),您可能希望减少预取数量,以实现消息在各消费者之间更均衡的分发。spring-doc.cadn.net.cn

有关预取的更多背景信息,请参阅这篇关于 RabbitMQ 中消费者利用率 的文章,以及这篇关于 排队论 的文章。spring-doc.cadn.net.cn

消息监听器

对于异步 Message 接收,会涉及一个专用组件(而非 AmqpTemplate)。该组件是用于容纳一个 Message-消费回调函数的容器。我们将在本节稍后部分讨论该容器及其属性。不过,首先我们应该查看回调函数,因为这是您的应用程序代码与消息系统进行集成的地方。回调函数有几种可选方案,其中一种是实现 MessageListener 接口,如下所示的列表:spring-doc.cadn.net.cn

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

如果您在回调逻辑中依赖于 AMQP Channel 实例,您可以改用 ChannelAwareMessageListener。它看起来相似,但多了一个参数。以下列表展示了 ChannelAwareMessageListener 接口的定义:spring-doc.cadn.net.cn

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”支持。spring-doc.cadn.net.cn

版本 1.5 引入了一种更灵活的机制,用于 POJO 消息传递,即 @RabbitListener 注解。 请参阅注解驱动的监听器端点以获取更多信息。

在使用适配器时,您只需提供一个对该适配器本身应调用的实例的引用即可。</p><p>以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

您可以扩展适配器并提供getListenerMethodName()的实现,以便根据消息动态选择不同的方法。此方法有两个参数,originalMessageextractedMessage,后者是任何转换的结果。默认情况下,配置了SimpleMessageConverter。有关更多信息和其他可用转换器的信息,请参阅SimpleMessageConverterspring-doc.cadn.net.cn

从版本 1.4.2 开始,原始消息具有 consumerQueueconsumerTag 属性,可用于确定消息来自哪个队列。spring-doc.cadn.net.cn

从版本 1.5 开始,您可以配置一个消费者队列或标签到方法名的映射,以动态选择要调用的方法。如果映射中没有匹配项,则回退到默认监听器方法。默认监听器方法(如未设置)为 handleMessagespring-doc.cadn.net.cn

从版本 2.0 开始,提供了一个便捷的 FunctionalInterface
以下列表显示了 FunctionalInterface 的定义:spring-doc.cadn.net.cn

@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}

该接口通过使用 Java 8 的 Lambda 表达式,实现了对适配器的便捷配置,如下例所示:spring-doc.cadn.net.cn

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

从版本 2.2 开始,buildListenerArguments(Object) 已被弃用,取而代之的是新的 buildListenerArguments(Object, Channel, Message)。新的方法有助于监听器获取 ChannelMessage 参数以执行更多操作,例如在手动确认模式下调用 channel.basicReject(long, boolean)。以下列表展示了最基本的示例:spring-doc.cadn.net.cn

public class ExtendedListenerAdapter extends MessageListenerAdapter {

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

}

现在,如果您需要接收“channel”和“message”,可以将 ExtendedListenerAdapter 配置为与 MessageListenerAdapter 相同。监听器的参数应设置为 buildListenerArguments(Object, Channel, Message) 返回的内容,如下所示的监听器示例:spring-doc.cadn.net.cn

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

现在您已经了解了 Message 监听回调的多种选项,接下来我们可以将注意力转向容器。基本上,容器负责处理“主动”职责,从而使监听器回调能够保持被动状态。容器是一个“生命周期”组件的示例。它提供了启动和停止的方法。在配置容器时,您本质上是在 AMQP 队列与 MessageListener 实例之间建立桥梁。您必须提供对 ConnectionFactory 的引用以及队列名称或队列实例,监听器应从这些队列中消费消息。spring-doc.cadn.net.cn

在2.0版本之前,有一个监听器容器,即SimpleMessageListenerContainer。现在有了第二个容器,即DirectMessageListenerContainer。两个容器之间的区别以及选择使用哪个时可能要考虑的标准将在选择一个容器中进行描述。spring-doc.cadn.net.cn

以下列表展示了最基本的示例,该示例通过使用 SimpleMessageListenerContainer 实现:spring-doc.cadn.net.cn

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

作为一个“活跃”的组件,最常见的方式是通过bean定义来创建监听器容器,以便它能在后台运行。以下示例展示了如何使用XML实现这一点:spring-doc.cadn.net.cn

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

以下列表展示了另一种使用 XML 实现的方法:spring-doc.cadn.net.cn

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

前面的两个示例均创建了一个 DirectMessageListenerContainer(注意 type 属性——它默认为 simple)。spring-doc.cadn.net.cn

或者,您可能更倾向于使用 Java 配置,其外观与前面的代码片段类似:spring-doc.cadn.net.cn

@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);
            }
        };
    }
}
消费者优先级

从 RabbitMQ 版本 3.2 开始,代理现在支持消费者优先级(参见 在 RabbitMQ 中使用消费者优先级)。这是通过在消费者上设置 x-priority 参数来启用的。现在 SimpleMessageListenerContainer 支持设置消费者参数,如下例所示:spring-doc.cadn.net.cn

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

为了方便起见,该命名空间在 listener 元素上提供了 priority 属性,如下例所示:spring-doc.cadn.net.cn

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

从版本1.3开始,您可以在运行时修改容器监听的队列。
参见侦听器容器队列spring-doc.cadn.net.cn

auto-delete队列

当容器被配置为监听 auto-delete 个队列时,如果队列具有 x-expires 选项,或者在 Broker 上配置了 生存时间(Time-To-Live) 策略,则在容器停止时(即最后一个消费者被取消时),Broker 会自动移除该队列。在 1.3 版本之前,由于队列缺失,容器无法重启。仅当连接关闭或重新打开时,RabbitAdmin 才会自动重新声明队列等资源,而容器停止和启动并不会触发此操作。spring-doc.cadn.net.cn

从版本 1.3 开始,容器在启动期间使用 RabbitAdmin 重新声明任何缺失的队列。spring-doc.cadn.net.cn

您还可以使用条件声明(参见条件声明)结合一个auto-startup="false"管理员来推迟队列声明,直到容器启动时才进行。
以下示例展示了如何操作:spring-doc.cadn.net.cn

<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 声明,而 auto-startup="false" 使得元素不会在上下文初始化期间被声明。此外,由于相同的原因,容器也不会启动。当稍后启动容器时,它会使用对 containerAdmin 的引用以声明这些元素。spring-doc.cadn.net.cn

批处理消息

批量消息(由生产者创建)会被监听器容器自动解批处理(使用springBatchFormat消息头)。拒绝批次中的任何消息都会导致整个批次被拒绝。有关批处理的更多信息,请参阅批处理spring-doc.cadn.net.cn

从版本 2.2 开始,SimpleMessageListenerContainer 可以用于在消费者端(即生产者发送离散消息的端)创建批次。spring-doc.cadn.net.cn

将容器属性 consumerBatchEnabled 设置为启用此功能。
deBatchingEnabled 必须也为 true,这样容器才能负责处理两种类型的批处理。
consumerBatchEnabled 为 true 时实现 BatchMessageListenerChannelAwareBatchMessageListener
从版本 2.2.7 开始,SimpleMessageListenerContainerDirectMessageListenerContainer 都可以取消批次生产者创建的批次,如 List<Message> 所示。
有关如何使用此功能与 @RabbitListener 的信息,请参见@RabbitListener 使用批处理spring-doc.cadn.net.cn

消费者事件

当容器检测到监听器(消费者)发生某种类型的故障时,会发布应用程序事件。事件 ListenerContainerConsumerFailedEvent 具有以下属性:spring-doc.cadn.net.cn

  • container: 消费者遇到问题的监听容器。spring-doc.cadn.net.cn

  • reason: 失败的文本原因。spring-doc.cadn.net.cn

  • fatal: 一个布尔值,指示失败是否为致命的。
    对于非致命异常,容器会根据 recoveryIntervalrecoveryBackoff(针对 SimpleMessageListenerContainer)或 monitorInterval(针对 DirectMessageListenerContainer)尝试重启消费者。spring-doc.cadn.net.cn

  • throwable: 被捕获的 Throwablespring-doc.cadn.net.cn

这些事件可以通过实现 ApplicationListener<ListenerContainerConsumerFailedEvent> 来消费。spring-doc.cadn.net.cn

系统范围内的事件(如连接失败)由所有消费者发布,当 concurrentConsumers 大于 1 时。

如果因为某个队列正被独占使用,消费者失败时,默认情况下除了发布事件外,还会记录一个WARN日志。
要更改此日志行为,请在SimpleMessageListenerContainer实例的exclusiveConsumerExceptionLogger属性中提供自定义的ConditionalExceptionLogger
另请参阅通道关闭事件的日志记录spring-doc.cadn.net.cn

致命错误始终以 ERROR 级别记录。此级别不可修改。spring-doc.cadn.net.cn

容器生命周期的各个阶段还会发布其他事件:spring-doc.cadn.net.cn

消费者标签

您可以提供一种策略来生成消费者标签。默认情况下,消费者标签由代理(broker)生成。以下列表显示了 ConsumerTagStrategy 接口定义:spring-doc.cadn.net.cn

public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}

队列已提供,以便可(可选地)在标签中使用。spring-doc.cadn.net.cn

基于注解驱动的监听器端点

接收消息的最简单异步方式是使用带注解的监听器端点基础设施。简而言之,它允许您将一个托管 Bean 的方法暴露为 Rabbit 监听器端点。以下示例展示了如何使用 @RabbitListener 注解:spring-doc.cadn.net.cn

@Component
public class MyService {

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

}

前面示例的核心思想是,每当名为 myQueue 的队列中有消息可用时,便会相应地调用 processOrder 方法(在此情况下,以消息的负载作为参数)。spring-doc.cadn.net.cn

注解端点基础设施会为每个注解方法在后台创建一个消息监听容器,方法是使用 RabbitListenerContainerFactoryspring-doc.cadn.net.cn

在前面的示例中,myQueue 必须已存在并绑定到某个交换机。只要应用程序上下文中存在一个 RabbitAdmin,队列即可自动声明并绑定。spring-doc.cadn.net.cn

注解属性(${some.property})或SpEL表达式(#{someExpression})可以被指定为queues等。

spring-doc.cadn.net.cn

有关为什么使用SpEL而不是属性占位符的示例,请参见监听多个队列spring-doc.cadn.net.cn

以下清单展示了声明Rabbit监听器的三种方法:spring-doc.cadn.net.cn

@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(持久的)以及交换机,并使用路由键将其绑定到交换机。
在第二个示例中,会声明并绑定一个匿名(独占、自动删除)队列;队列名称由框架使用 Base64UrlNamingStrategy 创建。
您无法使用此技术声明代理命名的队列;它们需要作为 Bean 定义进行声明;参见 容器和代理命名的队列
可以提供多个 QueueBinding 条目,使侦听器监听多个队列。
在第三个示例中,如果需要,将使用属性 my.queue 获取名称的队列声明为默认绑定到默认交换机,使用队列名称作为路由键。spring-doc.cadn.net.cn

自版本 2.0 起,@Exchange 注解支持任何交换类型,包括自定义类型。有关更多信息,请参阅 AMQP 概念spring-doc.cadn.net.cn

当您需要更高级的配置时,可以使用常规 @Bean 定义。spring-doc.cadn.net.cn

注意第一个示例中交换机上的 ignoreDeclarationExceptions
这允许例如绑定到一个已存在的交换机,该交换机可能具有不同的设置(如 internal)。
默认情况下,现有交换机的属性必须匹配。spring-doc.cadn.net.cn

从版本 2.0 开始,现在您可以使用多个路由键将队列绑定到交换机,如下所示的示例:spring-doc.cadn.net.cn

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

你还可以在 @QueueBinding 注解中为队列、交换机和绑定指定参数,如下例所示:spring-doc.cadn.net.cn

@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
与所有此类声明一样,如果队列已存在,则参数必须与队列上的参数相匹配。
对于头交换(header exchange),我们将绑定参数设置为匹配那些具有 thing1 头部且值为 somevalue 的消息,并且
thing2 头部必须存在,且可取任意值。
x-match 参数表示两个条件都必须满足。spring-doc.cadn.net.cn

参数名称、值和类型可以是属性占位符(${…​})或 SpEL 表达式(#{…​})。name 必须解析为一个 Stringtype 的表达式必须解析为一个 Class 或某类的完全限定名。 value 必须解析为某种可由 DefaultConversionService 转换为指定类型(如前述示例中的 x-message-ttl)的对象。spring-doc.cadn.net.cn

如果一个名称解析为 null 或空的 String,则该 @Argument 将被忽略。spring-doc.cadn.net.cn

Meta-annotations

有时,您可能希望为多个监听器使用相同的配置。为了减少重复的配置代码,您可以使用元注解来创建自己的监听器注解。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@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 注解创建的监听器都会将一个匿名、自动删除的队列绑定到扇出交换机 metaFanout。从 2.2.3 版本开始,@AliasFor 得以支持,允许重写元注解中的属性。此外,用户自定义注解现在可以被 @Repeatable,从而允许为一个方法创建多个容器。spring-doc.cadn.net.cn

@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 类中。以下示例展示了具体操作方法:spring-doc.cadn.net.cn

@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 个实例。spring-doc.cadn.net.cn

有关在SimpleRabbitListenerContainerFactoryDirectRabbitListenerContainerFactory之间进行选择的信息,请参阅容器的选择

从版本 2.2.2 开始,您可以提供一个 ContainerCustomizer 实现(如上所示)。
这可用于在容器创建并配置完成后进一步对其进行配置;例如,您可以使用它来设置容器工厂未公开的属性。spring-doc.cadn.net.cn

版本 2.4.8 提供了 CompositeContainerCustomizer,适用于您希望应用多个自定义器的场景。spring-doc.cadn.net.cn

默认情况下,基础设施会查找名为 rabbitListenerContainerFactory 的 Bean,作为工厂用于创建消息监听容器的来源。在此情况下,并忽略 RabbitMQ 基础设施的配置,可以调用 processOrder 方法,设置核心线程池大小为 3,最大线程池大小为 10。spring-doc.cadn.net.cn

您可以为每个注解自定义监听器容器工厂,也可以通过实现 RabbitListenerConfigurer 接口来配置一个显式的默认值。仅当至少有一个端点在未指定容器工厂的情况下注册时,才需要默认配置。有关完整说明和示例,请参阅 Javadocspring-doc.cadn.net.cn

容器工厂提供了添加 MessagePostProcessor 实例的方法,这些实例在接收到消息后(调用监听器之前)以及发送回复前被应用。spring-doc.cadn.net.cn

有关回复的信息,请参阅回复管理spring-doc.cadn.net.cn

从版本 2.0.6 开始,您可以在监听器容器工厂中添加 RetryTemplateRecoveryCallback
它在发送回复时使用。
当重试次数耗尽时,会调用 RecoveryCallback
您可以使用 SendRetryContextAccessor 从上下文中获取信息。
以下示例展示了如何实现此功能:spring-doc.cadn.net.cn

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 均会被自动检测到。spring-doc.cadn.net.cn

对于 SimpleRabbitListenerContainer 个实例,您可以使用类似以下的 XML:spring-doc.cadn.net.cn

<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:spring-doc.cadn.net.cn

<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 表达式(#{…​})和属性占位符(${…​})。其含义和允许值取决于容器类型,具体如下:spring-doc.cadn.net.cn

  • 对于 DirectMessageListenerContainer,该值必须是一个整数值,用于设置容器上的 consumersPerQueue 属性。spring-doc.cadn.net.cn

  • 对于 SimpleRabbitListenerContainer,该值可以是一个整数值,用于设置容器上的 concurrentConsumers 属性;也可以采用 m-n 的形式,其中 mconcurrentConsumers 属性,nmaxConcurrentConsumers 属性。spring-doc.cadn.net.cn

无论哪种情况,此设置都会覆盖工厂上的设置。之前,如果您有需要不同并发性的监听器,则必须定义不同的容器工厂。spring-doc.cadn.net.cn

该注解还允许通过 autoStartupexecutor(自 2.2 版本起)注解属性覆盖工厂 autoStartuptaskExecutor 属性。
为每个监听器使用不同的执行器可能有助于在日志和线程转储中识别与每个监听器关联的线程。spring-doc.cadn.net.cn

版本 2.2 还添加了 ackMode 属性,该属性允许您覆盖容器工厂的 acknowledgeMode 属性。spring-doc.cadn.net.cn

@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 AMQP Message 转换为 Spring-messaging Message。当目标方法被调用时,如果需要,会将消息负载转换为方法参数类型。spring-doc.cadn.net.cn

第一步的默认值 MessageConverter 是一个 Spring AMQP SimpleMessageConverter,它负责将消息转换为 Stringjava.io.Serializable 对象。其余所有情况均保持为 byte[]。在下文讨论中,我们称其为“消息转换器”。spring-doc.cadn.net.cn

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

要更改消息转换器,可以将其作为属性添加到容器工厂 Bean 中。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

此配置了一个 Jackson2 转换器,该转换器期望存在头部信息以指导转换过程。spring-doc.cadn.net.cn

您还可以使用 ContentTypeDelegatingMessageConverter,它能够处理不同内容类型的转换。spring-doc.cadn.net.cn

从版本 2.3 开始,您可以通过在 messageConverter 属性中指定一个 bean 名称来覆盖工厂转换器。spring-doc.cadn.net.cn

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

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

这避免了为更改转换器而必须声明不同的容器工厂。spring-doc.cadn.net.cn

在大多数情况下,除非例如您希望使用自定义 ConversionService,否则无需自定义方法参数转换器。spring-doc.cadn.net.cn

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

这种类型推断仅适用于方法级别的 @RabbitListener

请参阅 Jackson2JsonMessageConverter 以获取更多信息。spring-doc.cadn.net.cn

如果您希望自定义方法参数转换器,可以按如下方式操作:spring-doc.cadn.net.cn

@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());
    }

    ...

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

从版本 2.3.7 开始,您可以添加自己的 HandlerMethodArgumentResolver 并解析自定义方法参数。您所需要做的就是实现 RabbitListenerConfigurer,并使用类 RabbitListenerEndpointRegistrar 中的方法 setCustomMethodArgumentResolvers()spring-doc.cadn.net.cn

@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 注解自动检测到的端点。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@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,但你同样可以构建自己的端点变体,以描述自定义的调用机制。spring-doc.cadn.net.cn

需要注意的是,您也可以完全跳过使用 @RabbitListener,而通过 RabbitListenerConfigurer 以编程方式注册您的端点。spring-doc.cadn.net.cn

带注解的端点方法签名

到目前为止,我们一直在端点中注入一个简单的 String,但其方法签名实际上可以非常灵活。以下示例将它重写为通过自定义头信息注入 Orderspring-doc.cadn.net.cn

@Component
public class MyService {

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

以下列表显示了可用于与监听器端点参数匹配的参数:spring-doc.cadn.net.cn

未使用注解的元素,且不属于支持的类型(即 MessageMessagePropertiesMessage<?>Channel)时,将与有效载荷匹配。您可以通过使用 @Payload 注解参数来明确此行为。您还可以通过添加额外的 @Valid 来开启验证。spring-doc.cadn.net.cn

注入 Spring 的消息抽象能力特别有用,可充分利用传输特定消息中存储的所有信息,而无需依赖于传输特定的 API。</p><p>以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

方法参数的处理由 DefaultMessageHandlerMethodFactory 提供,您可进一步自定义它以支持其他方法参数。转换和验证支持也可在该处进行自定义。spring-doc.cadn.net.cn

例如,如果我们希望在处理之前确保我们的 Order 是有效的,我们可以用 @Valid 注解负载,并配置必要的验证器,如下所示:spring-doc.cadn.net.cn

@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 验证

从版本 2.3.7 开始,现在更轻松地为验证 @RabbitListener@RabbitHandler @Payload 参数添加 Validator。现在,您只需将验证器添加到注册器本身即可。spring-doc.cadn.net.cn

@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
当使用 Spring Boot 与验证Starters时,会自动配置一个 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;
  }
}
@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 方法可用。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@Component
public class MyService {

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

}

从版本 1.5 开始,您可以通过使用属性占位符和 SpEL 将队列名称外部化。<br/>以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@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 版本之前,只能以这种方式指定单个队列。每个队列都需要一个单独的属性。spring-doc.cadn.net.cn

回复管理

现有对 MessageListenerAdapter 的支持已允许您的方法具有非 void 返回类型。在这种情况下,调用的结果将被封装为一条消息,并发送到原始消息中 ReplyToAddress 头部所指定的地址,或发送到监听器上配置的默认地址。您可以通过使用消息抽象的 @SendTo 注解来设置该默认地址。spring-doc.cadn.net.cn

假设我们的 processOrder 方法现在应返回一个 OrderStatus,我们可以按如下方式编写,以自动发送回复:spring-doc.cadn.net.cn

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

如果您需要以传输无关的方式设置额外的标头,可以返回 Message,例如以下内容:spring-doc.cadn.net.cn

@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/方法会作为回复消息的一部分提供,该信息可用于消息后处理器中,以便将信息回传给调用方:spring-doc.cadn.net.cn

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 头部设置完毕以匹配请求后被调用。spring-doc.cadn.net.cn

@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;
    };
}

从版本 3.0 开始,您可以在容器工厂上配置后处理器,而不是在注解上配置。spring-doc.cadn.net.cn

factory.setReplyPostProcessorProvider(id -> (req, resp) -> {
    resp.getMessageProperties().setHeader("myHeader", req.getMessageProperties().getHeader("myHeader"));
    return resp;
});

参数 id 是监听器 ID。spring-doc.cadn.net.cn

注解上的设置将覆盖工厂设置。spring-doc.cadn.net.cn

@SendTo 被假定为遵循 exchange/routingKey 模式的一对回复 exchangeroutingKey,其中这些部分之一可以省略。有效的取值如下:spring-doc.cadn.net.cn

  • thing1/thing2: 交换机 replyTo 和交换机 routingKey
    thing1/: 交换机 replyTo 和默认(空)交换机 routingKey
    thing2/thing2: 交换机 replyTo routingKey 和默认(空)交换机。
    / 或空值:默认交换机 replyTo 和默认 routingKeyspring-doc.cadn.net.cn

此外,您可以不使用 @SendTo 属性而直接使用 value。这种情况等同于一个空的 sendTo 模式。@SendTo 仅在传入消息不具有 replyToAddress 属性时才被使用。spring-doc.cadn.net.cn

从版本 1.5 开始,@SendTo 值可以是一个 bean 初始化的 SpEL 表达式,如下例所示:spring-doc.cadn.net.cn

@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/routingKey 的形式(如前文所述示例之前所讨论的那样)。spring-doc.cadn.net.cn

表达式 #{…​} 在初始化期间仅计算一次。

对于动态回复路由,消息发送者应包含 reply_to 消息属性,或使用后续示例之后描述的替代运行时 SpEL 表达式。spring-doc.cadn.net.cn

从版本 1.6 开始,@SendTo 可以是一个 SpEL 表达式,该表达式会在运行时针对请求和响应进行求值,如下例所示:spring-doc.cadn.net.cn

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

SpEL 表达式的运行时特性由 !{…​} 分隔符表示。该表达式的求值上下文 #root 对象具有三个属性:spring-doc.cadn.net.cn

上下文具有一个映射属性访问器、一个标准类型转换器和一个 Bean 解析器,这使得上下文中的其他 Bean 可以被引用(例如,@someBeanName.determineReplyQ(request, result))。spring-doc.cadn.net.cn

总之,#{…​} 在初始化期间仅计算一次,而 #root 对象即为应用上下文。 beans 通过其名称进行引用。!{…​} 在每次消息处理时于运行时进行计算,根对象具有前述列出的属性。beans 通过其名称进行引用,并以 @ 作为前缀。spring-doc.cadn.net.cn

从版本 2.1 开始,也支持简单的属性占位符(例如 ${some.reply.to})。对于早期版本,可以使用以下方法作为替代方案,如下例所示:spring-doc.cadn.net.cn

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

如果您正在使用一种复杂的消息转换器(例如 ContentTypeDelegatingMessageConverter),您可以通过设置监听器上的 replyContentType 属性来控制回复的内容类型。这使得转换器能够为回复选择适当的委托转换器。spring-doc.cadn.net.cn

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

默认情况下,为保持向后兼容性,任何由转换器设置的内容类型属性在转换后都会被此值覆盖。例如,SimpleMessageConverter 这类转换器会使用响应类型而非内容类型来确定所需的转换,并相应地在响应消息中设置内容类型。这可能并非期望的操作,可通过将 converterWinsContentType 属性设置为 false 来覆盖。例如,如果您返回一个包含 JSON 的 String,则 SimpleMessageConverter 会将响应中的内容类型设置为 text/plain。以下配置可确保即使使用了 SimpleMessageConverter,内容类型也能被正确设置。spring-doc.cadn.net.cn

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

这些属性(replyContentTypeconverterWinsContentType)在返回类型为 Spring AMQP Message 或 Spring Messaging Message<?> 时无效。在第一种情况下,不涉及转换;只需设置 contentType 消息属性即可。在第二种情况下,行为通过消息头进行控制:spring-doc.cadn.net.cn

@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,这样转换器设置的任何值都将被保留。spring-doc.cadn.net.cn

多方法监听器

从版本 1.5.0 开始,您可以在类级别指定 @RabbitListener 注解。结合新的 @RabbitHandler 注解,这使得单个监听器可根据传入消息的负载类型调用不同的方法。最佳描述方式是通过一个示例:spring-doc.cadn.net.cn

@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) {
        ...
    }

}

在这种情况下,如果转换后的有效负载是@RabbitHandlerThing2Cat,则调用单个Hat方法。
你应该理解,系统必须能够根据有效负载类型识别唯一的方法。
检查类型是否可分配给没有注解的单个参数或者带有@Payload注解的参数。
请注意,相同的方法签名适用,如在方法级别的@RabbitListener中讨论的(前面描述过)。spring-doc.cadn.net.cn

从版本 2.0.3 开始,可以将 @RabbitHandler 方法指定为默认方法,当其他方法均不匹配时将调用该方法。最多只能指定一个方法为默认方法。spring-doc.cadn.net.cn

@RabbitHandler 仅用于在转换后处理消息负载;如果您希望接收未转换的原始 Message 对象,则必须在方法上使用 @RabbitListener,而非在类上。
@Repeatable @RabbitListener

从版本 1.6 开始,@RabbitListener 注解被标记为 @Repeatable。这意味着该注解可以多次出现在同一被注解的元素(方法或类)上。在这种情况下,每个注解都会创建一个独立的监听器容器,每个容器都调用相同的监听器 @Bean。重复注解可与 Java 8 或更高版本一起使用。spring-doc.cadn.net.cn

代理@RabbitListener和泛型

如果您的服务旨在被代理(例如,在 @Transactional 的情况下),当接口包含泛型参数时,您应牢记一些注意事项。考虑以下示例:spring-doc.cadn.net.cn

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)。在此情况下,所有注解都必须在实现类中的目标方法上进行声明,如下例所示:spring-doc.cadn.net.cn

static class TxServiceImpl implements TxService<Foo> {

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

}
处理异常

默认情况下,如果已注解的监听器方法抛出异常,该异常将被抛回容器,消息将被重新入队、重新投递、丢弃或路由到死信交换,具体取决于容器和代理的配置。发送方不会收到任何返回结果。spring-doc.cadn.net.cn

从版本 2.0 开始,@RabbitListener 注解新增了两个属性:errorHandlerreturnExceptionsspring-doc.cadn.net.cn

这些并非默认配置。spring-doc.cadn.net.cn

您可以使用 errorHandler 来提供一个 RabbitListenerErrorHandler 实现的 Bean 名称。
此函数式接口包含一个方法,如下所示:spring-doc.cadn.net.cn

@FunctionalInterface
public interface RabbitListenerErrorHandler {

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

}

如您所见,您可以访问从容器接收到的原始消息、由消息转换器生成的 Spring 消息 Message<?> 对象,以及监听器抛出的异常(被封装在 ListenerExecutionFailedException 中)。
错误处理器既可以返回某些结果(该结果将作为回复发送),也可以抛出原始异常或新的异常(该异常将根据 returnExceptions 的设置被抛给容器或返回给发送方)。spring-doc.cadn.net.cn

returnExceptions 属性值为 true 时,会导致异常被返回给发送方。
该异常会被封装在 RemoteInvocationResult 对象中。
在发送方一侧,存在一个可用的 RemoteInvocationAwareMessageConverterAdapter,若将其配置到 RabbitTemplate 中,则会重新抛出服务器端的异常,该异常被封装在 AmqpRemoteException 中。
服务器端异常的堆栈跟踪信息通过合并服务器端与客户端的堆栈跟踪信息生成。spring-doc.cadn.net.cn

此机制通常仅与默认的 SimpleMessageConverter 配合使用,该默认值采用 Java 序列化方式。
异常通常不兼容 Jackson(即“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)失败的消息:spring-doc.cadn.net.cn

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 开始,如果发生消息转换异常,将调用错误处理器,并在 nullmessage 参数中传入该值。这允许应用程序向调用方发送某些结果,以表明已接收到格式不良的消息。此前,此类错误由容器抛出并处理。spring-doc.cadn.net.cn

容器管理

为注解创建的容器未注册到应用程序上下文中。您可以通过在 getListenerContainers() 上调用 RabbitListenerEndpointRegistry bean 来获取所有容器的集合。然后,您可以遍历此集合,例如停止或启动所有容器,或在注册表本身上调用 Lifecycle 方法,这将对每个容器执行相应操作。spring-doc.cadn.net.cn

您还可以通过使用其 id 来获取对单个容器的引用,使用 getListenerContainer(String id) —— 例如,registry.getListenerContainer("multi") 表示上述代码片段创建的容器。spring-doc.cadn.net.cn

从版本 1.5.2 开始,您可以使用 getListenerContainerIds() 获取已注册容器的 id 值。spring-doc.cadn.net.cn

从版本 1.5 开始,现在您可以将 group 分配给容器的 RabbitListener 端点。spring-doc.cadn.net.cn

默认情况下,停止容器会取消消费者,并在停止前处理所有预先获取的消息。从版本 2.4.14、3.0.6 开始,您可以将[forceStop]容器属性设置为true,以便在当前消息处理后立即停止,导致任何预先获取的消息被重新排队。例如,如果使用独占或单活跃消费者时,这非常有用。spring-doc.cadn.net.cn

@RabbitListener with Batching

接收消息批次时,通常由容器执行取消批处理操作,并且每次调用监听器时仅传递一条消息。从版本 2.2 开始,您可以配置监听器容器工厂和监听器,以便在一次调用中接收整个批次的消息,只需设置工厂的batchListener属性,并使方法参数有效负载为ListCollectionspring-doc.cadn.net.cn

@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 —— 详见下文)。实际上,去批处理操作从容器转移到了监听器适配器,而适配器则负责创建传递给监听器的列表。spring-doc.cadn.net.cn

批处理启用的工厂不能与多方法监听器一起使用。spring-doc.cadn.net.cn

此外,从版本 2.2 开始,当以一次一条的方式接收批处理消息时,最后一则消息会包含一个布尔类型头,其值设为 true。您可以通过在监听器方法中添加 @Header(AmqpHeaders.LAST_IN_BATCH) 参数(即 boolean last)来获取该头信息。该头信息由 MessageProperties.isLastInBatch() 映射而来。此外,AmqpHeaders.BATCH_SIZE 会在每条消息分片中填充批处理的大小。spring-doc.cadn.net.cn

此外,已在 SimpleMessageListenerContainer 中添加了一个新属性 consumerBatchEnabled。当该属性为 true 时,容器将创建一批消息,最多可达 batchSize;若在 receiveTimeout 时间内无新消息到达,则会发送部分批次。如果收到由生产者创建的批次,该批次将被解包并加入到消费者端的批次中;因此实际发送的消息数量可能超过 batchSize(即从代理服务器接收到的消息数量)。当 consumerBatchEnabled 为 true 时,deBatchingEnabled 必须为 true;容器工厂将强制执行此要求。spring-doc.cadn.net.cn

@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 时:spring-doc.cadn.net.cn

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

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

@RabbitListener(queues = "batch.3", containerFactory = "consumerBatchContainerFactory")
public void consumerBatch3(List<Invoice> strings) {
    ...
}
  • 第一个是使用接收到的原始未转换的 org.springframework.amqp.core.Message 进行调用。spring-doc.cadn.net.cn

  • 第二个调用时,会使用转换后的有效载荷和映射的头/属性传递 org.springframework.messaging.Message<?>spring-doc.cadn.net.cn

  • 第三个调用时使用转换后的有效载荷,但无法访问头信息/属性。spring-doc.cadn.net.cn

您还可以添加一个 Channel 参数,通常在使用 MANUAL 确认模式时使用。由于第三个示例中您无法访问 delivery_tag 属性,因此该方法用处不大。spring-doc.cadn.net.cn

Spring Boot 为 consumerBatchEnabledbatchSize 提供了配置属性,但不为 batchListener 提供。
从 3.0 版本开始,在容器工厂上将 consumerBatchEnabled 设置为 true 也会将 batchListener 设置为 true
consumerBatchEnabledtrue 时,监听器 必须 是批处理监听器。spring-doc.cadn.net.cn

从版本 3.0 开始,监听器方法可以接收 Collection<?>List<?> 个参数。spring-doc.cadn.net.cn

使用容器工厂

监听器容器工厂被引入,用于支持@RabbitListener和注册容器到RabbitListenerEndpointRegistry,如编程式端点注册中所讨论。spring-doc.cadn.net.cn

从版本 2.1 开始,它们可用于创建任何监听器容器——甚至是没有监听器的容器(例如用于 Spring Integration 中)。<br>当然,必须在启动容器之前添加监听器。spring-doc.cadn.net.cn

创建此类容器有两种方法:spring-doc.cadn.net.cn

以下示例展示了如何使用 SimpleRabbitListenerEndpoint 创建监听器容器:spring-doc.cadn.net.cn

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

以下示例展示了如何在创建后添加监听器:spring-doc.cadn.net.cn

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

在任何情况下,监听器也可以是 ChannelAwareMessageListener,因为现在它已成为 MessageListener 的子接口。spring-doc.cadn.net.cn

这些技术在您希望创建多个具有相似属性的容器,或使用Spring Boot自动配置提供的预配置容器工厂(如Spring Boot自动配置所提供的)时非常有用,或者两者兼而有之。spring-doc.cadn.net.cn

以这种方式创建的容器是普通的 @Bean 实例,并未在 RabbitListenerEndpointRegistry 中注册。
异步@RabbitListener返回类型

@RabbitListener(以及 @RabbitHandler)方法可以使用异步返回类型 CompletableFuture<?>Mono<?> 进行指定,从而允许回复异步发送。ListenableFuture<?> 已不再受支持;该功能已被 Spring 框架弃用。spring-doc.cadn.net.cn

监听器容器工厂必须配置为 AcknowledgeMode.MANUAL,以确保消费者线程不会确认消息;相反,异步完成时,将由异步操作在完成后确认或否定确认该消息。当异步结果以错误完成时,消息是否重新入队取决于所抛出的异常类型、容器配置以及容器错误处理器。默认情况下,消息将被重新入队,除非容器的 defaultRequeueRejected 属性设置为 false(默认值为 true)。如果异步结果以 AmqpRejectAndDontRequeueException 完成,则消息将不会被重新入队。如果容器的 defaultRequeueRejected 属性为 false,您可以通过将未来的异常设置为 ImmediateRequeueException 来覆盖该值,此时消息将被重新入队。如果监听器方法中发生某些异常,导致无法创建异步结果对象,则您必须捕获该异常,并返回一个适当的返回对象,以使消息被确认或重新入队。

从版本 2.2.21、2.3.13 和 2.4.1 开始,当检测到异步返回类型时,AcknowledgeMode 将自动设置为 MANUAL。此外,带有致命异常的传入消息将被单独否定确认;此前,任何先前未确认的消息也会被否定确认。spring-doc.cadn.net.cn

从版本 3.0.5 开始,@RabbitListener(以及 @RabbitHandler)方法可以使用 Kotlin suspend 进行标记,整个处理过程及可选的响应生成均在相应的 Kotlin 协程中完成。所有关于 AcknowledgeMode.MANUAL 的上述规则依然适用。org.jetbrains.kotlinx:kotlinx-coroutines-reactor 依赖项必须存在于类路径中,以支持 suspend 函数调用。spring-doc.cadn.net.cn

从版本 3.0.5 开始,如果在具有异步返回类型(包括 Kotlin 挂起函数)的监听器上配置了 RabbitListenerErrorHandler,则在发生故障后会调用错误处理器。异常处理 可了解有关此错误处理器及其用途的更多信息。spring-doc.cadn.net.cn

线程与异步消费者

许多不同的线程参与异步消费者。spring-doc.cadn.net.cn

TaskExecutor 配置的线程池中的线程在 SimpleMessageListenerContainer 通过 RabbitMQ Client 接收新消息时,用于调用 MessageListener。如果未进行配置,则使用 SimpleAsyncTaskExecutor。如果您使用了线程池执行器,则需确保线程池大小足以处理所配置的并发量。在使用 DirectMessageListenerContainer 的情况下,MessageListener 会直接在 RabbitMQ Client 线程上被调用。在此情况下,taskExecutor 将用于执行监控消费者的任务。spring-doc.cadn.net.cn

当使用默认值 SimpleAsyncTaskExecutor 时,监听器在调用的线程上,由监听器容器 beanNamethreadNamePrefix 中使用。这有利于日志分析。我们通常建议在日志追加器配置中始终包含线程名称。当通过容器上的 taskExecutor 属性明确提供一个 TaskExecutor 时,会原样使用,不做任何修改。建议您采用类似的方法为自定义 TaskExecutor Bean 定义所创建的线程命名,以帮助在日志消息中识别线程。

CachingConnectionFactory 中配置的 Executor 会在创建连接时传递给 RabbitMQ Client,并由其线程用于将新消息分发至监听器容器。
如果未进行此配置,客户端将使用一个内部线程池执行器(在撰写本文时)为每个连接分配大小为 Runtime.getRuntime().availableProcessors() * 2 的线程池。spring-doc.cadn.net.cn

如果您拥有大量工厂或正在使用 CacheMode.CONNECTION,您可能希望考虑使用一个共享的 ThreadPoolTaskExecutor,并确保其线程数量足以满足您的工作负载需求。spring-doc.cadn.net.cn

使用 DirectMessageListenerContainer 时,您需要确保连接工厂配置了一个任务执行器,该执行器具备足够的线程数,以支持所有使用该工厂的监听容器所期望的并发量。默认池大小(在撰写本文时)为 Runtime.getRuntime().availableProcessors() * 2

RabbitMQ client 使用一个 ThreadFactory 来为低级 I/O(套接字)操作创建线程。
要修改此工厂,您需要配置底层的 RabbitMQ ConnectionFactory,如 配置基础客户端连接工厂 中所述。spring-doc.cadn.net.cn

选择容器

版本 2。0 引入了 DirectMessageListenerContainer(DMLC)。先前,仅可用 SimpleMessageListenerContainer(SMLC)。SMLC 为每个消费者使用一个内部队列和一个专用线程。如果容器被配置为监听多个队列,则将使用相同的消费者线程来处理所有队列。并发由 concurrentConsumers 及其他属性进行控制。当消息从 RabbitMQ 客户端到达时,客户端线程通过队列将它们交给消费者线程。这种架构是必需的,因为在早期版本的RabbitMQ客户端中,无法实现多个并发交付。客户端的新版本采用了修订后的线程模型,现在可以支持并发。这使得可以在 DMLC 中引入该功能,即监听器现在直接在 RabbitMQ 客户端线程上调用。因此,其架构实际上比SMLC更“简单”。然而,这种方法存在一些局限性,且DMLC不支持SMLC的某些功能。此外,并发性由 consumersPerQueue(以及客户端库的线程池)进行控制。代码 concurrentConsumers 及相关属性在此容器中不可用。spring-doc.cadn.net.cn

以下功能是 SMLC 所具备但 DMLC 不具备的:spring-doc.cadn.net.cn

  • batchSize: 使用 SMLC 时,您可以设置此值以控制单个事务中传递的消息数量,或减少确认(acks)的数量,但此举可能导致在发生故障后重复传递的消息数量增加。
    (DMLC 确实具有 messagesPerAck,您可将其用于减少确认次数,与 batchSize 和 SMLC 的用法相同,但该选项不能与事务配合使用——每条消息均在独立的事务中被传递并确认)。spring-doc.cadn.net.cn

  • consumerBatchEnabled: 启用消费者中离散消息的批处理;有关更多信息,请参阅消息监听容器配置spring-doc.cadn.net.cn

  • maxConcurrentConsumers 和消费者缩放间隔或触发器 —— DMLC 中没有自动缩放功能。不过,它允许您以编程方式更改 consumersPerQueue 属性,消费者将相应地进行调整。spring-doc.cadn.net.cn

然而,DMLC相较于SMLC具有以下优势:spring-doc.cadn.net.cn

  • 在运行时动态添加和移除队列更加高效。<br/>使用 SMLC 时,整个消费者线程会被重启(所有消费者被取消并重新创建)。<br/>而使用 DMLC 时,未受影响的消费者不会被取消。spring-doc.cadn.net.cn

  • 避免了 RabbitMQ 客户端线程与消费者线程之间的上下文切换。spring-doc.cadn.net.cn

  • 在SMLC中,线程是跨消费者共享的,而不是为每个消费者分配一个专用线程。

    但是,请参阅异步消费和多线程中的连接工厂配置的重要说明。spring-doc.cadn.net.cn

有关每个容器适用的配置属性的信息,请参阅消息监听器容器配置spring-doc.cadn.net.cn

检测空闲异步消费者

虽然高效,但异步消费者的一个问题在于检测其是否处于空闲状态——用户可能希望在一段时间内没有消息到达时执行某些操作。spring-doc.cadn.net.cn

从版本 1.6 开始,现在可以配置监听器容器在一段时间内没有消息传递时发布一个 ListenerContainerIdleEvent。当容器处于空闲状态时,每 idleEventInterval 毫秒发布一次事件。spring-doc.cadn.net.cn

要配置此功能,请在容器上设置 idleEventInterval。以下示例展示了如何在 XML 和 Java 中实现(适用于 SimpleMessageListenerContainerSimpleRabbitListenerContainerFactory):spring-doc.cadn.net.cn

<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;
}

在这些情况下,每当容器空闲时,每分钟都会发布一次事件。spring-doc.cadn.net.cn

事件消费

您可以通过实现 ApplicationListener 来捕获空闲事件——既可以是通用监听器,也可以是仅接收此特定事件的限定监听器。您还可以使用 @EventListener,该功能在 Spring Framework 4.2 中引入。spring-doc.cadn.net.cn

以下示例将 @RabbitListener@EventListener 合并为一个类。你需要理解的是,应用监听器会接收到所有容器的事件,因此如果你希望根据哪个容器处于空闲状态而采取特定操作,则可能需要检查监听器 ID。你还可以使用 @EventListener condition 来实现此目的。spring-doc.cadn.net.cn

事件具有四个属性:spring-doc.cadn.net.cn

以下示例展示了如何同时使用 @RabbitListener@EventListener 注解来创建监听器:spring-doc.cadn.net.cn

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 来缩小接收到的事件范围。
如果您希望使用空闲事件来停止监听器容器,则不应在调用监听器的线程上调用 container.stop()。这样做总是会导致延迟并产生不必要的日志消息。相反,您应将该事件转交给另一个线程,由该线程负责停止容器。
监控监听器性能

从版本 2.2 开始,如果类路径上检测到 Micrometer,且应用程序上下文中存在一个(或恰好一个)标注为 @PrimaryMeterRegistry(例如在使用 Spring Boot 时),监听器容器将自动为监听器创建并更新 Micrometer Timer。可以通过将容器属性 micrometerEnabled 设置为 false 来禁用计时器。spring-doc.cadn.net.cn

维护了两个计时器——一个用于监听器成功调用,另一个用于失败情况。通过简单的 MessageListener 配置,每个已配置的队列都对应一对计时器。spring-doc.cadn.net.cn

计时器命名为 spring.rabbitmq.listener,并具有以下标签:spring-doc.cadn.net.cn

您可以使用 micrometerTags 容器属性添加额外的标签。spring-doc.cadn.net.cn

Micrometer 观测

自版本 3.0 起,现已支持使用 Micrometer 进行观测,适用于 RabbitTemplate 和监听器容器。spring-doc.cadn.net.cn

将每个组件上的observationEnabled设置为启用观察;这将禁用Micrometer计时器,因为计时器现在将由每次观察进行管理。当使用带注释的侦听器时,请在容器工厂上设置observationEnabledspring-doc.cadn.net.cn

有关更多信息,请参阅 Micrometer 追踪spring-doc.cadn.net.cn

要为计时器/跟踪添加标签,请分别在模板或监听器容器中配置自定义 RabbitTemplateObservationConventionRabbitListenerObservationConventionspring-doc.cadn.net.cn

默认实现为模板观察添加 name 标签,为容器添加 listener.id 标签。spring-doc.cadn.net.cn

您可以选择继承 DefaultRabbitTemplateObservationConventionDefaultRabbitListenerObservationConvention,或者提供完全新的实现。spring-doc.cadn.net.cn

有关更多详细信息,请参阅Micrometer Observation 文档spring-doc.cadn.net.cn

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

虽然更倾向于使用 AnonymousQueue 实例作为自动删除队列,但从 2.1 版本开始,您也可以使用代理命名队列与监听容器配合使用。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@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 声明队列时,它会将 broker 返回的名称更新到 Queue.actualName 属性中。您必须使用 setQueues() 来配置容器,以便容器能够在运行时访问已声明的名称。仅设置名称是不够的。spring-doc.cadn.net.cn

您无法在容器运行时向其中添加命名的代理队列。
当连接被重置并建立新的连接时,新队列会获得一个新的名称。

spring-doc.cadn.net.cn

由于容器重启与队列重新声明之间存在竞争条件,因此重要的是将容器的 missingQueuesFatal 属性设置为 false,因为容器很可能最初尝试重新连接到旧队列。spring-doc.cadn.net.cn

4.1.8. 消息转换器

AmqpTemplate 还定义了若干用于发送和接收消息的方法,这些方法会委托给一个 MessageConverterMessageConverter 为每个方向提供了一个单一方法:一个用于将数据 转换为 Message,另一个用于将数据 Message 转换出来。请注意,当将数据转换为 Message 时,除了对象本身外,您还可以提供额外的属性。object 参数通常对应于消息体。以下列表展示了 MessageConverter 接口的定义:spring-doc.cadn.net.cn

public interface MessageConverter {

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

    Object fromMessage(Message message) throws MessageConversionException;

}

相关 Message 发送方法在 AmqpTemplate 上比我们之前讨论的方法更简单,因为它们不需要 Message 实例。相反,MessageConverter 负责通过将提供的对象转换为 Message 正文的字节数组,然后添加任何提供的 MessageProperties,来“创建”每个 Message。以下列表展示了各种方法的定义:spring-doc.cadn.net.cn

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”属性已被设置。</p><p>以下列表展示了这两种方法的定义:spring-doc.cadn.net.cn

Object receiveAndConvert() throws AmqpException;

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

默认的 MessageConverter 策略实现称为 SimpleMessageConverter。这是当您未显式配置其他替代方案时,RabbitTemplate 实例所使用的转换器。它可处理文本内容、序列化的 Java 对象以及字节数组。spring-doc.cadn.net.cn

从...Message

如果输入 Message 的内容类型以 "text" 开头(例如,text/plain),它还会检查 content-encoding 属性,以确定在将 Message 的主体字节数组转换为 Java String 时所使用的字符集。如果输入 Message 上未设置 content-encoding 属性,则默认使用 UTF-8 字符集。如果您需要覆盖此默认设置,可以配置一个 SimpleMessageConverter 实例,设置其 defaultCharset 属性,并将其注入到 RabbitTemplate 实例中。spring-doc.cadn.net.cn

如果输入 Message 的 content-type 属性值设置为 "application/x-java-serialized-object",则 SimpleMessageConverter 尝试将字节数组反序列化(重新构建)为 Java 对象。虽然这可能对简单的原型设计有所帮助,但我们不建议依赖 Java 序列化,因为它会导致生产者与消费者之间产生紧密耦合。当然,这也排除了在任一端使用非 Java 系统的可能性。由于 AMQP 是一种传输层协议,若因此类限制而丧失其大部分优势,将十分可惜。在接下来的两个部分中,我们将探讨一些替代方案,以便在不依赖 Java 序列化的情况下传递丰富的领域对象内容。spring-doc.cadn.net.cn

对于所有其他内容类型,SimpleMessageConverter 直接将 Message 的主体内容作为字节数组返回。spring-doc.cadn.net.cn

请参阅 Java 反序列化 以获取重要信息。spring-doc.cadn.net.cn

转换为Message

在将任意 Java 对象转换为 Message 时,SimpleMessageConverter 同样处理字节数组、字符串以及可序列化实例。它将这些对象各自转换为字节(对于字节数组,无需进行转换),并相应地设置内容类型属性。如果待转换的 Object 不属于上述任一类型,则 Message 的主体为 null。spring-doc.cadn.net.cn

SerializerMessageConverter

此转换器与 SimpleMessageConverter 类似,但可配置其他 Spring Framework
SerializerDeserializer 实现以用于 application/x-java-serialized-object 转换。spring-doc.cadn.net.cn

请参阅 Java 反序列化 以获取重要信息。spring-doc.cadn.net.cn

Jackson2JsonMessageConverter

本节介绍如何使用 Jackson2JsonMessageConverterMessage 之间进行转换。它包含以下部分:spring-doc.cadn.net.cn

转换为Message

如前一节所述,通常不建议依赖 Java 序列化。一种更为常见且更灵活、跨不同语言和平台可移植的替代方案是 JSON(JavaScript 对象表示法)。可以在任何 RabbitTemplate 实例上配置转换器,以覆盖其对 SimpleMessageConverter 默认实现的使用。Jackson2JsonMessageConverter 使用 com.fasterxml.jackson 2.x 库。以下示例配置了一个 Jackson2JsonMessageConverterspring-doc.cadn.net.cn

<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 属性配置一个静态类型,如下例所示:spring-doc.cadn.net.cn

<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 头部中的值映射到其他值。以下示例展示了如何实现此操作:spring-doc.cadn.net.cn

@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应用程序转换消息的完整讨论,请参阅从非Spring应用程序接收JSON示例应用程序。spring-doc.cadn.net.cn

从版本 2.4.3 开始,转换器在 supportedMediaType 包含 charset 参数时,将不再添加 contentEncoding 消息属性;此行为也用于编码。spring-doc.cadn.net.cn

String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));
从...Message

传入的消息会根据发送系统添加到报头中的类型信息转换为对象。spring-doc.cadn.net.cn

从版本 2.4.3 开始,如果没有 contentEncoding 消息属性,转换器将尝试检测 charset 参数是否存在于 contentType 消息属性中,并使用该参数。如果两者均不存在,且 supportedMediaType 具有 charset 参数,则将使用该参数进行解码,最终回退到 defaultCharset 属性。新增了一个方法 setSupportedMediaTypespring-doc.cadn.net.cn

String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));

在 1.6 版本之前的版本中,如果缺少类型信息,则转换将失败。</p><p>从 1.6 版本开始,如果缺少类型信息,转换器将使用 Jackson 默认值(通常是 Map)来转换 JSON。spring-doc.cadn.net.cn

此外,从版本 1.6 开始,当您在方法上使用 @RabbitListener 注解时,推断的类型信息会被添加到 MessageProperties 中。这使得转换器能够根据目标方法的参数类型进行转换。此功能仅在存在一个无注解的参数,或仅有一个带有 @Payload 注解的参数时才适用。在分析过程中,Message 类型的参数将被忽略。spring-doc.cadn.net.cn

默认情况下,推断的类型信息将覆盖由发送系统创建的传入 TypeId 及相关头信息。这使得接收系统能够自动转换为不同的领域对象。仅当参数类型为具体类(而非抽象类或接口)或来自 java.util 包时,此规则才适用。在所有其他情况下,使用 TypeId 及相关头信息。在某些情况下,您可能希望覆盖默认行为,并始终使用 TypeId 信息。例如,假设您有一个 @RabbitListener,它接受一个 Thing1 参数,但消息中包含一个 Thing2,该 Thing2Thing1 的子类(而 Thing1 是具体的)。推断的类型将是错误的。为处理此情况,请将 TypePrecedence 属性设置在 Jackson2JsonMessageConverter 上为 TYPE_ID,而不是默认的 INFERRED。(该属性实际上位于转换器的 DefaultJackson2JavaTypeMapper 上,但为方便起见,转换器上提供了相应的 setter 方法。)如果您注入了一个自定义类型映射器,应将属性设置在映射器上。
当从 Message 转换时,传入的 MessageProperties.getContentType() 必须符合 JSON 格式(contentType.contains("json") 用于检查)。
从版本 2.2 开始,如果不存在 contentType 属性,或其值为默认值 application/octet-stream,则假定使用 application/json
若要恢复到以前的行为(返回未转换的 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 的默认设置。spring-doc.cadn.net.cn

然而,您可以创建一个自定义转换器,并使用 targetMethod 消息属性来决定要转换为何种类型。spring-doc.cadn.net.cn

这种类型推断只有在将 @RabbitListener 注解声明在方法级别时才能实现。对于类级别 @RabbitListener,所转换的类型将用于选择要调用的 @RabbitHandler 方法。因此,基础设施提供了 targetObject 消息属性,您可以在自定义转换器中使用它来确定类型。
从版本 1.6.11 开始,Jackson2JsonMessageConverter 以及因此 DefaultJackson2JavaTypeMapperDefaultClassMapper)提供了 trustedPackages 选项,以应对 序列化工具链(Serialization Gadgets) 漏洞。
默认情况下并为保持向后兼容性,Jackson2JsonMessageConverter 信任所有包——即它对选项使用 *

从版本 2.4.7 开始,转换器可配置为在 Jackson 反序列化消息体后返回 Optional.empty(),而 Jackson 返回 null。这有助于 @RabbitListener 接收空负载(null payloads),方式有两种:spring-doc.cadn.net.cn

@RabbitListener(queues = "op.1")
void listen(@Payload(required = false) Thing payload) {
    handleOptional(payload); // payload might be null
}

@RabbitListener(queues = "op.2")
void listen(Optional<Thing> optional) {
    handleOptional(optional.orElse(this.emptyThing));
}

要启用此功能,请将 setNullAsOptionalEmpty 设置为 true;当设置为 false(默认值)时,转换器将回退到原始消息体(byte[])。spring-doc.cadn.net.cn

@Bean
Jackson2JsonMessageConverter converter() {
    Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
    converter.setNullAsOptionalEmpty(true);
    return converter;
}
反序列化抽象类

在 2.2.8 版本之前,如果某个 @RabbitListener 的推断类型为抽象类(包括接口),转换器会回退到检查请求头中的类型信息;若存在该信息,则使用它;若不存在,则尝试创建该抽象类。这会导致一个问题:当配置了自定义 ObjectMapper(并使用自定义反序列化器来处理该抽象类)时,若传入的消息中包含无效的类型头信息,就会出现问题。spring-doc.cadn.net.cn

从版本 2.2.8 开始,默认情况下保留之前的处理行为。如果您有自定义的 ObjectMapper,并且希望忽略类型头信息,并始终使用推断的类型进行转换,请将 alwaysConvertToInferredType 设置为 true。此举是为了确保向后兼容性,并避免在转换必然失败时(使用标准 ObjectMapper)产生额外的开销。spring-doc.cadn.net.cn

使用 Spring Data 投影接口

从版本 2.2 开始,您可以将 JSON 转换为 Spring Data 投影接口,而不仅仅是具体类型。这使得可以实现非常灵活且低耦合的数据绑定,包括从 JSON 文档中的多个位置查找值。例如,以下接口可被定义为消息负载类型:spring-doc.cadn.net.cn

interface SomeSample {

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

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

访问器方法将默认用于通过接收到的 JSON 文档中的字段来查找属性名称。@JsonPath 表达式允许自定义值查找方式,甚至可以定义多个 JSON 路径表达式,以从多个位置查找值,直到某个表达式返回实际值。spring-doc.cadn.net.cn

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

当用作 @RabbitListener 方法的参数时,接口类型会像平常一样自动传递给转换器。spring-doc.cadn.net.cn

从...Message使用RabbitTemplate

如前所述,类型信息通过消息头传递,以协助转换器在将消息转换为其他格式时进行操作。这在大多数情况下都能正常工作。然而,当使用泛型类型时,它只能转换简单对象和已知的“容器”对象(列表、数组和映射)。从 2.0 版本开始,Jackson2JsonMessageConverter 实现了 SmartMessageConverter,使其能够与新的 RabbitTemplate 方法配合使用,这些方法接受一个 ParameterizedTypeReference 参数。这使得复杂泛型类型的转换成为可能,如下例所示:spring-doc.cadn.net.cn

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

另一种选择是 MarshallingMessageConverter
它将委托给 Spring OXM 库中对 MarshallerUnmarshaller 策略接口的实现。
您可在此处阅读有关该库的更多信息 这里
在配置方面,最常见的方式是仅提供构造函数参数,因为大多数 Marshaller 的实现也实现了 Unmarshaller
以下示例展示了如何配置一个 MarshallingMessageConverterspring-doc.cadn.net.cn

<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 与消息之间进行转换。spring-doc.cadn.net.cn

两者 Jackson2XmlMessageConverterJackson2JsonMessageConverter 都具有相同的基类:AbstractJackson2MessageConverterspring-doc.cadn.net.cn

AbstractJackson2MessageConverter 被引入以替代已移除的类:AbstractJsonMessageConverter

The Jackson2XmlMessageConverter uses the com.fasterxml.jackson 2.x library.spring-doc.cadn.net.cn

您可以以与 Jackson2JsonMessageConverter 相同的方式使用它,只不过它支持 XML 而非 JSON。
以下示例配置了一个 Jackson2JsonMessageConverterspring-doc.cadn.net.cn

<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 以获取更多信息。spring-doc.cadn.net.cn

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

该类于 1.4.2 版本引入,允许根据 MessageProperties 中的内容类型属性将请求委托给特定的 MessageConverter。默认情况下,如果不存在 contentType 属性,或者其值与所有已配置的转换器均不匹配,则会委托给一个 SimpleMessageConverter。以下示例配置了一个 ContentTypeDelegatingMessageConverterspring-doc.cadn.net.cn

<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 对象。spring-doc.cadn.net.cn

从不可信来源反序列化 Java 对象时可能存在漏洞。spring-doc.cadn.net.cn

如果您接受来自不可信源的消息(其中 content-type 代表 application/x-java-serialized-object),您应考虑配置哪些包和类被允许反序列化。此要求适用于当配置为使用 DefaultDeserializer(无论是隐式还是通过配置)时的 SimpleMessageConverterSerializerMessageConverterspring-doc.cadn.net.cn

默认情况下,允许列表为空,这意味着不会反序列化任何类。spring-doc.cadn.net.cn

您可以设置一组模式,例如 thing1.thing1.thing2.Cat.MySafeClassspring-doc.cadn.net.cn

模式按顺序进行检查,直到找到匹配项为止。如果没有匹配项,则抛出 SecurityExceptionspring-doc.cadn.net.cn

您可以使用这些转换器上的 allowedListPatterns 属性设置模式。
或者,如果您信任所有消息发送方,可以将环境变量 SPRING_AMQP_DESERIALIZATION_TRUST_ALL 或系统属性 spring.amqp.deserialization.trust.all 设置为 truespring-doc.cadn.net.cn

消息属性转换器

策略接口 MessagePropertiesConverter 用于在 Rabbit 客户端 BasicProperties 与 Spring AMQP MessageProperties 之间进行转换。默认实现(DefaultMessagePropertiesConverter)通常已能满足大多数需求,但若需要,您也可以自定义实现。默认属性转换器会在元素大小不超过 1024 字节时,将 BasicProperties 类型为 LongString 的元素转换为 String 实例。更大的 LongString 实例则不会被转换(详见下一段)。此限制可通过构造函数参数进行覆盖。spring-doc.cadn.net.cn

从版本 1.6 开始,长度超过长字符串限制(默认:1024)的标题现在默认由 DefaultMessagePropertiesConverter 作为 LongString 实例保留。您可以通过 getBytes[]toString()getStream() 方法访问其内容。spring-doc.cadn.net.cn

之前,DefaultMessagePropertiesConverter 将此类头信息“转换”为 DataInputStream(实际上只是引用了 LongString 实例的 DataInputStream)。在输出时,此头信息未被转换(仅转换为字符串——例如,通过在流上调用 toString() 来实现,如 java.io.DataInputStream@1d057a39)。spring-doc.cadn.net.cn

大型传入 LongString 头部现在也将在输出时正确“转换”(默认情况下)。spring-doc.cadn.net.cn

提供了一个新的构造函数,以便您可以配置转换器以继续按之前的方式工作。</p><p>以下列表显示了该方法的 Javadoc 注释和声明:spring-doc.cadn.net.cn

/**
 * 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 进行相互转换时,由于 MessageProperties.correlationId 是一种 byte[],而 BasicProperties 使用的是 String,因此执行了一次不必要的 byte[] <→ String 转换。(最终,RabbitMQ 客户端使用 UTF-8 将 String 转换为字节,以放入协议消息中)。spring-doc.cadn.net.cn

为了提供最大的向后兼容性,已在 DefaultMessagePropertiesConverter 中添加了一个名为 correlationIdPolicy 的新属性。该属性接受一个 DefaultMessagePropertiesConverter.CorrelationIdPolicy 枚举类型的参数。默认情况下,它被设置为 BYTES,这将复制之前的行为。spring-doc.cadn.net.cn

对于传入消息:spring-doc.cadn.net.cn

对于出站消息:spring-doc.cadn.net.cn

自版本 1.6 起,入站 deliveryMode 属性不再映射到 MessageProperties.deliveryMode。而是映射到 MessageProperties.receivedDeliveryMode。此外,入站 userId 属性也不再映射到 MessageProperties.userId,而是映射到 MessageProperties.receivedUserId。这些更改旨在避免在使用同一 MessageProperties 对象发送出站消息时,这些属性被意外传播。spring-doc.cadn.net.cn

从版本 2.2 开始,DefaultMessagePropertiesConverter 会使用 getName() 而非 toString() 来转换任何值类型为 Class<?> 的自定义头信息;这可避免应用程序必须从 toString() 表示形式中解析类名。spring-doc.cadn.net.cn

4.1.9. 修改消息 - 压缩和其他功能

存在多个扩展点。它们允许您在消息发送至 RabbitMQ 之前或在接收到消息后立即执行某些处理。spring-doc.cadn.net.cn

消息转换器 中所见,其中一个扩展点位于 AmqpTemplate convertAndReceive 操作中,在此处您可以提供一个 MessagePostProcessor
例如,在您的 POJO 被转换后,MessagePostProcessor 允许您在 Message 上设置自定义标头或属性。spring-doc.cadn.net.cn

从版本 1.4.2 开始,已向 RabbitTemplate - setBeforePublishPostProcessors()setAfterReceivePostProcessors() 添加了额外的扩展点。第一个允许在将消息发送到 RabbitMQ 之前立即运行一个后处理器。当使用批处理(参见批量处理)时,在组装完批次之后、发送前调用此方法。第二个是在收到消息后立即调用。spring-doc.cadn.net.cn

这些扩展点用于诸如压缩等功能,为此提供了几种 MessagePostProcessor 实现。 GZipPostProcessorZipPostProcessorDeflaterPostProcessor 在发送前对消息进行压缩,而 GUnzipPostProcessorUnzipPostProcessorInflaterPostProcessor 则对收到的消息进行解压缩。spring-doc.cadn.net.cn

从版本 2.1.5 开始,可以使用 copyProperties = true 选项将 GZipPostProcessor 配置为复制原始消息属性的副本。默认情况下,出于性能考虑,这些属性会被复用,并通过压缩内容编码及可选的 MessageProperties.SPRING_AUTO_DECOMPRESS 头部进行修改。如果您保留了原始出站消息的引用,其属性也会随之发生变化。因此,如果您的应用程序保留了包含这些消息后处理器的出站消息副本,请考虑启用 copyProperties 选项。
从版本 2.2.12 开始,您可以配置压缩后处理器在内容编码元素之间使用的分隔符。

spring-doc.cadn.net.cn

在 2.2.11 版本及之前,该分隔符被硬编码为 :,现在已更改为将 , ` by default. The decompressors will work with both delimiters. However, if you publish messages with 2.3 or later and consume with 2.2.11 or earlier, you MUST set the `encodingDelimiter 设置为压缩器(s)上的 : 属性。spring-doc.cadn.net.cn

当您的消费者升级到 2.2.11 或更高版本时,您可以恢复默认的 `, `。spring-doc.cadn.net.cn

同样,SimpleMessageListenerContainer 也具有一个 setAfterReceivePostProcessors() 方法,使得解压缩操作在消息被容器接收后执行。spring-doc.cadn.net.cn

从版本 2.1.4 开始,addBeforePublishPostProcessors()addAfterReceivePostProcessors() 已被添加到 RabbitTemplate 中,以允许分别向“发布前”和“接收后”的后处理器列表中追加新的后处理器。此外,还提供了用于移除后处理器的方法。同样地,AbstractMessageListenerContainer 也新增了 addAfterReceivePostProcessors()removeAfterReceivePostProcessor() 方法。有关更多详情,请参阅 RabbitTemplateAbstractMessageListenerContainer 的 Javadoc 文档。spring-doc.cadn.net.cn

4.1.10. 请求/回复消息传递

AmqpTemplate 还提供了多种 sendAndReceive 方法,这些方法接受与前述单向发送操作(exchangeroutingKeyMessage)相同的参数选项。
这些方法在请求-应答场景中非常有用,因为它们可在发送前自动配置必要的 reply-to 属性,并能在为该目的而内部创建的专用队列上监听应答消息。spring-doc.cadn.net.cn

类似的请求-回复方法也已提供,其中 MessageConverter 同时应用于请求和回复。这些方法被命名为 convertSendAndReceive。有关更多详情,请参阅 AmqpTemplate 的 Javadocspring-doc.cadn.net.cn

从 1.5.0 版本开始,每个sendAndReceive方法变体都有一个重载版本,该版本接受CorrelationData。结合正确配置的连接工厂,这使得可以在操作的发送端接收发布者确认。相关发布者确认和返回以及Javadoc for RabbitOperations获取更多详细信息。spring-doc.cadn.net.cn

从版本2.0开始,这些方法有变体(convertSendAndReceiveAsType),它们多接受一个额外的 ParameterizedTypeReference 参数来转换复杂的返回类型。模板必须配置为 SmartMessageConverter使用 RabbitTemplateMessage 转换为其他类型 可获取更多信息。spring-doc.cadn.net.cn

从版本 2.1 开始,您可以使用 RabbitTemplate 配置 noLocalReplyConsumer 选项,以控制回复消费者的 noLocal 标志。这默认是 falsespring-doc.cadn.net.cn

回复超时

默认情况下,发送和接收方法在五秒后超时并返回 null。您可以通过设置 replyTimeout 属性来修改此行为。从版本 1.5 开始,如果将 mandatory 属性设置为 true(或针对特定消息时 mandatory-expression 的计算结果为 true),当消息无法投递到队列时,会抛出 AmqpMessageReturnedException 异常。该异常包含 returnedMessagereplyCodereplyText 等属性,以及用于发送的 exchangeroutingKeyspring-doc.cadn.net.cn

此功能使用发布者返回。您可以通过将CachingConnectionFactory上的publisherReturns设置为true来启用它(参见发布者确认和返回)。此外,您不得已向RabbitTemplate注册您自己的ReturnCallback

从版本 2.1.2 开始,新增了一个 replyTimedOut 方法,使子类能够获知超时信息,从而可以清理任何保留的状态。spring-doc.cadn.net.cn

从版本 2.0.11 和 2.1.3 开始,当您使用默认值 DirectReplyToMessageListenerContainer 时,可以通过设置模板的 replyErrorHandler 属性来添加一个错误处理器。此错误处理器将用于处理任何失败的投递,例如延迟回复以及未带关联头(correlation header)的消息。传入的异常是一个 ListenerExecutionFailedException,它具有一个 failedMessage 属性。spring-doc.cadn.net.cn

RabbitMQ 直接回复-回复地址
从版本 3.4.0 开始,RabbitMQ 服务器支持 直接回复到。这消除了使用固定回复队列的主要原因(即避免为每个请求创建临时队列)。从 Spring AMQP 版本 1.4.1 开始,默认情况下(如果服务器支持)将使用直接回复到功能,而不是创建临时回复队列。当未提供 replyQueue(或其名称设置为 amq.rabbitmq.reply-to)时,RabbitTemplate 会自动检测是否支持直接回复到功能,并据此选择使用它或回退到使用临时回复队列。在使用直接回复到功能时,不需要配置 reply-listener,也不应对其进行配置。

回复监听器仍与命名队列(除了 amq.rabbitmq.reply-to)兼容,从而可控制回复的并发性等。spring-doc.cadn.net.cn

从版本 1.6 开始,如果您希望为每个回复使用一个临时、独占、自动删除的队列,请将 useTemporaryReplyQueues 属性设置为 true。如果设置了 replyAddress,则此属性将被忽略。spring-doc.cadn.net.cn

您可以更改决定是否使用直接回复地址的条件,方法是继承 RabbitTemplate 并重写 useDirectReplyTo() 方法以检查不同的条件。该方法仅在第一个请求发出时调用一次。spring-doc.cadn.net.cn

在 2.0 版本之前,RabbitTemplate 会在每次请求时创建一个新的消费者,并在收到回复(或超时)后取消该消费者。现在,模板改用 DirectReplyToMessageListenerContainer 来替代,从而允许复用消费者。模板依然负责将回复与对应的请求进行关联,因此不会出现延迟回复被错误地发送给其他发送方的情况。如果您希望恢复到之前的 Behavior,请将 useDirectReplyToContainer(使用 XML 配置时为 direct-reply-to-container)属性设置为 false。spring-doc.cadn.net.cn

代码 AsyncRabbitTemplate 没有此选项。
当直接使用回复地址时,它始终使用 DirectReplyToContainer 进行回复。spring-doc.cadn.net.cn

从版本 2.3.7 开始,模板新增了一个属性 useChannelForCorrelation。当该属性为 true 时,服务器无需将关联 ID(correlation id)从请求消息头复制到响应消息中。相反,发送请求所使用的通道将用于将响应与请求进行关联。spring-doc.cadn.net.cn

消息关联与回复队列

当使用固定回复队列(除 amq.rabbitmq.reply-to 外)时,您必须提供关联数据,以便将回复与请求进行匹配。参见 RabbitMQ 远程过程调用(RPC)。默认情况下,标准 correlationId 属性用于存储关联数据。然而,如果您希望使用自定义属性来保存关联数据,可以在 元素上设置 correlation-key 属性。显式地将该属性设置为 correlationId 等同于不设置该属性。客户端和服务器必须使用相同的头部字段来存放关联数据。spring-doc.cadn.net.cn

Spring AMQP 版本 1.1 曾使用一个名为 spring_reply_correlation 的自定义属性来存储此数据。如果希望在当前版本中恢复此行为(例如,为了与另一款使用 1.1 版本的应用程序保持兼容),必须将该属性设置为 spring_reply_correlation

默认情况下,模板会自动生成其自身的关联ID(忽略任何用户提供的值)。
如果您希望使用自己的关联ID,请将 RabbitTemplate 实例的 userCorrelationId 属性设置为 truespring-doc.cadn.net.cn

相关 ID 必须是唯一的,以避免为请求返回错误的响应。
回复监听器容器

在使用版本早于 3.4.0 的 RabbitMQ 时,每个回复都会使用一个新的临时队列。但是可以在模板上配置单个回复队列,这样更有效率,并且还可以在该队列上设置参数。然而,在这种情况下,您还必须提供一个<reply-listener/>子元素。此元素为回复队列提供了监听器容器,其中模板是监听器。允许在 <listener-container/> 上使用的所有消息监听器容器配置属性也允许在此元素上使用,除了从模板配置继承的connection-factorymessage-converterspring-doc.cadn.net.cn

如果您运行应用程序的多个实例或使用多个 RabbitTemplate 实例,则必须为每个实例使用一个唯一的回复队列。

spring-doc.cadn.net.cn

RabbitMQ 无法从队列中选择消息,因此,如果所有实例都使用相同的队列,每个实例将竞争回复消息,且不一定能收到属于自己的回复。spring-doc.cadn.net.cn

以下示例定义了一个带有连接工厂的兔子模板:spring-doc.cadn.net.cn

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

虽然容器和模板共享一个连接工厂,但它们并不共享一个通道。因此,请求和回复不会在同一个事务中执行(如果启用了事务)。spring-doc.cadn.net.cn

在 1.5.0 版本之前,reply-address 属性不可用。回复始终通过默认交换机并以 reply-queue 名称作为路由键进行路由。这仍然是默认行为,但现在您可以指定新的 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/> 命名空间元素定义模板时(如前面示例所示),解析器会将模板中的容器和连线定义为监听器。spring-doc.cadn.net.cn

当模板不使用固定的 replyQueue(或正在使用直接回复——参见 RabbitMQ 直接回复)时,不需要监听器容器。

spring-doc.cadn.net.cn

在使用 RabbitMQ 3.4.0 或更高版本时,直接 reply-to 是首选机制。spring-doc.cadn.net.cn

如果您将 RabbitTemplate 定义为 <bean/>,或使用 @Configuration 类来将其定义为 @Bean,或者在程序中动态创建模板时,您需要自行定义并配置回复监听器容器。
如果您未执行此操作,模板将永远不会接收到回复,最终会超时,并向 sendAndReceive 方法的调用返回 null 作为回复。spring-doc.cadn.net.cn

从版本 1.5 开始,RabbitTemplate 会检测其是否已配置为 MessageListener 以接收回复。如果未配置,则尝试发送并接收带有回复地址的消息时将失败,抛出 IllegalStateException(因为回复永远不会被接收)。spring-doc.cadn.net.cn

此外,如果使用简单的 replyAddress(队列名称),回复监听器容器会验证其正在监听一个具有相同名称的队列。如果回复地址是交换机和路由键,则无法执行此检查,并会记录一条调试日志消息。spring-doc.cadn.net.cn

当您自己配置回复监听器和模板时,重要的是要确保模板的 replyAddress 属性与容器的 queues(或 queueNames)属性指向同一个队列。

spring-doc.cadn.net.cn

模板会将回复地址插入到出站消息的 replyTo 属性中。spring-doc.cadn.net.cn

以下列表展示了如何手动配置 Bean 的示例:spring-doc.cadn.net.cn

<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,以及一个“远程”监听容器,用于处理请求并返回回复,详见 此测试用例spring-doc.cadn.net.cn

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

在 1.3.6 版本之前,对于超时消息的延迟回复仅被记录日志。现在,若收到延迟回复,将予以拒绝(模板会抛出 AmqpRejectAndDontRequeueException)。如果回复队列配置为将被拒绝的消息发送至死信交换机,则可稍后检索该回复以供分析。为此,请将一个队列绑定至配置的死信交换机,并将路由键设置为回复队列的名称。spring-doc.cadn.net.cn

请参阅 RabbitMQ 死信文档,了解有关配置死信的更多信息。
您也可以查看 FixedReplyQueueDeadLetterTests 测试用例,以获取示例。spring-doc.cadn.net.cn

异步 Rabbit 模板

版本 1.6 引入了 AsyncRabbitTemplate。它具有与AmqpTemplate上类似的方法(以及convertSendAndReceive),但是它们不会阻塞,而是返回一个CompletableFuturespring-doc.cadn.net.cn

方法 sendAndReceive 返回一个 RabbitMessageFuture
方法 convertSendAndReceive 返回一个 RabbitConverterFuturespring-doc.cadn.net.cn

您可以选择以同步方式稍后通过调用未来对象上的 get() 来获取结果,也可以注册一个回调函数,该回调函数在异步获取结果时被调用。以下列表展示了这两种方法:spring-doc.cadn.net.cn

@Autowired
private AsyncRabbitTemplate template;

...

public void doSomeWorkAndGetResultLater() {

    ...

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

    // do some more work

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

    ...

}

public void doSomeWorkAndGetResultAsync() {

    ...

    RabbitConverterFuture<String> future = this.template.convertSendAndReceive("foo");
    future.whenComplete((result, ex) -> {
        if (ex == null) {
            // success
        }
        else {
            // failure
        }
    });

    ...

}

如果设置为 mandatory,且消息无法投递,则该未来对象将抛出一个 ExecutionException 异常,其原因包含 AmqpMessageReturnedException,该异常封装了返回的消息及有关返回的详细信息。spring-doc.cadn.net.cn

如果设置为 enableConfirms,该异步结果对象具有一个名为 confirm 的属性,该属性本身是一个 CompletableFuture<Boolean> 对象,其中 true 表示发布是否成功。
如果确认异步结果为 false,则 RabbitFuture 具有另一个名为 nackCause 的属性,其中包含失败原因(如可用)。spring-doc.cadn.net.cn

如果确认(publisher confirm)在回复之后收到,则会被丢弃,因为回复意味着发布已成功。

您可以设置模板上的 receiveTimeout 属性以超时回复(默认值为 30000,即 30 秒)。如果发生超时,该未来对象将以 AmqpReplyTimeoutException 完成。spring-doc.cadn.net.cn

该模板实现了 SmartLifecycle。在存在待处理回复时停止模板,会导致待处理的 Future 实例被取消。spring-doc.cadn.net.cn

从版本 2.0 开始,异步模板现在支持 直接回复,而不是配置的回复队列。要启用此功能,请使用以下任一构造函数:spring-doc.cadn.net.cn

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

public AsyncRabbitTemplate(RabbitTemplate template)

参见 RabbitMQ 直接回复,以在同步 RabbitTemplate 中使用直接回复功能。spring-doc.cadn.net.cn

版本 2.0 引入了这些方法的变体(convertSendAndReceiveAsType),它们接受一个额外的 ParameterizedTypeReference 参数,用于转换复杂返回类型。您必须配置底层的 RabbitTemplate 并使用 SmartMessageConverter。有关更多信息,请参阅如何从 Message 转换为 RabbitTemplatespring-doc.cadn.net.cn

从版本 3.0 开始,AsyncRabbitTemplate 方法现在返回 CompletableFuture 个值,而不是 ListenableFuture 个值。
Spring Remoting with AMQP

Spring 远程调用已不再受支持,因为该功能已从 Spring Framework 中移除。spring-doc.cadn.net.cn

使用 sendAndReceive 操作,采用 RabbitTemplate(客户端)和 @RabbitListener 代替。spring-doc.cadn.net.cn

4.1.11. 配置代理

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

接口 AmqpAdmin 基于使用 Spring AMQP 领域抽象,如下所示:spring-doc.cadn.net.cn

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() 方法返回有关队列的一些有限信息(消息计数和消费者计数)。
返回的属性键作为常量在 RabbitTemplate (QUEUE_NAMEQUEUE_MESSAGE_COUNTQUEUE_CONSUMER_COUNT) 中可用。
RabbitMQ REST APIQueueInfo 对象中提供了更多信息。spring-doc.cadn.net.cn

无参数 declareQueue() 方法在代理上定义一个队列,其名称为自动生成的。此自动生成队列的附加属性为 exclusive=trueautoDelete=truedurable=falsespring-doc.cadn.net.cn

declareQueue(Queue queue)方法接受一个Queue对象,并返回声明队列的名称。如果提供的Queuename属性是一个空String,代理会使用生成的名称声明这个队列。actualName属性会被添加到Queue中并返回给调用者。你只能通过直接调用RabbitAdmin来以编程方式使用此功能。在应用程序上下文中定义队列时,当管理员自动声明队列时,可以将name属性设置为""(空字符串)。然后代理创建名称。从版本2.1开始,监听器容器可以使用此类队列。容器和代理命名队列获取更多信息。spring-doc.cadn.net.cn

这与 AnonymousQueue 形成对比,后者中框架会生成一个唯一的 (UUID) 名称,并将 durable 设置为 false,将 exclusiveautoDelete 设置为 true。一个 <rabbit:queue/> 若其 name 属性为空(或缺失),则始终会创建一个 AnonymousQueuespring-doc.cadn.net.cn

请参阅 AnonymousQueue 以了解为何 AnonymousQueue 比由代理生成的队列名称更受推荐,以及如何控制名称的格式。从版本 2.1 开始,匿名队列默认使用参数 Queue.X_QUEUE_LEADER_LOCATOR 设置为 client-local 进行声明。这可确保队列在应用程序所连接的节点上被声明。声明式队列必须具有固定名称,因为它们可能在上下文中的其他位置被引用——例如,在以下示例中的监听器中:spring-doc.cadn.net.cn

<rabbit:listener-container>
    <rabbit:listener ref="listener" queue-names="#{someQueue.name}" />
</rabbit:listener-container>

此接口的 RabbitMQ 实现为 RabbitAdmin,当通过 Spring XML 配置时,其示例如下:spring-doc.cadn.net.cn

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

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

当缓存模式 CachingConnectionFactoryCHANNEL(默认值)时,RabbitAdmin 实现会自动延迟声明队列、交换机和绑定关系,这些组件均在同一 ApplicationContext 中声明。一旦打开与代理服务器的 Connection 连接,这些组件便会立即被声明。某些命名空间特性使得此操作极为便捷——例如,在 Stocks 示例应用程序中,我们有以下内容:spring-doc.cadn.net.cn

<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 定义标识符。以下示例配置了一个具有显式名称的队列:spring-doc.cadn.net.cn

<rabbit:queue name="stocks.trade.queue"/>
您可以同时提供 idname 属性。

spring-doc.cadn.net.cn

这使您能够通过一个与队列名称无关的 ID 来引用该队列(例如,在绑定中使用)。spring-doc.cadn.net.cn

同时,它还允许使用标准 Spring 功能(如属性占位符和用于队列名称的 SpEL 表达式)。spring-doc.cadn.net.cn

当使用名称作为 Bean 标识符时,这些功能将不可用。spring-doc.cadn.net.cn

队列可以配置额外的参数——例如,x-message-ttl。当您使用命名空间支持时,这些参数以 Map(即参数名/参数值对)的形式提供,这些参数是通过使用 <rabbit:queue-arguments> 元素来定义的。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<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>

默认情况下,参数被视为字符串。对于其他类型的参数,您必须提供其类型。以下示例展示了如何指定类型:spring-doc.cadn.net.cn

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments value-type="java.lang.Long">
        <entry key="x-message-ttl" value="100"/>
    </rabbit:queue-arguments>
</rabbit:queue>

在提供混合类型参数时,您必须为每个条目元素指定类型。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<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 Framework 3.2 及更高版本开始,可以更简洁地声明如下:spring-doc.cadn.net.cn

<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 参数可通过 Queue 类上的 setLeaderLocator() 方法作为一级属性支持。
从版本 2.1 开始,默认情况下,匿名队列会通过此属性设置为 client-local 进行声明。
这可确保队列在应用程序所连接的节点上进行声明。spring-doc.cadn.net.cn

RabbitMQ 代理不允许声明参数不匹配的队列。例如,如果已存在一个 queue(即无 time to live 参数),而您尝试以(例如)key="x-message-ttl" value="100" 的参数重新声明该队列,则会抛出异常。

默认情况下,RabbitAdmin 在发生任何异常时会立即停止处理所有声明。这可能导致下游问题,例如监听器容器因另一个队列(在出错队列之后定义)未被声明而无法初始化。spring-doc.cadn.net.cn

此行为可以通过将 ignore-declaration-exceptions 属性设置为 true 来修改,该属性属于 RabbitAdmin 实例。
此选项会指示 RabbitAdmin 记录异常并继续声明其他元素。
在使用 Java 配置 RabbitAdmin 时,此属性被称为 ignoreDeclarationExceptions
这是一个全局设置,适用于所有元素。
队列、交换机和绑定也具有类似属性,但仅适用于这些特定元素。spring-doc.cadn.net.cn

在 1.6 版本之前,此属性仅在通道中发生 IOException 时生效,例如当前属性与期望属性不匹配时。现在,此属性对任何异常均生效,包括 TimeoutException 及其他异常。spring-doc.cadn.net.cn

此外,任何声明异常都会导致发布一个 DeclarationExceptionEvent,这是一个 ApplicationEvent,可被上下文中的任何 ApplicationListener 消费。该事件包含对管理员的引用、正在被声明的元素以及 Throwablespring-doc.cadn.net.cn

头交换

从版本 1.3 开始,您可以将 HeadersExchange 配置为匹配多个请求头。您还可以指定是任意一个还是所有请求头都必须匹配。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<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 开始,您可以将 Exchanges 配置为一个 internal 标志(默认值为 false),并且如果应用程序上下文中存在,则该 Exchange 会在 Broker 上通过一个 RabbitAdmin 正确配置。如果交换机的 internal 标志为 true,RabbitMQ 将不允许客户端使用该交换机。这在死信交换机或交换机到交换机的绑定中非常有用,此时您不希望交换机被发布者直接使用。spring-doc.cadn.net.cn

要了解如何使用 Java 配置 AMQP 基础设施,请查看 Stock 示例应用程序,其中包含类 @Configuration,该类又具有 RabbitClientConfigurationRabbitServerConfiguration 子类。以下列表显示了 AbstractStockRabbitConfiguration 的代码:spring-doc.cadn.net.cn

@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 类进行配置的:spring-doc.cadn.net.cn

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

    @Bean
    public Queue stockRequestQueue() {
        return new Queue("app.stock.request");
    }
}

这是整个继承链的终点,共包含 @Configuration 个类。
最终结果是,在应用程序启动时,TopicExchangeQueue 已被声明给代理(broker)。
服务器配置中未将 TopicExchange 绑定到队列,该操作由客户端应用程序完成。
然而,股票请求队列会自动绑定到 AMQP 默认交换机。
此行为由规范所定义。spring-doc.cadn.net.cn

客户端 @Configuration 类要有趣一些。其声明如下:spring-doc.cadn.net.cn

@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

}

客户端通过 declareQueue() 方法在 AmqpAdmin 上声明另一个队列。它将该队列绑定到市场数据交换机,绑定的路由模式存储在属性文件中。spring-doc.cadn.net.cn

队列和交换器的构建器API

版本 1.6 引入了一个便捷的流式 API,用于在使用 Java 配置时配置 QueueExchange 对象。以下示例展示了如何使用它:spring-doc.cadn.net.cn

@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();
}

从版本 2.0 开始,ExchangeBuilder 默认创建持久化交换机(durable exchanges),以与各个 AbstractExchange 类中的简单构造函数保持一致。要使用构建器创建非持久化交换机,请在调用 .build() 之前使用 .durable(false)。不带参数的 durable() 方法已不再提供。spring-doc.cadn.net.cn

版本 2.2 引入了流畅式 API,用于添加“常见”的交换机和队列参数…spring-doc.cadn.net.cn

@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 个对象的集合(QueueExchangeBinding)包装在 Declarables 个对象中。
当连接建立时(初始连接及连接失败后),RabbitAdmin 会检测应用上下文中的此类 Bean(以及独立的 Declarable Bean),并在代理上声明其中包含的对象。
以下示例展示了如何实现此操作:spring-doc.cadn.net.cn

@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 实例。

spring-doc.cadn.net.cn

这在某些情况下可能引发不期望的副作用,因为管理员必须遍历所有 Collection<?> 个 bean。spring-doc.cadn.net.cn

版本 2.2 添加了 getDeclarablesByType 方法到 Declarables;此方法可用作一种便捷方式,例如在声明监听器容器 bean 时使用。spring-doc.cadn.net.cn

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")。spring-doc.cadn.net.cn

从版本 2.1.9 开始,RabbitAdmin 新增了一个属性 explicitDeclarationsOnly(默认值为 false);当该属性设置为 true 时,管理员将仅声明那些显式配置为由该管理员声明的 Bean。spring-doc.cadn.net.cn

从 1.2 版本开始,您可以有条件地声明这些元素。这在应用程序连接到多个代理(broker)且需要指定某个特定元素应与哪些代理进行声明时特别有用。

表示这些元素的类实现了 Declarable,该接口包含两个方法:shouldDeclare()getDeclaringAdmins()RabbitAdmin 使用这些方法来确定特定实例是否应实际处理其 Connection 上的声明。spring-doc.cadn.net.cn

属性可在命名空间中作为属性使用,如下例所示:spring-doc.cadn.net.cn

<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 属性为 false)。

同样,您也可以使用基于 Java 的 @Configuration 来实现相同的效果。在以下示例中,组件由 admin1 声明,但并非由 admin2 声明:spring-doc.cadn.net.cn

@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属性

属性 name<rabbit:queue/><rabbit:exchange/> 元素上反映的是代理中实体的名称。对于队列,如果省略了 name,则会创建一个匿名队列(参见 AnonymousQueue)。spring-doc.cadn.net.cn

在 2.0 版本之前的版本中,name 也被注册为一个 Bean 名称别名(类似于 name<bean/> 元素上的用法)。spring-doc.cadn.net.cn

这导致了两个问题:spring-doc.cadn.net.cn

从版本 2.0 开始,如果您同时声明了带有 idname 属性的这些元素之一,则该名称将不再被声明为一个 Bean 名称别名。如果希望声明一个队列和一个交换机并使用相同的 name,则必须提供一个 idspring-doc.cadn.net.cn

如果元素仅包含一个 name 属性,则不会发生任何变化。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

该 Bean 仍可通过 name 引用——例如,在绑定声明中。spring-doc.cadn.net.cn

然而,如果名称包含 SpEL 表达式,您仍然无法引用它——您必须为引用目的提供一个 idspring-doc.cadn.net.cn

spring-doc.cadn.net.cn

AnonymousQueue

一般来说,当您需要一个唯一命名、独占且自动删除的队列时,我们建议您使用 AnonymousQueue,而不是由代理服务器定义的队列名称(使用 "" 作为 Queue 名称会导致代理服务器生成队列名称)。spring-doc.cadn.net.cn

这是因为:spring-doc.cadn.net.cn

  1. 队列实际上是在与代理建立连接时声明的。这发生在 Bean 被创建并装配完成之后很久。</p><p>使用该队列的 Bean 需要知道其名称。</p><p>事实上,当应用程序启动时,代理可能甚至尚未运行。spring-doc.cadn.net.cn

  2. 如果由于某种原因与代理的连接丢失,管理员会重新声明名称相同的 AnonymousQueue。如果我们使用代理声明的队列,队列名称将会改变。spring-doc.cadn.net.cn

您可以控制由 AnonymousQueue 实例使用的队列名称的格式。spring-doc.cadn.net.cn

默认情况下,队列名称以 spring.gen- 开头,后接 UUID 的 Base64 编码表示形式——例如:spring.gen-MRBv9sqISkuCiPfOYfpo4gspring-doc.cadn.net.cn

您可以在构造函数参数中提供一个 AnonymousQueue.NamingStrategy 的实现。
以下示例展示了如何做到这一点:spring-doc.cadn.net.cn

@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-643590fedb0espring-doc.cadn.net.cn

Base64 编码使用 RFC 4648 中定义的“URL 和文件名安全字母表”。尾部填充字符(=)已被移除。spring-doc.cadn.net.cn

您可以提供自己的命名策略,从而在队列名称中包含其他信息(例如应用程序名称或客户端主机)。spring-doc.cadn.net.cn

在使用 XML 配置时,您可以指定命名策略。naming-strategy 属性存在于 <rabbit:queue> 元素上,该元素用于引用实现了 AnonymousQueue.NamingStrategy 的 Bean。以下示例展示了以各种方式指定命名策略的方法:spring-doc.cadn.net.cn

<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 这样的名称。spring-doc.cadn.net.cn

您还可以提供自己的命名策略bean。spring-doc.cadn.net.cn

从版本 2.1 开始,匿名队列默认通过设置参数 Queue.X_QUEUE_LEADER_LOCATORclient-local 进行声明。这可确保队列在应用程序所连接的节点上被声明。您可以通过在构造实例后调用 queue.setLeaderLocator(null) 来恢复之前的行为。spring-doc.cadn.net.cn

恢复自动删除声明

通常情况下,RabbitAdmin(s)仅会恢复应用上下文中声明为 Bean 的队列/交换机/绑定;如果此类声明是自动删除的,当连接丢失时,代理服务器将将其移除。当连接重新建立后,管理员将重新声明这些实体。通常,通过调用 admin.declareQueue(…​)admin.declareExchange(…​)admin.declareBinding(…​) 创建的实体不会被恢复。spring-doc.cadn.net.cn

从版本 2.4 开始,管理员新增了一个属性 redeclareManualDeclarations;当其值为 true 时,管理员将恢复这些实体,而不仅限于应用程序上下文中的 Bean。spring-doc.cadn.net.cn

如果调用 deleteQueue(…​)deleteExchange(…​)removeBinding(…​),将不会对单个声明进行恢复。当队列和交换机被删除时,与可恢复实体关联的绑定会被移除。spring-doc.cadn.net.cn

最后,调用 resetAllManualDeclarations() 将防止恢复任何先前声明的实体。spring-doc.cadn.net.cn

4.1.12. Broker 事件监听器

当启用 事件交换插件 时,如果您向应用上下文添加一个类型为 BrokerEventListener 的 Bean,则会将选定的代理事件作为 BrokerEvent 实例发布出去,这些实例可使用常规的 Spring ApplicationListener@EventListener 方法进行消费。代理会将事件发布到一个主题交换(topic exchange)amq.rabbitmq.event,并为每种事件类型使用不同的路由键(routing key)。监听器使用事件键(event keys),这些键用于将 AnonymousQueue 绑定至交换机,从而使监听器仅接收所选的事件。由于这是主题交换,可以使用通配符(同时也可显式请求特定事件),如下所示示例:spring-doc.cadn.net.cn

@Bean
public BrokerEventListener eventListener() {
    return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#");
}

您还可以通过使用常规的 Spring 技术,在各个事件监听器中进一步缩小接收到的事件范围,如下例所示:spring-doc.cadn.net.cn

@EventListener(condition = "event.eventType == 'queue.created'")
public void listener(BrokerEvent event) {
    ...
}

4.1.13. 延迟消息交换

版本 1.6 引入了对延迟消息交换插件的支持spring-doc.cadn.net.cn

该插件目前被标记为实验性功能,但已可用超过一年(截至本文撰写时)。</p><p>如果插件发生变更导致必要调整,我们计划尽快添加对这些变更的支持。</p><p>因此,Spring AMQP 中的此功能也应被视为实验性功能。</p><p>该功能已在 RabbitMQ 3.6.0 和插件版本 0.0.1 上进行了测试。

要使用 RabbitAdmin 声明一个交换机为延迟交换机,可将交换机 Bean 上的 delayed 属性设置为 true。该 RabbitAdmin 使用交换机类型(DirectFanout 等)来设置 x-delayed-type 参数,并以 x-delayed-message 类型声明交换机。spring-doc.cadn.net.cn

属性 delayed(默认值:false)在使用 XML 配置交换机(exchange)bean 时也适用。
以下示例展示了如何使用它:spring-doc.cadn.net.cn

<rabbit:topic-exchange name="topic" delayed="true" />

要发送延迟消息,您可以通过 x-delay 头部设置 MessageProperties,如下例所示:spring-doc.cadn.net.cn

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;
    }

});

要检查消息是否延迟,可使用 getReceivedDelay() 方法在 MessageProperties 上调用。该属性为独立设置,以避免将延迟信息意外传播至由输入消息生成的输出消息。spring-doc.cadn.net.cn

4.1.14. RabbitMQ REST API

当管理插件启用时,RabbitMQ 服务器会暴露一个 REST API,用于监控和配置代理服务器。API 的 Java 绑定 现已提供。com.rabbitmq.http.client.Client 是一个标准、即时且因此为阻塞式的 API。它基于 Spring Web 模块及其 RestTemplate 实现。另一方面,com.rabbitmq.http.client.ReactorNettyClient 是一个响应式、非阻塞的实现,基于 Reactor Netty 项目。spring-doc.cadn.net.cn

跳过依赖项(com.rabbitmq:http-client)现在也变为 optionalspring-doc.cadn.net.cn

请参阅其 Javadoc 获取更多信息。spring-doc.cadn.net.cn

4.1.15. 异常处理

许多与 RabbitMQ Java 客户端相关的操作都可能抛出受检异常。例如,存在大量可能抛出 IOException 实例的情况。Spring AMQP 的 RabbitTemplateSimpleMessageListenerContainer 及其他组件会捕获这些异常,并将其转换为 AmqpException 层次结构中的一种异常。这些异常在 org.springframework.amqp 包中定义,而 AmqpException 则是该层次结构的基类。spring-doc.cadn.net.cn

当监听器抛出异常时,它会被包装在一个ListenerExecutionFailedException中。通常消息被经纪人拒绝并重新排队。defaultRequeueRejected设置为false会导致消息被丢弃(或路由到死信交换)。正如在消息监听器和异步情况中所讨论的那样,监听器可以抛出一个AmqpRejectAndDontRequeueException(或ImmediateRequeueAmqpException)来有条件地控制此行为。spring-doc.cadn.net.cn

然而,存在一类错误,其中监听器无法控制其行为。当遇到无法转换的消息时(例如,无效的 content_encoding 头部),某些异常会在消息到达用户代码之前被抛出。当 defaultRequeueRejected 设置为 true(默认值)(或抛出 ImmediateRequeueAmqpException)时,此类消息将反复重新投递。在 1.3.2 版本之前,用户需要编写自定义的 ErrorHandler(如 异常处理 中所讨论),以避免此情况。spring-doc.cadn.net.cn

从版本 1.3.2 开始,默认的 ErrorHandler 现在已更改为 ConditionalRejectingErrorHandler,该值会拒绝(且不会重新入队)因不可恢复错误而失败的消息。具体而言,它会拒绝以下列错误失败的消息:spring-doc.cadn.net.cn

  • o.s.amqp…​MessageConversionException: 在使用 MessageConverter 将传入消息负载转换时可能抛出的异常。spring-doc.cadn.net.cn

  • o.s.messaging…​MessageConversionException: 当映射到 @RabbitListener 方法时,如果需要额外的转换,转换服务可能会抛出此异常。spring-doc.cadn.net.cn

  • o.s.messaging…​MethodArgumentNotValidException: 如果在监听器中使用了验证(例如,@Valid),而验证失败时可能会抛出此异常。spring-doc.cadn.net.cn

  • o.s.messaging…​MethodArgumentTypeMismatchException: 如果入站消息被转换为不适用于目标方法的类型,则可能抛出此异常。例如,参数声明为 Message<Foo>,但接收到的是 Message<Bar>spring-doc.cadn.net.cn

  • java.lang.NoSuchMethodException: 已在版本 1.6.3 中添加。spring-doc.cadn.net.cn

  • java.lang.ClassCastException: 已在版本 1.6.3 中添加。spring-doc.cadn.net.cn

您可以使用 FatalExceptionStrategy 配置此错误处理程序的实例,以便用户可以提供自己的规则来有条件地拒绝消息——例如,委托实现来自 Spring Retry 的 BinaryExceptionClassifier消息监听器和异步情况)。
此外,ListenerExecutionFailedException 现在具有一个 failedMessage 属性,您可以在决策中使用它。
如果 FatalExceptionStrategy.isFatal() 方法返回 true,则错误处理程序会抛出 AmqpRejectAndDontRequeueException
默认的 FatalExceptionStrategy 在确定异常为致命时记录警告消息。spring-doc.cadn.net.cn

自版本 1.6.3 起,一种便捷的方式是通过继承 ConditionalRejectingErrorHandler.DefaultExceptionStrategy 并重写 isUserCauseFatal(Throwable cause) 方法,使其对致命异常返回 true,从而将用户自定义异常添加到致命列表中。spring-doc.cadn.net.cn

处理死信队列(DLQ)消息的一种常见模式是,对这些消息设置 time-to-live,同时配置额外的 DLQ 参数,使得这些消息过期后被重新路由回主队列以进行重试。这种技术的问题在于,导致致命异常的消息会无限循环。从版本 2.1 开始,ConditionalRejectingErrorHandler 会检测到消息中包含一个导致致命异常抛出的 x-death 头部信息。随后该消息会被记录并丢弃。您可以通过将 discardFatalsWithXDeath 属性设置在 ConditionalRejectingErrorHandler 上为 false 来恢复到之前的处理行为。spring-doc.cadn.net.cn

从版本 2.1.9 开始,包含这些致命异常的消息将被拒绝,并且默认情况下不会重新入队(即使容器确认模式为 MANUAL)。这些异常通常发生在监听器被调用之前,因此监听器没有机会对消息进行确认(ack)或否定确认(nack),导致消息仍以未确认状态保留在队列中。若要恢复到以前的行为,请将 rejectManual 属性在 ConditionalRejectingErrorHandler 上设置为 false

4.1.16. 事务

Spring Rabbit 框架支持在同步和异步使用场景中自动管理事务,提供多种不同的语义选择,且可通过声明式方式配置,这与现有 Spring 事务用户所熟悉的用法一致。这使得许多(甚至大多数)常见的消息传递模式得以轻松实现。spring-doc.cadn.net.cn

有两种方式可以向框架表明所需的事务语义。在 RabbitTemplateSimpleMessageListenerContainer 中,都存在一个标志 channelTransacted,如果该标志为 true,则会指示框架使用事务通道,并在所有操作(发送或接收)结束后通过提交或回滚(根据结果)完成事务,其中异常将触发回滚。另一种方式是提供一个外部事务,作为当前操作的上下文,使用 Spring 的 PlatformTransactionManager 实现之一。如果框架在发送或接收消息时已有事务正在进行,且 channelTransacted 标志为 true,则消息事务的提交或回滚将推迟到当前事务结束时才执行。若 channelTransacted 标志为 false,则对消息操作不应用任何事务语义(自动确认)。spring-doc.cadn.net.cn

标志 channelTransacted 是一个配置时设置。它在 AMQP 组件创建时(通常在应用启动时)被声明并处理一次。外部事务在原则上更具动态性,因为系统在运行时会根据当前线程状态作出响应。然而,在实际应用中,当事务以声明方式叠加到应用程序上时,它也常常是一个配置设置。spring-doc.cadn.net.cn

对于同步用例且使用 RabbitTemplate 的情况,外部事务由调用者提供,无论是通过声明式方式还是命令式方式(根据个人偏好),其方式与典型的 Spring 事务模型一致。以下示例展示了一种声明式方法(通常更受推荐,因其非侵入性),其中模板已配置为 channelTransacted=truespring-doc.cadn.net.cn

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}

在前面的示例中,收到一个 String 负载,经过转换后作为消息体发送到标记为 @Transactional 的方法内部。如果数据库处理因异常失败,则传入的消息会被返回至消息代理(broker),而传出的消息则不会被发送。此行为适用于链式调用中的任何事务性方法操作(除非例如直接操作 Channel 以提前提交事务)。spring-doc.cadn.net.cn

对于异步用例(使用 SimpleMessageListenerContainer 时),如果需要外部事务,则必须由容器在设置监听器时请求该事务。为了表明需要外部事务,用户需在配置容器时向其提供 PlatformTransactionManager 的实现类。以下示例展示了如何操作:spring-doc.cadn.net.cn

@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 标志也设置为 truespring-doc.cadn.net.cn

spring-doc.cadn.net.cn

其效果是:如果监听器因异常而失败,则事务将回滚,同时消息也会被返还给消息代理(broker)。spring-doc.cadn.net.cn

值得注意的是,如果事务无法提交(例如,由于数据库约束错误或连接问题),AMQP 事务也会被回滚,消息同样会被返还给消息代理。spring-doc.cadn.net.cn

这有时被称为“最佳努力一阶段提交”(Best Efforts 1 Phase Commit),是一种用于可靠消息传递的非常强大的模式。spring-doc.cadn.net.cn

如果在前面的示例中,channelTransacted 标志被设置为 false(默认值),则外部事务仍会提供给监听器,但所有消息操作都将自动确认(auto-acked),因此即使业务操作回滚,消息操作也会被提交。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

条件回滚

在 1.6.6 版本之前,当使用外部事务管理器(如 JDBC)时,向容器的 transactionAttribute 添加回滚规则没有任何效果。异常始终会回滚事务。spring-doc.cadn.net.cn

此外,当在容器的建议链中使用 事务建议 时,条件回滚作用不大,因为所有监听器异常都会被封装在一个 ListenerExecutionFailedException 中。spring-doc.cadn.net.cn

第一个问题已修复,现在规则已正确应用。此外,现已提供 ListenerFailedRuleBasedTransactionAttribute。它继承自 RuleBasedTransactionAttribute,唯一的区别在于它能感知 ListenerExecutionFailedException,并利用此类异常的原因来执行规则。此事务属性可直接在容器中使用,或通过事务通知(transaction advice)进行调用。spring-doc.cadn.net.cn

以下示例使用此规则:spring-doc.cadn.net.cn

@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =
        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(
        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}
关于已接收消息回滚的说明

AMQP事务仅适用于发送到代理的消息和确认。
因此,当Spring事务回滚且已收到消息时,Spring AMQP不仅要回滚事务,还要手动拒绝该消息(某种程度上类似于nack,但这并不是规范所称的)

对拒收消息采取的操作与事务无关,取决于defaultRequeueRejected属性(默认:true)。

有关拒收回退消息的更多信息,请参阅消息监听器和异步情况spring-doc.cadn.net.cn

有关 RabbitMQ 事务及其限制的更多信息,请参阅 RabbitMQ Broker Semanticsspring-doc.cadn.net.cn

在 RabbitMQ 2.7.0 之前,此类消息(以及任何在通道关闭或中止时未被确认的消息)会返回到 Rabbit 代理的队列末尾。</p><p>自 2.7.0 版本起,被拒绝的消息会进入队列头部,其处理方式类似于 JMS 中回滚的消息。
先前,在本地事务和提供 TransactionManager的情况下,消息在事务回滚时的重新排队行为不一致。在前一种情况下,应用了正常的重新排队逻辑(AmqpRejectAndDontRequeueExceptiondefaultRequeueRejected=false)(参见消息监听器和异步情况)。使用事务管理器时,消息会在回滚时无条件地重新排队。从版本 2.0 开始,行为是一致的,并且在这两种情况下都应用了正常的重新排队逻辑。要恢复到以前的行为,可以将容器的 alwaysRequeueWithTxManagerRollback 属性设置为 true。参见消息监听器容器配置
使用RabbitTransactionManager

RabbitTransactionManager》是将Rabbit操作置于外部事务内并与其同步执行的一种替代方案。此事务管理器实现了PlatformTransactionManager接口,并应与单个Rabbit ConnectionFactory配合使用。spring-doc.cadn.net.cn

此策略无法提供 XA 事务——例如,为了在消息传递和数据库访问之间共享事务。

应用程序代码需要通过 ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean) 获取事务性 Rabbit 资源,而不是通过标准的 Connection.createChannel() 调用并随后创建通道。当使用 Spring AMQP 的 RabbitTemplate 时,它会自动检测线程绑定的 Channel 并自动参与其事务。spring-doc.cadn.net.cn

使用 Java 配置,您可以通过以下 Bean 设置一个新的 RabbitTransactionManager:spring-doc.cadn.net.cn

@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    return new RabbitTransactionManager(connectionFactory);
}

如果您更倾向于使用 XML 配置,可以在您的 XML 应用上下文文件中声明以下 Bean:spring-doc.cadn.net.cn

<bean id="rabbitTxManager"
      class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>
事务同步

将 RabbitMQ 事务与某些其他事务(例如数据库管理系统 DBMS)进行同步,可提供“最佳努力一阶段提交”语义。在事务同步的后完成阶段,RabbitMQ 事务可能无法成功提交。此情况由 spring-tx 基础设施记录为错误日志,但不会向调用代码抛出异常。从版本 2.3.10 开始,您可以在同一处理事务的线程上,在事务提交后调用 ConnectionUtils.checkAfterCompletion()。若未发生异常,则该方法直接返回;否则将抛出一个 AfterCompletionFailedException,其包含一个表示完成同步状态的属性。spring-doc.cadn.net.cn

通过调用 ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true) 启用此功能;这是一个全局标志,适用于所有线程。spring-doc.cadn.net.cn

4.1.17. 消息监听容器配置

配置与事务和质量服务相关的SimpleMessageListenerContainer(SMLC)和DirectMessageListenerContainer(DMLC)有相当多的选择,其中一些选择会相互影响。适用于SMLC、DMLC或StreamListenerContainer(StLC)(参见使用RabbitMQ流插件)的属性会在相应的列中标记勾号。有关帮助您决定哪种容器适合您的应用程序的信息,请参阅选择容器spring-doc.cadn.net.cn

下表显示了容器属性名称及其在使用命名空间配置 <rabbit:listener-container/> 时对应的属性名称(括号内为等效属性名)。该元素上的 type 属性可以是 simple(默认值)或 direct,分别用于指定 SMLCDMLC
某些属性未通过命名空间暴露。这些属性由 N/A 表示。spring-doc.cadn.net.cn

表格 3. 消息监听器容器的配置选项
属性<br/>(属性) 描述 SMLC DMLC StLC

ackTimeout
(N/A)spring-doc.cadn.net.cn

当设置为 messagesPerAck 时,此超时时间将用作发送确认(ack)的替代方案。当新消息到达时,未确认消息的数量会与 messagesPerAck 进行比较,且自上次确认以来的时间会与该值进行比较。如果任一条件满足 true,则消息会被确认。当没有新消息到达且存在未确认消息时,此超时时间仅为近似值,因为该条件仅每隔 monitorInterval 才检查一次。另请参见本表中的 messagesPerAckmonitorIntervalspring-doc.cadn.net.cn

tickmark

acknowledgeMode
(acknowledge)spring-doc.cadn.net.cn

  • NONE: 不发送确认(与 channelTransacted=true 不兼容)。
    RabbitMQ 将其称为“自动确认”(autoack),因为代理假定所有消息均已确认,无需消费者采取任何操作。spring-doc.cadn.net.cn

  • MANUAL: 侦听器必须通过调用 Channel.basicAck() 来确认所有消息。spring-doc.cadn.net.cn

  • AUTO: 容器会自动确认消息,除非 MessageListener 抛出异常。
    注意:acknowledgeModechannelTransacted 是互补的——如果通道是事务性的,除了确认(ack)外,代理还要求提交通知。
    这是默认模式。
    另请参阅 batchSizespring-doc.cadn.net.cn

tickmark
tickmark

adviceChain
(advice-chain)spring-doc.cadn.net.cn

应用于监听器执行的一组AOP通知。
这可用于应用额外的横向关注点,例如在代理死亡时自动重试。
注意:只要代理仍然存活,简单的AMQP错误后的重新连接由CachingConnectionFactory处理。spring-doc.cadn.net.cn

tickmark
tickmark

afterReceivePostProcessors
(N/A)spring-doc.cadn.net.cn

一个包含 MessagePostProcessor 个实例的数组,这些实例在调用监听器之前被调用。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

后处理器可以实现 PriorityOrderedOrderedspring-doc.cadn.net.cn

该数组按顺序排序,无序成员最后调用。spring-doc.cadn.net.cn

如果一个后处理器返回 null,则消息将被丢弃(并根据需要进行确认)。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

tickmark
tickmark

alwaysRequeueWithTxManagerRollback
(N/A)spring-doc.cadn.net.cn

设置为 true,以在配置了事务管理器时,在回滚时始终重新入队消息。spring-doc.cadn.net.cn

tickmark
tickmark

autoDeclare
(auto-declare)spring-doc.cadn.net.cn

当设置为 true(默认值)时,容器会在启动期间检测到至少一个队列缺失(例如该队列为 auto-delete 或已过期的队列)的情况下,使用 RabbitAdmin 重新声明所有 AMQP 对象(队列、交换机、绑定),但若因任何原因导致队列缺失,则仍会继续执行重新声明操作。要禁用此行为,请将此属性设置为 false。注意:如果容器的所有队列均缺失,则容器将无法启动。spring-doc.cadn.net.cn

在 1.6 版本之前,如果上下文中存在多个管理员,则容器会随机选择其中一个。如果没有管理员,它会内部创建一个。无论哪种情况,都可能导致意外结果。从 1.6 版本开始,为使 autoDeclare 正常工作,上下文中必须恰好存在一个 RabbitAdmin,或者需通过容器的 rabbitAdmin 属性配置对特定实例的引用。
tickmark
tickmark

autoStartup
(auto-startup)spring-doc.cadn.net.cn

标志,用于指示容器是否应在 ApplicationContext 时启动(作为 SmartLifecycle 回调的一部分,这些回调发生在所有 Bean 初始化之后)。
默认值为 true,但如果您的消息代理在启动时可能不可用,您可以将其设置为 false,并在稍后手动调用 start(),以确保消息代理已就绪。spring-doc.cadn.net.cn

tickmark
tickmark
tickmark

batchSize
(transaction-size) (batch-size)spring-doc.cadn.net.cn

当与 acknowledgeMode 设置为 AUTO 一起使用时,容器会在发送确认(ack)之前尝试处理最多此数量的消息(每个消息的等待时间不超过接收超时设置)。
这也是事务性通道提交的时候。
如果 prefetchCount 小于 batchSize,则会将其增加至与 batchSize 相等。spring-doc.cadn.net.cn

tickmark

batchingStrategy
(N/A)spring-doc.cadn.net.cn

解除批处理消息时所使用的策略。
默认 SimpleDebatchingStrategy
参见 批量处理@RabbitListener 的批量处理spring-doc.cadn.net.cn

tickmark
tickmark

channelTransacted
(channel-transacted)spring-doc.cadn.net.cn

布尔标志,用于指示应在事务中确认所有消息(手动或自动确认)。spring-doc.cadn.net.cn

tickmark
tickmark

concurrency
(N/A)spring-doc.cadn.net.cn

m-n 每个侦听器的并发消费者范围(最小值,最大值)。 如果只提供 n,则 n 是消费者的固定数量。 请参阅 侦听器并发spring-doc.cadn.net.cn

tickmark

concurrentConsumers
(concurrency)spring-doc.cadn.net.cn

每个监听器最初启动的并发消费者数量。
请参阅监听器并发性
对于StLC,并发性通过重载的superStream方法进行控制;请参阅使用单个活动消费者消费超级流spring-doc.cadn.net.cn

tickmark
tickmark

connectionFactory
(connection-factory)spring-doc.cadn.net.cn

ConnectionFactory 的引用。
在使用 XML 命名空间进行配置时,默认引用的 Bean 名称为 rabbitConnectionFactoryspring-doc.cadn.net.cn

tickmark
tickmark

consecutiveActiveTrigger
(min-consecutive-active)spring-doc.cadn.net.cn

消费者在不发生接收超时的情况下,连续接收到的最小消息数,考虑启动新消费者。
也受“batchSize”影响。
监听器并发性。默认值:10。spring-doc.cadn.net.cn

tickmark

consecutiveIdleTrigger
(min-consecutive-idle)spring-doc.cadn.net.cn

消费者在停止之前必须经历的最少接收超时次数。也受“batchSize”影响。监听器并发。默认值:10。spring-doc.cadn.net.cn

tickmark

consumerBatchEnabled
(batch-enabled)spring-doc.cadn.net.cn

如果 MessageListener 支持此功能,则将其设置为 true 可启用离散消息的批处理,最多可达到 batchSize;如果没有新消息到达,receiveTimeout 将会发送部分批次。 当此值为 false 时,仅支持由生产者创建的批次进行批处理;请参阅批量处理spring-doc.cadn.net.cn

tickmark

consumerCustomizer
(N/A)spring-doc.cadn.net.cn

A ConsumerCustomizer bean used to modify stream consumers created by the container.spring-doc.cadn.net.cn

tickmark

consumerStartTimeout
(N/A)spring-doc.cadn.net.cn

消费者线程启动前等待的毫秒数。
如果此时间已过,将记录错误日志。
可能发生这种情况的一个例子是:配置的 taskExecutor 线程数不足以支持容器 concurrentConsumers 的需求。spring-doc.cadn.net.cn

参见线程和异步消费者。默认值:60000(一分钟)。spring-doc.cadn.net.cn

tickmark

consumerTagStrategy
(consumer-tag-strategy)spring-doc.cadn.net.cn

设置ConsumerTagStrategy的实现,为每个消费者创建(唯一)标签。spring-doc.cadn.net.cn

tickmark
tickmark

consumersPerQueue
(consumers-per-queue)spring-doc.cadn.net.cn

为每个配置的队列创建消费者的数量。请参阅监听器并发性spring-doc.cadn.net.cn

tickmark

consumeDelay
(N/A)spring-doc.cadn.net.cn

当使用 RabbitMQ 分片插件concurrentConsumers > 1 配合时,可能会出现竞态条件,导致消费者无法在各分片间均匀分布。请使用此属性在消费者启动之间添加短暂延迟,以避免该竞态条件。您应通过实验来确定适用于您环境的合适延迟时间。spring-doc.cadn.net.cn

tickmark
tickmark

debatchingEnabled
(N/A)spring-doc.cadn.net.cn

如果为 true,则监听器容器会解批处理批量消息,并针对每个消息调用监听器。
从版本 2.2.7 开始,生产者创建的批次 将在监听器是 List<Message>BatchMessageListener 的情况下进行解批处理。
否则,批次中的消息将逐一呈现。默认值为 true。
参见 批量处理带有批量处理的 @RabbitListenerspring-doc.cadn.net.cn

tickmark
tickmark

declarationRetries
(declaration-retries)spring-doc.cadn.net.cn

当主动队列声明失败时进行重试的次数。被动队列声明发生在消费者启动时,或在从多个队列消费时,若初始化期间并非所有队列都可用时发生。当所有配置的队列均无法通过被动声明(因任何原因)且重试次数耗尽后,容器行为由前述的‘missingQueuesFatal’属性控制。默认值:三次重试(总计四次尝试)。spring-doc.cadn.net.cn

tickmark

defaultRequeueRejected
(requeue-rejected)spring-doc.cadn.net.cn

确定因监听器抛出异常而被拒绝的消息是否应重新入队。spring-doc.cadn.net.cn

tickmark
tickmark

errorHandler
(error-handler)spring-doc.cadn.net.cn

对一个ErrorHandler策略的引用,用于处理在MessageListener执行期间可能发生的任何未捕获异常。默认值:ConditionalRejectingErrorHandlerspring-doc.cadn.net.cn

tickmark
tickmark

exclusive
(exclusive)spring-doc.cadn.net.cn

确定此容器中的单个消费者是否独占访问队列。当此值为 true 时,容器的并发数必须为 1。如果其他消费者拥有独占访问权,容器将根据 recovery-intervalrecovery-back-off 的设置尝试恢复该消费者。在使用命名空间时,此属性会与队列名称一起出现在 <rabbit:listener/> 元素上。默认值: falsespring-doc.cadn.net.cn

tickmark
tickmark

exclusiveConsumerExceptionLogger
(N/A)spring-doc.cadn.net.cn

当独占消费者无法访问队列时使用的异常记录器。
默认情况下,此日志以 WARN 级别记录。spring-doc.cadn.net.cn

tickmark
tickmark

failedDeclarationRetryInterval
(failed-declaration -retry-interval)spring-doc.cadn.net.cn

被动队列声明重试尝试之间的间隔时间。<br/>被动队列声明发生在消费者启动时,或者在从多个队列中消费时,初始化期间并非所有队列都可用时。<br/>默认值:5000(五秒)。spring-doc.cadn.net.cn

tickmark
tickmark

forceCloseChannel
(N/A)spring-doc.cadn.net.cn

如果消费者在 shutdownTimeout 时间内未响应关闭请求,且此值为 true,则通道将被关闭,导致任何未确认的消息被重新入队。从 2.0 版本开始,默认值为 true。您可以将其设置为 false 以恢复旧版行为。spring-doc.cadn.net.cn

tickmark
tickmark

forceStop
(N/A)spring-doc.cadn.net.cn

设置为 true 以在容器停止时停止(当前记录处理完毕后);导致所有预取的消息被重新入队。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

默认情况下,容器将在停止前取消消费者并处理所有预取的消息。spring-doc.cadn.net.cn

自版本 2.4.14、3.0.6 起可用。spring-doc.cadn.net.cn

默认值为 falsespring-doc.cadn.net.cn

spring-doc.cadn.net.cn

tickmark
tickmark

globalQos
(global-qos)spring-doc.cadn.net.cn

当为 true 时,prefetchCount 将全局应用于通道,而非应用于通道上的每个消费者。
有关更多信息,请参阅 basicQos.globalspring-doc.cadn.net.cn

tickmark
tickmark

(group)spring-doc.cadn.net.cn

仅在使用命名空间时才可用。
指定后,将注册一个类型为 Collection<MessageListenerContainer> 的 Bean,并将每个 <listener/> 元素的容器添加到集合中。
这允许通过遍历该集合来启动和停止整个容器组。
如果多个 <listener-container/> 元素具有相同的组值,则集合中的容器将构成所有此类指定容器的聚合体。spring-doc.cadn.net.cn

tickmark
tickmark

idleEventInterval
(idle-event-interval)spring-doc.cadn.net.cn

参见检测空闲的异步消费者spring-doc.cadn.net.cn

tickmark
tickmark

javaLangErrorHandler
(N/A)spring-doc.cadn.net.cn

当容器线程捕获到 Error 时调用的 AbstractMessageListenerContainer.JavaLangErrorHandler 实现。默认实现会调用 System.exit(99);若要恢复到先前的行为(即不执行任何操作),请添加一个空操作处理器。spring-doc.cadn.net.cn

tickmark
tickmark

maxConcurrentConsumers
(max-concurrency)spring-doc.cadn.net.cn

如果需要,启动的最大并发消费者数量。
必须大于或等于“concurrentConsumers”。
参见监听器并发spring-doc.cadn.net.cn

tickmark

messagesPerAck
(N/A)spring-doc.cadn.net.cn

在确认(acks)之间接收的消息数量。
使用此设置可减少发送至代理(broker)的确认次数(但会增加消息被重复投递的可能性)。
通常,您仅应在高吞吐量监听器容器中设置此属性。
如果设置了此值,且某条消息被拒绝(抛出异常),则已挂起的确认将被提交,而失败的消息会被拒绝。
不支持与事务性通道(transacted channels)一起使用。
如果 prefetchCount 小于 messagesPerAck,则会将其增大以匹配 messagesPerAck
默认:每条消息均进行确认。
另请参见本表中的 ackTimeoutspring-doc.cadn.net.cn

tickmark

mismatchedQueuesFatal
(mismatched-queues-fatal)spring-doc.cadn.net.cn

当容器启动时,如果此属性为 true(默认值: false),容器会检查上下文中声明的所有队列是否与代理上已存在的队列兼容。如果存在不匹配的属性(例如 auto-delete)或参数(例如 x-message-ttl),容器(以及应用上下文)将因致命异常而无法启动。spring-doc.cadn.net.cn

如果问题是在恢复过程中被检测到的(例如,在连接丢失后),容器将被停止。spring-doc.cadn.net.cn

应用上下文中必须存在一个唯一的 RabbitAdmin(或由容器通过使用 rabbitAdmin 属性显式配置的一个)。
否则,此属性必须为 falsespring-doc.cadn.net.cn

如果代理在初始启动期间不可用,容器将启动,并在连接建立时检查相关条件。
检查是在上下文中的所有队列上执行,而不仅仅是特定侦听器配置使用的队列。

spring-doc.cadn.net.cn

如果您希望仅将检查限制为容器所使用的那些队列,则应为容器配置单独的RabbitAdmin,并使用rabbitAdmin属性提供对其的引用。spring-doc.cadn.net.cn

有关更多信息,请参阅条件声明spring-doc.cadn.net.cn

在启动一个标记为 @Lazy 的 bean 中的 @RabbitListener 容器时,已禁用不匹配队列参数的检测。此举旨在避免可能发生的死锁,该死锁可能导致此类容器的启动延迟长达 60 秒。

spring-doc.cadn.net.cn

使用延迟监听器 bean 的应用程序应在获取延迟 bean 引用之前检查队列参数。spring-doc.cadn.net.cn

tickmark
tickmark

missingQueuesFatal
(missing-queues-fatal)spring-doc.cadn.net.cn

当设置为 true(默认值)时,如果配置的任意队列在代理上均不可用,则视为致命错误。这会导致应用上下文在启动期间无法初始化。此外,当容器运行时队列被删除,默认情况下,消费者将尝试重新连接队列三次(每次间隔五秒),若这些尝试失败,则会停止容器。spring-doc.cadn.net.cn

在以前的版本中,这是不可配置的。spring-doc.cadn.net.cn

当设置为 false 时,在完成三次重试后,容器将进入恢复模式,如同其他问题(如代理服务宕机)一样。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

容器会根据 recoveryInterval 属性的配置尝试进行恢复。spring-doc.cadn.net.cn

在每次恢复尝试过程中,每个消费者会再次以五秒为间隔、连续尝试四次,被动声明队列。spring-doc.cadn.net.cn

该过程将持续无限进行下去。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

您还可以使用属性bean来全局设置所有容器的属性,如下所示:spring-doc.cadn.net.cn

<util:properties
        id="spring.amqp.global.properties">
    <prop key="mlc.missing.queues.fatal">
        false
    </prop>
</util:properties>

此全局属性不适用于任何已显式设置 missingQueuesFatal 属性的容器。spring-doc.cadn.net.cn

默认重试属性(三次重试,每次间隔五秒)可以通过设置以下属性进行覆盖。spring-doc.cadn.net.cn

在启动一个标记为 @Lazy 的 bean 中的 @RabbitListener 容器时,禁用了缺失队列检测。这旨在避免可能的死锁,该死锁可能导致此类容器的启动延迟长达 60 秒。

spring-doc.cadn.net.cn

使用延迟监听器 bean 的应用程序应在获取延迟 bean 引用之前检查相关队列。spring-doc.cadn.net.cn

tickmark
tickmark

monitorInterval
(monitor-interval)spring-doc.cadn.net.cn

通过 DMLC,任务会按此间隔安排运行,以监控消费者的当前状态并恢复任何已失败的消费者。spring-doc.cadn.net.cn

tickmark

noLocal
(N/A)spring-doc.cadn.net.cn

设置为 true 以禁用从服务器向消费者传递在相同频道连接上发布的消息。spring-doc.cadn.net.cn

tickmark
tickmark

phase
(phase)spring-doc.cadn.net.cn

autoStartuptrue 时,表示此容器应在哪个生命周期阶段内启动和停止。数值越小,该容器启动得越早,停止得越晚。默认值为 Integer.MAX_VALUE,表示该容器尽可能晚地启动,并尽可能早地停止。spring-doc.cadn.net.cn

tickmark
tickmark

possibleAuthenticationFailureFatal
(possible-authentication-failure-fatal)spring-doc.cadn.net.cn

当设置为 true(SMLC 的默认值)时,如果在连接过程中抛出 PossibleAuthenticationFailureException,则视为致命错误。这会导致应用程序上下文在启动期间无法完成初始化(若容器配置为自动启动)。spring-doc.cadn.net.cn

2.0 版本起。spring-doc.cadn.net.cn

直接消息监听容器spring-doc.cadn.net.cn

当设置为 false(默认值)时,每个消费者将根据 monitorInterval 尝试重新连接。spring-doc.cadn.net.cn

SimpleMessageListenerContainerspring-doc.cadn.net.cn

当设置为 false 时,在完成 3 次重试后,容器将进入恢复模式,如同其他问题(例如代理服务器宕机)一样。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

容器将根据 recoveryInterval 属性进行恢复尝试。spring-doc.cadn.net.cn

在每次恢复尝试过程中,每个消费者将再次尝试启动 4 次。spring-doc.cadn.net.cn

此过程将持续无限进行。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

您还可以使用属性bean来全局设置所有容器的属性,如下所示:spring-doc.cadn.net.cn

<util:properties
    id="spring.amqp.global.properties">
  <prop
    key="mlc.possible.authentication.failure.fatal">
     false
  </prop>
</util:properties>

此全局属性将不会应用于任何已显式设置 missingQueuesFatal 属性的容器。spring-doc.cadn.net.cn

默认重试属性(3次重试,每次间隔5秒)可使用此后的属性进行覆盖。spring-doc.cadn.net.cn

tickmark
tickmark

prefetchCount
(prefetch)spring-doc.cadn.net.cn

每个消费者可同时存在的未确认消息数量。
该值越高,消息传递速度越快,但非顺序处理的风险也越高。
如果 acknowledgeModeNONE,则忽略此设置。
必要时,该值会调整以匹配 batchSizemessagePerAck
自 2.0 版本起默认值为 250。
您可以将其设为 1,以恢复之前的处理行为。spring-doc.cadn.net.cn

在某些场景下,预取值应设置得较低——例如,当消息较大时,尤其是处理速度较慢(消息可能在客户端进程中累积成大量内存),并且需要严格的消息顺序时(此时应将预取值重置为1)。</p><p>此外,在低频消息传递且存在多个消费者(包括单个监听容器实例内的并发)的情况下,您可能希望降低预取值,以实现消息在各消费者之间更均衡的分配。

另请参见 globalQosspring-doc.cadn.net.cn

tickmark
tickmark

rabbitAdmin
(admin)spring-doc.cadn.net.cn

当监听器容器侦听至少一个自动删除队列,并且在启动期间发现该队列缺失时,容器使用 RabbitAdmin 声明该队列以及任何相关的绑定和交换。
如果这些元素被配置为使用条件声明(参见 条件声明),则容器必须使用已配置的管理员来声明这些元素。
在这里指定该管理员。仅在对带有条件声明的自动删除队列进行操作时需要。
如果您不希望在容器启动之前声明自动删除队列,请将管理员上的 auto-startup 设置为 false
默认情况下,RabbitAdmin 将声明所有非条件性元素。spring-doc.cadn.net.cn

tickmark
tickmark

receiveTimeout
(receive-timeout)spring-doc.cadn.net.cn

等待每个消息的最大时间。
如果 acknowledgeMode=NONE,此设置影响甚微——容器会不断循环并请求另一条消息。
对于事务性 Channel 且配置为 batchSize > 1 的情况,该设置影响最大,可能导致已消费的消息在超时到期前未被确认。
consumerBatchEnabled 为 true 时,若在此超时发生前批处理尚未完成,则将提前交付部分批处理。spring-doc.cadn.net.cn

tickmark

recoveryBackOff
(recovery-back-off)spring-doc.cadn.net.cn

指定 BackOff 表示在消费者因非致命原因启动失败时,重试启动消费者的时间间隔。默认值为 FixedBackOff,即每五秒无限次重试。与 recoveryInterval 相互排斥。spring-doc.cadn.net.cn

tickmark
tickmark

recoveryInterval
(recovery-interval)spring-doc.cadn.net.cn

确定在消费者因非致命原因启动失败时,再次尝试启动消费者之间的时间间隔(单位:毫秒)。
默认值:5000。
recoveryBackOff 互斥。spring-doc.cadn.net.cn

tickmark
tickmark

retryDeclarationInterval
(missing-queue- retry-interval)spring-doc.cadn.net.cn

如果在消费者初始化期间,配置的队列子集可用,则消费者将从这些队列开始消费。消费者会尝试通过使用此间隔被动声明缺失的队列。当该间隔结束时,将再次使用“declarationRetries”和“failedDeclarationRetryInterval”。如果仍存在缺失的队列,消费者将再次等待此间隔后重试。该过程将持续无限进行,直到所有队列都可用。默认值:60000(一分钟)。spring-doc.cadn.net.cn

tickmark

shutdownTimeout
(N/A)spring-doc.cadn.net.cn

当容器关闭时(例如,如果其包含的 ApplicationContext 被关闭),它将等待正在进行的消息处理,直至达到此限制。默认值为五秒。spring-doc.cadn.net.cn

tickmark
tickmark

startConsumerMinInterval
(min-start-interval)spring-doc.cadn.net.cn

每次按需启动新的消费者之前必须经过的毫秒数。
请参见监听器并发
默认值:10000(10秒)。spring-doc.cadn.net.cn

tickmark

statefulRetryFatal
无消息ID (N/A)spring-doc.cadn.net.cn

在使用有状态重试建议时,如果接收到一条缺少 messageId 属性的消息,默认情况下该消息会被视为对消费者致命的(即消费者将被停止)。将此设置为 false 可以丢弃(或路由至死信队列)此类消息。spring-doc.cadn.net.cn

tickmark
tickmark

stopConsumerMinInterval
(min-stop-interval)spring-doc.cadn.net.cn

检测到空闲消费者时,必须经过的毫秒数,从上一个消费者停止以来,直到停止当前消费者。
参见监听器并发性。默认值:60000(一分钟)。spring-doc.cadn.net.cn

tickmark

streamConverter
(N/A)spring-doc.cadn.net.cn

A StreamMessageConverter 用于将原生流消息转换为 Spring AMQP 消息。spring-doc.cadn.net.cn

tickmark

taskExecutor
(task-executor)spring-doc.cadn.net.cn

对 Spring TaskExecutor(或标准 JDK 1.5+ Executor)的引用,用于执行监听器调用器。默认为 SimpleAsyncTaskExecutor,使用内部管理的线程。spring-doc.cadn.net.cn

tickmark
tickmark

taskScheduler
(task-scheduler)spring-doc.cadn.net.cn

使用 DMLC 时,调度器曾用于在 'monitorInterval' 时间间隔运行监控任务。spring-doc.cadn.net.cn

tickmark

transactionManager
(transaction-manager)spring-doc.cadn.net.cn

监听器操作的外部事务管理器。
也与 channelTransacted 相辅相成——如果 Channel 是事务性的,其事务将与外部事务同步。spring-doc.cadn.net.cn

tickmark
tickmark

4.1.18. 监听器并发性

SimpleMessageListenerContainer

默认情况下,监听器容器会启动一个单一的消费者,从队列中接收消息。spring-doc.cadn.net.cn

在查看前一节中的表格时,您可以看到许多用于控制并发性的属性和特性。最简单的是 concurrentConsumers,它会创建固定数量的消费者,这些消费者可并行处理消息。spring-doc.cadn.net.cn

在 1.3.0 版本之前,这是唯一可用的设置,且必须停止并重新启动容器才能更改该设置。spring-doc.cadn.net.cn

自版本 1.3.0 起,您现在可以动态调整 concurrentConsumers 属性。如果在容器运行期间对其进行更改,则会根据需要添加或移除消费者,以适应新的设置。spring-doc.cadn.net.cn

此外,还新增了一个名为 maxConcurrentConsumers 的属性,容器会根据工作负载动态调整并发度。这与另外四个属性协同工作:consecutiveActiveTriggerstartConsumerMinIntervalconsecutiveIdleTriggerstopConsumerMinInterval。在默认设置下,增加消费者数量的算法如下:spring-doc.cadn.net.cn

如果 maxConcurrentConsumers 尚未达到,并且在连续十个周期内已有活跃的消费者,且自上一次启动消费者以来已过去至少 10 秒,则会启动一个新的消费者。若消费者在 batchSize * receiveTimeout 毫秒的时间内至少接收了一条消息,则认为该消费者处于活跃状态。spring-doc.cadn.net.cn

在默认设置下,减少消费者(consumers)的算法工作方式如下:spring-doc.cadn.net.cn

如果运行中的消费者数量超过 concurrentConsumers,且一个消费者检测到连续十次超时(空闲)状态,并且最后停止的消费者至少在 60 秒前已停止,则会停止该消费者。超时时间取决于 receiveTimeoutbatchSize 这两个属性。若消费者在 batchSize * receiveTimeout 毫秒内未收到任何消息,则视为处于空闲状态。因此,以默认超时时间(1 秒)和 batchSize 值为 4 的情况为例,当消费者空闲 40 秒后(即四次超时对应一次空闲检测)将被停止。spring-doc.cadn.net.cn

实际上,只有在整个容器空闲一段时间后,消费者才能被停止。这是因为代理服务器在其所有活跃消费者之间分配其工作。

每个消费者都使用一个通道,无论配置了多少个队列。spring-doc.cadn.net.cn

从版本 2.0 开始,concurrentConsumersmaxConcurrentConsumers 属性可以通过 concurrency 属性进行设置——例如,2-4spring-doc.cadn.net.cn

使用DirectMessageListenerContainer

使用此容器时,并发性基于配置的队列和 consumersPerQueue。每个队列的每个消费者均使用独立的通道,而并发性由 Rabbit 客户端库进行控制。默认情况下,在撰写本文时,它使用一个大小为 DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2 的线程池。spring-doc.cadn.net.cn

您可以配置一个 taskExecutor 来提供所需的最高并发数。spring-doc.cadn.net.cn

4.1.19. 独占消费者

从版本 1.3 开始,您可以将监听器容器配置为单个独占消费者。这可防止其他容器在当前消费者被取消之前从队列中消费消息。此类容器的并发数必须为 1spring-doc.cadn.net.cn

在使用独占消费者时,其他容器会根据 recoveryInterval 属性尝试从队列中消费消息,并在尝试失败时记录一条 WARN 消息。spring-doc.cadn.net.cn

4.1.20. 监听器容器队列

版本 1.3 引入了许多改进,以增强监听容器对多个队列的处理能力。spring-doc.cadn.net.cn

容器可以初始配置为监听零个队列。队列可以在运行时动态添加和移除。SimpleMessageListenerContainer 在任何预先获取的消息处理完毕后,会回收(取消并重新创建)所有消费者。DirectMessageListenerContainer 为每个队列单独创建/取消消费者,而不会影响其他队列上的消费者。请参阅 Javadoc 中关于 addQueuesaddQueueNamesremoveQueuesremoveQueueNames 方法的说明。spring-doc.cadn.net.cn

如果并非所有队列都可用,容器将每60秒尝试被动声明(并从中消费)缺失的队列。spring-doc.cadn.net.cn

此外,如果消费者从代理(例如,队列被删除时)接收到取消通知,该消费者会尝试恢复,恢复后的消费者将继续从任何其他已配置的队列中处理消息。之前,对某个队列的取消操作会导致整个消费者被取消,最终由于缺少队列,容器将停止运行。spring-doc.cadn.net.cn

如果您希望永久删除队列,应在删除队列之前或之后更新容器,以避免未来尝试从该队列中消费消息。spring-doc.cadn.net.cn

4.1.21. 弹性:从错误和代理故障中恢复

Spring AMQP 提供的一些关键(且最流行)的高层功能,主要涉及在协议错误或消息代理故障发生时的恢复与自动重连功能。我们已在本指南中介绍了所有相关组件,但在此将它们汇总起来并单独指出各项功能及恢复场景,应能有所帮助。spring-doc.cadn.net.cn

主要的重连功能由 CachingConnectionFactory 本身启用。此外,通常也建议使用 RabbitAdmin 的自动声明功能。另外,如果您关注确保消息送达,可能还需要在 RabbitTemplateSimpleMessageListenerContainer 中使用 channelTransacted 标志,以及在 SimpleMessageListenerContainer 中使用 AcknowledgeMode.AUTO(或手动处理确认,若您自行处理确认机制)。spring-doc.cadn.net.cn

自动声明交换机、队列和绑定

组件 RabbitAdmin 可以在启动时声明交换机、队列和绑定。它通过 ConnectionListener 懒惰地完成此操作。因此,如果在启动时代理(broker)不存在,也无妨。第一次使用 Connection 时(例如,通过发送消息),监听器将被触发,并应用管理功能。在监听器中执行自动声明的另一大好处是,若因任何原因(例如,代理宕机、网络故障等)导致连接断开,当连接重新建立时,这些声明会再次生效。spring-doc.cadn.net.cn

以这种方式声明的队列必须具有固定名称——要么显式声明,要么由框架为 AnonymousQueue 个实例生成。

spring-doc.cadn.net.cn

匿名队列是非持久的、独占的,并且在使用后会自动删除。spring-doc.cadn.net.cn

自动声明仅在 CachingConnectionFactory 缓存模式为 CHANNEL(默认值)时执行。

spring-doc.cadn.net.cn

此限制存在,因为独占队列和自动删除队列绑定到连接上。spring-doc.cadn.net.cn

从版本 2.2.2 开始,RabbitAdmin 将检测类型为 DeclarableCustomizer 的 Bean,并在实际处理声明之前应用该函数。这很有用,例如,在框架中首次支持该功能之前,可设置新的参数(属性)。spring-doc.cadn.net.cn

@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 定义访问权限的项目中,这也非常有用。spring-doc.cadn.net.cn

同步操作中的失败及重试选项

如果您在使用 RabbitTemplate(例如)时于同步序列中丢失了与代理服务器的连接,Spring AMQP 将抛出一个 AmqpException(通常但不总是 AmqpIOException)。
我们不会试图掩盖问题的发生,因此您必须能够捕获并响应该异常。
如果您怀疑连接已丢失(且并非由您造成),最简单的做法是重新尝试该操作。
您可以手动完成此操作,也可以考虑使用 Spring Retry 来处理重试(无论是以编程方式还是声明式方式)。spring-doc.cadn.net.cn

Spring Retry 提供了几种 AOP 拦截器,并具有极大的灵活性,可用于指定重试参数(包括重试次数、异常类型、退避算法等)。Spring AMQP 还提供了一些便捷的工厂 bean,用于以适合 AMQP 使用场景的便捷形式创建 Spring 重试拦截器,并提供强类型回调接口,您可使用这些接口实现自定义恢复逻辑。查看 StatefulRetryOperationsInterceptorStatelessRetryOperationsInterceptor 的 Javadoc 和属性以获取更多详细信息。无状态重试适用于没有事务或在重试回调内部启动事务的情况。请注意,无状态重试比有状态重试更易于配置和分析,但若存在必须回滚或肯定将回滚的正在进行中的事务,则通常不适用。在事务进行过程中发生的连接中断,应产生与回滚相同的效果。因此,对于事务在调用栈更高处启动的重连场景,有状态重试通常是最佳选择。有状态重试需要一种机制来唯一标识一条消息。最简单的做法是让发送方在 MessageId 消息属性中放入一个唯一值。提供的消息转换器提供了此选项:您可以将 createMessageIds 设置为 true。否则,您可以将 MessageKeyGenerator 实现注入到拦截器中。密钥生成器必须为每条消息返回一个唯一密钥。在2.0版本之前的版本中。0,提供了 MissingMessageIdAdvice。它使得未包含 messageId 属性的消息可以被精确重试一次(忽略重试设置)。此建议已不再提供,因为与 spring-retry 版本 1 一同被移除。2, 其功能已集成到拦截器和消息监听容器中。spring-doc.cadn.net.cn

为了向后兼容性,一条消息 ID 为 null 的消息默认情况下会被视为对消费者致命的(消费者将被停止),即在重试一次后。

spring-doc.cadn.net.cn

要复现 MissingMessageIdAdvice 所提供的功能,您可以在监听器容器上将 statefulRetryFatalWithNullMessageId 属性设置为 falsespring-doc.cadn.net.cn

在此设置下,消费者将继续运行,而消息会被拒绝(在重试一次后)。spring-doc.cadn.net.cn

该消息将被丢弃或路由到死信队列(如果已配置的话)。spring-doc.cadn.net.cn

从版本 1.3 开始,提供了一个构建器 API,用于辅助通过 Java(在 @Configuration 类中)组装这些拦截器。以下示例展示了如何实现此操作:spring-doc.cadn.net.cn

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}

只能以这种方式配置部分重试功能。更高级的功能需要将 RetryTemplate 配置为 Spring Bean。有关可用策略及其配置的完整信息,请参阅 Spring Retry Javadocspring-doc.cadn.net.cn

重试批处理监听器

不建议为批处理监听器配置重试,除非批处理是由生产者创建的,并且仅包含单个记录。批处理消息可获取有关消费者和生产者创建的批处理的信息。对于由消费者创建的批处理,框架无法知道是批处理中的哪条消息导致了失败,因此在重试次数用尽后无法恢复。对于由生产者创建的批处理,由于实际上只有一条消息失败,可以恢复整个消息。应用程序可能需要通知自定义恢复程序故障发生时在批处理中的位置,例如通过设置抛出异常的索引属性。spring-doc.cadn.net.cn

批处理监听器的重试恢复器必须实现 MessageBatchRecovererspring-doc.cadn.net.cn

消息监听器与异步情况

如果 MessageListener 因业务异常而失败,该异常由消息监听容器处理,随后容器会返回继续监听其他消息。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

如果失败是由连接断开(非业务异常)导致的,则负责为监听器收集消息的消费者必须被取消并重新启动。spring-doc.cadn.net.cn

SimpleMessageListenerContainer 可无缝处理此情况,并在日志中记录监听器正在重启的信息。spring-doc.cadn.net.cn

实际上,它会无限循环,持续尝试重启消费者。spring-doc.cadn.net.cn

只有当消费者行为极其恶劣时,它才会放弃。spring-doc.cadn.net.cn

一个副作用是:如果在容器启动时消息代理(broker)不可用,容器将持续重试,直到成功建立连接为止。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

业务异常处理(与协议错误和连接断开不同)可能需要更多的思考和一些自定义配置,特别是当使用事务或容器确认时。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

在 2.8.x 版本之前,RabbitMQ 尚未定义死信(dead letter)行为。spring-doc.cadn.net.cn

因此,默认情况下,因业务异常被拒绝或回滚的消息可能会被无限次重新投递。spring-doc.cadn.net.cn

为限制客户端的重投递次数,一种选择是在监听器的建议链(advice chain)中设置一个 StatefulRetryOperationsInterceptorspring-doc.cadn.net.cn

拦截器可包含一个恢复回调(recovery callback),用于实现自定义的死信处理动作——具体应根据您所处的特定环境进行调整。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

另一种选择是将容器的 defaultRequeueRejected 属性设置为 false。这会导致所有失败的消息被丢弃。在使用 RabbitMQ 2.8.x 或更高版本时,这也便于将消息投递到死信交换器(dead letter exchange)。spring-doc.cadn.net.cn

或者,您可以抛出一个 AmqpRejectAndDontRequeueException。此举可防止消息重新入队,无论 defaultRequeueRejected 属性的设置如何。spring-doc.cadn.net.cn

从版本 2.1 开始,引入了一个 ImmediateRequeueAmqpException 来执行完全相反的逻辑:无论 defaultRequeueRejected 属性如何设置,消息都将被重新入队。spring-doc.cadn.net.cn

通常,会同时使用这两种技术的组合。
您可以在通知链中使用一个 StatefulRetryOperationsInterceptor,并配合一个抛出 AmqpRejectAndDontRequeueExceptionMessageRecoverer
当所有重试均告失败时,MessageRecover 会被调用。
RejectAndDontRequeueRecoverer 正是执行此操作的组件。
默认的 MessageRecoverer 会处理异常消息,并发出一个 WARN 消息。spring-doc.cadn.net.cn

从版本 1.3 开始,提供了一种新的 RepublishMessageRecoverer,以便在重试次数用尽后发布失败的消息。spring-doc.cadn.net.cn

当恢复器处理最后一个异常时,消息会被确认(ack),并且如果已配置,则不会由代理发送至死信交换机。spring-doc.cadn.net.cn

当在消费者端使用 RepublishMessageRecoverer 时,接收到的消息在 receivedDeliveryMode 消息属性中包含 deliveryMode。此时,deliveryModenull。这意味着代理端采用 NON_PERSISTENT 传递模式。从 2.0 版本开始,您可以配置 RepublishMessageRecovererdeliveryMode,以在消息被重新发布且满足 null 条件时将其设置到消息中。默认情况下,它使用 MessageProperties 默认值——MessageDeliveryMode.PERSISTENT

以下示例展示了如何将 RepublishMessageRecoverer 设置为恢复器:spring-doc.cadn.net.cn

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

RepublishMessageRecoverer 会以消息头中附加额外信息(如异常消息、堆栈跟踪、原始交换信息及路由键)的方式发布消息。可通过创建子类并重写 additionalHeaders() 来添加更多消息头。此外,deliveryMode(或其他任何属性)也可在 additionalHeaders() 中进行修改,如下例所示:spring-doc.cadn.net.cn

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 字节(“预留空间”),则会进行截断。您可以通过设置恢复器的 frameMaxHeadroom 属性来调整此值,以根据需要为其他头信息分配更多或更少的空间。从版本 2.1.13 和 2.2.3 开始,异常消息已包含在此计算中,并且将使用以下算法最大化堆栈跟踪的长度:spring-doc.cadn.net.cn

  • 如果堆栈跟踪本身超过限制,异常消息标题将被截断为97字节加上…​,同时堆栈跟踪也会被截断。spring-doc.cadn.net.cn

  • 如果堆栈跟踪较短,消息将被截断(加上 …​)以适应可用字节数(但堆栈跟踪内部的消息本身将被截断为 97 字节加 …​)。spring-doc.cadn.net.cn

无论发生何种截断,原始异常都会被记录下来,以保留完整信息。评估在头部增强之后进行,以便可将异常类型等信息用于表达式中。spring-doc.cadn.net.cn

从版本 2.4.8 开始,错误交换和路由键可作为 SpEL 表达式提供,其中 Message 是用于求值的根对象。spring-doc.cadn.net.cn

从版本 2.3.3 开始,提供了一个新的子类 RepublishMessageRecovererWithConfirms;该子类同时支持两种形式的发布者确认机制,并会在等待确认返回结果(或在未确认、消息被退回时抛出异常)之前进行阻塞。spring-doc.cadn.net.cn

如果确认类型为 CORRELATED,子类也会检测是否返回了消息,并抛出 AmqpMessageReturnedException;如果发布被否定确认,则抛出 AmqpNackReceivedExceptionspring-doc.cadn.net.cn

如果确认类型为 SIMPLE,子类将调用通道上的 waitForConfirmsOrDie 方法。spring-doc.cadn.net.cn

有关确认和返回的更多信息,请参阅发布者确认和返回spring-doc.cadn.net.cn

从版本 2.1 开始,会向 ImmediateRequeueMessageRecoverer 添加一个以抛出 ImmediateRequeueAmqpException,从而通知监听器容器重新将当前失败的消息重新入队。spring-doc.cadn.net.cn

Spring 重试异常分类

Spring Retry 在确定哪些异常可以触发重试方面具有极大的灵活性。默认配置会对所有异常进行重试。由于用户异常会被包装在 ListenerExecutionFailedException 中,因此我们需要确保分类器检查异常的底层原因。默认的分类器仅检查最顶层的异常。spring-doc.cadn.net.cn

自 Spring Retry 1.0.3 起,BinaryExceptionClassifier 具有一个名为 traverseCauses 的属性(默认值为 false)。当 true 时,它会遍历异常原因,直到找到匹配项或无更多原因为止。spring-doc.cadn.net.cn

要使用此分类器进行重试,您可以使用一个通过接受最大尝试次数的构造函数创建的 SimpleRetryPolicyMap(即 Exception 实例的 traverseCauses)以及布尔值(traverseCauses),并将此策略注入到 RetryTemplate 中。spring-doc.cadn.net.cn

4.1.22. 支持多个代理(或集群)

版本 2.3 在单个应用程序与多个代理或代理集群通信时提供了更多便利。消费者端的主要优势在于,基础设施可以自动将自动声明的队列关联到相应的代理。spring-doc.cadn.net.cn

这最好通过一个示例来说明:spring-doc.cadn.net.cn

@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(SimpleRoutingConnectionFactory 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,这会导致队列在代理上被声明(如果它不存在的话)。
通过将 RabbitAdmin 的 Bean 按照 <container-factory-name>-admin 的命名约定进行命名,基础设施能够确定应由哪个管理员来声明队列。
该方法同样适用于 bindings = @QueueBinding(…​),此时交换机和绑定也将被声明。
但不适用于 queues,因为后者期望队列(或队列)已经存在。spring-doc.cadn.net.cn

在生产者端,提供了一个方便的ConnectionFactoryContextWrapper类,以便更简单地使用RoutingConnectionFactory(参见路由连接工厂)。spring-doc.cadn.net.cn

如上所示,已添加一个 SimpleRoutingConnectionFactory Bean,并使用路由键 onetwothree。此外,还有一个 RabbitTemplate 使用了该工厂。以下是一个使用该模板并通过包装器路由到其中一个代理集群的示例。spring-doc.cadn.net.cn

@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 级别。spring-doc.cadn.net.cn

如果您希望监控应用程序与代理之间的 AMQP 协议,可以使用 Wireshark 等工具,该工具提供插件以解码该协议。此外,RabbitMQ Java 客户端自带一个非常有用的类,即 Tracer。当作为 main 运行时,默认情况下它监听本地主机的 5673 端口,并连接到本地主机的 5672 端口。您可以运行该工具,并将您的连接工厂配置更改为连接到本地主机的 5673 端口。它会在控制台中显示解码后的协议信息。更多详情请参阅 Tracer 的 Javadoc 文档。spring-doc.cadn.net.cn

4.2. 使用 RabbitMQ 流插件

版本 2.4 引入了对 RabbitMQ Stream 插件 Java 客户端 的初始支持,该客户端适用于 RabbitMQ Stream 插件spring-doc.cadn.net.cn

spring-rabbit-stream 依赖项添加到您的项目中:spring-doc.cadn.net.cn

示例 3。maven
<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit-stream</artifactId>
  <version>3.0.14</version>
</dependency>
示例 4. gradle
compile 'org.springframework.amqp:spring-rabbit-stream:3.0.14'

您可以像平常一样配置队列,使用 RabbitAdmin Bean,并通过 QueueBuilder.stream() 方法指定队列类型。例如:spring-doc.cadn.net.cn

@Bean
Queue stream() {
    return QueueBuilder.durable("stream.queue1")
            .stream()
            .build();
}

然而,这仅在您同时使用非流式组件(例如 SimpleMessageListenerContainerDirectMessageListenerContainer)时才有效,因为当建立 AMQP 连接时,管理器会触发以声明已定义的 Bean。
如果您的应用程序仅使用流式组件,或希望使用高级流式配置功能,则应改用配置 StreamAdminspring-doc.cadn.net.cn

@Bean
StreamAdmin streamAdmin(Environment env) {
    return new StreamAdmin(env, sc -> {
        sc.stream("stream.queue1").maxAge(Duration.ofHours(2)).create();
        sc.stream("stream.queue2").create();
    });
}

请参考 RabbitMQ 文档以获取有关 StreamCreator 的更多信息。spring-doc.cadn.net.cn

4.2.1. 发送消息

The RabbitStreamTemplate provides a subset of the RabbitTemplate (AMQP) functionality.spring-doc.cadn.net.cn

示例5. RabbitStreamOperations
public interface RabbitStreamOperations extends AutoCloseable {

	CompletableFuture<Boolean> send(Message message);

	CompletableFuture<Boolean> convertAndSend(Object message);

	CompletableFuture<Boolean> convertAndSend(Object message, @Nullable MessagePostProcessor mpp);

	CompletableFuture<Boolean> send(com.rabbitmq.stream.Message message);

	MessageBuilder messageBuilder();

	MessageConverter messageConverter();

	StreamMessageConverter streamMessageConverter();

	@Override
	void close() throws AmqpException;

}

RabbitStreamTemplate 实现包含以下构造函数和属性:spring-doc.cadn.net.cn

示例 6. RabbitStreamTemplate
public RabbitStreamTemplate(Environment environment, String streamName) {
}

public void setMessageConverter(MessageConverter messageConverter) {
}

public void setStreamConverter(StreamMessageConverter streamConverter) {
}

public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) {
}

MessageConverter 中使用于 convertAndSend 方法中,将对象转换为 Spring AMQP Messagespring-doc.cadn.net.cn

The StreamMessageConverter is used to convert from a Spring AMQP Message to a native stream Message.spring-doc.cadn.net.cn

您还可以直接发送原生流 Message;其中 messageBuilder() 方法提供对 Producer 的消息构建器的访问。spring-doc.cadn.net.cn

The ProducerCustomizer provides a mechanism to customize the producer before it is built.spring-doc.cadn.net.cn

参阅 Java 客户端文档,了解如何自定义 EnvironmentProducerspring-doc.cadn.net.cn

从版本 3.0 开始,方法返回类型为 CompletableFuture,而不是 ListenableFuture

4.2.2. 接收消息

异步消息接收由 StreamListenerContainer(在使用 @RabbitListener 时还包括 StreamRabbitListenerContainerFactory)提供。spring-doc.cadn.net.cn

监听器容器还需要一个 Environment 以及一个流名称。spring-doc.cadn.net.cn

您可以使用经典方式 MessageListener 接收 Spring AMQP Message 消息,也可以使用新接口接收原生流式 Message 消息:spring-doc.cadn.net.cn

public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}

有关支持的属性信息,请参阅消息监听容器配置spring-doc.cadn.net.cn

与模板类似,容器具有一个 ConsumerCustomizer 属性。spring-doc.cadn.net.cn

参阅 Java 客户端文档,了解如何自定义 EnvironmentConsumerspring-doc.cadn.net.cn

当使用 @RabbitListener 时,需配置一个 StreamRabbitListenerContainerFactory;此时,大多数 @RabbitListener 属性(concurrency 等)将被忽略。仅支持 idqueuesautoStartupcontainerFactory
此外,queues 中只能包含一个流名称。spring-doc.cadn.net.cn

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();
}

@Bean
Queue stream() {
    return QueueBuilder.durable("test.stream.queue1")
            .stream()
            .build();
}

@Bean
Queue stream() {
    return QueueBuilder.durable("test.stream.queue2")
            .stream()
            .build();
}

版本 2.4.5 为 StreamListenerContainer(及其工厂)新增了 adviceChain 属性。此外,还提供了一个新的工厂 Bean,用于创建无状态重试拦截器,并可选地使用 StreamMessageRecoverer 来处理原始流消息的消费。spring-doc.cadn.net.cn

@Bean
public StreamRetryOperationsInterceptorFactoryBean sfb(RetryTemplate retryTemplate) {
    StreamRetryOperationsInterceptorFactoryBean rfb =
            new StreamRetryOperationsInterceptorFactoryBean();
    rfb.setRetryOperations(retryTemplate);
    rfb.setStreamMessageRecoverer((msg, context, throwable) -> {
        ...
    });
    return rfb;
}
状态化重试在此容器中不受支持。

4.2.4. 超级流

超级流(Super Stream)是一个用于分区流的抽象概念,通过将多个流队列绑定到一个交换机(exchange),并设置参数 x-super-stream: true 来实现。spring-doc.cadn.net.cn

配置

为了方便起见,可以通过定义一个类型为 SuperStream 的单一 Bean 来配置一个超级流。spring-doc.cadn.net.cn

@Bean
SuperStream superStream() {
    return new SuperStream("my.super.stream", 3);
}

The RabbitAdmin 检测到此 Bean,并将声明交换机(my.super.stream)和 3 个队列(分区)— my.super-stream-n,其中 n012,并使用路由键 n 进行绑定。spring-doc.cadn.net.cn

如果您也希望通过 AMQP 将消息发布到交换机,可以提供自定义的路由键:spring-doc.cadn.net.cn

@Bean
SuperStream superStream() {
    return new SuperStream("my.super.stream", 3, (q, i) -> IntStream.range(0, i)
					.mapToObj(j -> "rk-" + j)
					.collect(Collectors.toList()));
}

键的数量必须等于分区的数量。spring-doc.cadn.net.cn

生产到超级流

您必须在 superStreamRoutingFunction 中添加 RabbitStreamTemplatespring-doc.cadn.net.cn

@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
    RabbitStreamTemplate template = new RabbitStreamTemplate(env, "stream.queue1");
    template.setSuperStreamRouting(message -> {
        // some logic to return a String for the client's hashing algorithm
    });
    return template;
}

您还可以通过 AMQP 发布,使用 RabbitTemplatespring-doc.cadn.net.cn

使用单个活跃消费者消费超级流

调用监听器容器上的 superStream 方法,以在超流上启用单个活动消费者。spring-doc.cadn.net.cn

@Bean
StreamListenerContainer container(Environment env, String name) {
    StreamListenerContainer container = new StreamListenerContainer(env);
    container.superStream("ss.sac", "myConsumer", 3); // concurrency = 3
    container.setupMessageListener(msg -> {
        ...
    });
    container.setConsumerCustomizer((id, builder) -> builder.offset(OffsetSpecification.last()));
    return container;
}
此时,当并发数大于1时,实际并发数进一步由Environment进行控制;若要实现完全并发,请将环境的maxConsumersByConnection设置为1。参见配置环境

4.2.5. Micrometer 观察

自版本 3.0.5 起,现已支持使用 Micrometer 进行观测,适用于 RabbitStreamTemplate 和流监听器容器。该容器现在也支持 Micrometer 计时器(当未启用观测时)。spring-doc.cadn.net.cn

将每个组件上的observationEnabled设置为启用观察;这将禁用Micrometer计时器,因为计时器现在将由每次观察进行管理。当使用带注释的侦听器时,请在容器工厂上设置observationEnabledspring-doc.cadn.net.cn

有关更多信息,请参阅 Micrometer 追踪spring-doc.cadn.net.cn

要为计时器/跟踪添加标签,请分别在模板或监听器容器中配置自定义 RabbitStreamTemplateObservationConventionRabbitStreamListenerObservationConventionspring-doc.cadn.net.cn

默认实现为模板观察添加 name 标签,为容器添加 listener.id 标签。spring-doc.cadn.net.cn

您可以选择继承 DefaultRabbitStreamTemplateObservationConventionDefaultStreamRabbitListenerObservationConvention,或者提供完全新的实现。spring-doc.cadn.net.cn

有关更多详细信息,请参阅Micrometer Observation 文档spring-doc.cadn.net.cn

4.3 日志子系统AMQP追加器

该框架为一些流行的日志子系统提供了日志追加器:spring-doc.cadn.net.cn

Appender 是通过使用日志子系统中可用的常规机制进行配置的,有关其可用属性请参见以下各节。spring-doc.cadn.net.cn

4.3.1. 常用属性

以下属性适用于所有追加器:spring-doc.cadn.net.cn

表4. 常用附加程序属性
属性 默认 描述
 exchangeName
 logs

要发布日志事件的交易所名称。spring-doc.cadn.net.cn

 exchangeType
 topic

要发布日志事件的交换类型——仅当记录器声明了交换时才需要。请参见 declareExchangespring-doc.cadn.net.cn

 routingKeyPattern
 %c.%p

日志子系统模式格式,用于生成路由键。spring-doc.cadn.net.cn

 applicationId

应用 ID —— 如果模式包含 %X{applicationId},则添加到路由键中。spring-doc.cadn.net.cn

 senderPoolSize
 2

用于发布日志事件的线程数量。spring-doc.cadn.net.cn

 maxSenderRetries
 30

当 broker 不可用或其他错误发生时,消息发送应尝试发送多少次? 重试方式如下:N ^ log(N),其中 N 是重试次数。spring-doc.cadn.net.cn

 addresses

以逗号分隔的经纪人地址列表,格式如下:host:port[,host:port]* 会覆盖 hostportspring-doc.cadn.net.cn

 host
 localhost

RabbitMQ 主机,用于连接。spring-doc.cadn.net.cn

 port
 5672

RabbitMQ 连接端口。spring-doc.cadn.net.cn

 virtualHost
 /

RabbitMQ 虚拟主机,用于连接。spring-doc.cadn.net.cn

 username
 guest

RabbitMQ 用户,用于连接时使用。spring-doc.cadn.net.cn

 password
 guest

RabbitMQ 用户密码。spring-doc.cadn.net.cn

 useSsl
 false

用于 RabbitMQ 连接的 SSL 设置。
请参阅 RabbitConnectionFactoryBean 及配置 SSLspring-doc.cadn.net.cn

 verifyHostname
 true

为 TLS 连接启用服务器主机名验证。
请参阅RabbitConnectionFactoryBean 和配置 SSLspring-doc.cadn.net.cn

 sslAlgorithm
 null

要使用的SSL算法。spring-doc.cadn.net.cn

 sslPropertiesLocation
 null

SSL 属性文件的位置。spring-doc.cadn.net.cn

 keyStore
 null

密钥库的位置。spring-doc.cadn.net.cn

 keyStorePassphrase
 null

密钥库的密码。spring-doc.cadn.net.cn

 keyStoreType
 JKS

密钥库类型。spring-doc.cadn.net.cn

 trustStore
 null

信任库的位置。spring-doc.cadn.net.cn

 trustStorePassphrase
 null

信任库的密码。spring-doc.cadn.net.cn

 trustStoreType
 JKS

信任库类型。spring-doc.cadn.net.cn

 saslConfig
 null (RabbitMQ client default applies)

The saslConfig - see the javadoc for RabbitUtils.stringToSaslConfig for valid values.spring-doc.cadn.net.cn

 contentType
 text/plain

content-type 日志消息的属性。spring-doc.cadn.net.cn

 contentEncoding

content-encoding 日志消息的属性。spring-doc.cadn.net.cn

 declareExchange
 false

在该追加器启动时,是否声明已配置的交换机。
参见 durableautoDeletespring-doc.cadn.net.cn

 durable
 true

declareExchangetrue 时,持久标志被设置为此值。spring-doc.cadn.net.cn

 autoDelete
 false

declareExchangetrue 时,自动删除标志被设置为此值。spring-doc.cadn.net.cn

 charset
 null

在将 String 转换为 byte[] 时所使用的字符集。
默认值:null(使用系统默认字符集)。
如果当前平台不支持该字符集,则回退使用系统字符集。spring-doc.cadn.net.cn

 deliveryMode
 PERSISTENT

PERSISTENTNON_PERSISTENT 用于确定 RabbitMQ 是否应持久化消息。spring-doc.cadn.net.cn

 generateId
 false

用于确定 messageId 属性是否被设置为唯一值。spring-doc.cadn.net.cn

 clientConnectionProperties
 null

A comma-delimited list of key:value pairs for custom client properties to the RabbitMQ connection.spring-doc.cadn.net.cn

 addMdcAsHeaders
 true

MDC 属性始终被添加到 RabbitMQ 消息头中,直到引入此属性为止。这可能导致大 MDC 时出现问题,因为 RabbitMQ 对所有消息头的缓冲区大小有限,且该缓冲区相当小。此属性的引入旨在避免在 MDC 较大时出现此类问题。默认情况下,为保持向后兼容性,该值设置为 truefalse 表示禁用将 MDC 序列化到消息头中。
请注意,JsonLayout 默认将 MDC 添加到消息中。spring-doc.cadn.net.cn

4.3.2. Log4j 2 追加器

以下示例展示了如何配置 Log4j 2 记录器:spring-doc.cadn.net.cn

<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 开始,默认情况下,log4j2 过滤器将消息发布到 RabbitMQ 的线程为调用线程。这是因为 Log4j 2 默认不创建线程安全的事件。如果代理服务器不可用,将使用 maxSenderRetries 进行重试,且重试之间无延迟。如果您希望恢复先前在独立线程上发布消息的行为(senderPoolSize),可以将 async 属性设置为 true。不过,您还需配置 Log4j 2 以使用 DefaultLogEventFactory 而非 ReusableLogEventFactory。实现此目的的一种方法是设置系统属性 -Dlog4j2.enable.threadlocals=false。如果您使用 ReusableLogEventFactory 进行异步发布,则由于跨线程通信(cross-talk)导致事件极有可能被损坏。spring-doc.cadn.net.cn

4.3.3. Logback 追加器

以下示例展示了如何配置 logback 追加器:spring-doc.cadn.net.cn

<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 开始,Logback AmqpAppender 提供了一个 includeCallerData 选项,默认情况下为 false。提取调用者数据可能相当耗时,因为日志事件必须创建一个异常对象并检查它以确定调用位置。因此,默认情况下,当事件被添加到事件队列时,不会提取与该事件关联的调用者数据。您可以通过将 includeCallerData 属性设置为 true 来配置附加器,以包含调用者数据。spring-doc.cadn.net.cn

从版本 2.0.0 开始,Logback AmqpAppender 支持带有 Logback 编码器encoder 选项。 encoderlayout 选项相互排斥。spring-doc.cadn.net.cn

4.3.4. 自定义消息

默认情况下,AMQP Appender 会填充以下消息属性:spring-doc.cadn.net.cn

此外,它们还会在请求头中填充以下值:spring-doc.cadn.net.cn

每个追加器都可以被子类化,从而允许您在发布前修改消息。以下示例展示了如何自定义日志消息:spring-doc.cadn.net.cn

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    public Message postProcessMessageBeforeSend(Message message, Event event) {
        message.getMessageProperties().setHeader("foo", "bar");
        return message;
    }

}

从 2.2.4 版本开始,log4j2 AmqpAppender 可以通过 @PluginBuilderFactory 进行扩展,同时扩展也包括 AmqpAppender.Builderspring-doc.cadn.net.cn

@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. 自定义客户端属性

您可以通过添加字符串属性或更复杂的属性来添加自定义客户端属性。spring-doc.cadn.net.cn

简单字符串属性

每个追加器都支持向RabbitMQ连接添加客户端属性。spring-doc.cadn.net.cn

<font face="黑体"> 本示例演示如何为logback添加自定义客户端属性:</font>spring-doc.cadn.net.cn

<appender name="AMQP" ...>
    ...
    <clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
    ...
</appender>
示例 7. log4j2
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        ...
        clientConnectionProperties="thing1:thing2,cat:hat"
        ...
    </RabbitMQ>
</Appenders>

属性是用逗号分隔的键值对列表。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

密钥和值不能包含逗号或冒号。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

(这些属性在查看连接时出现在RabbitMQ管理UI上。)spring-doc.cadn.net.cn

用于Logback的高级技术

你可继承Logback的appender。 这样可以在连接建立前修改客户端连接属性。 下面展示了如何操作:spring-doc.cadn.net.cn

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;
    }

}

然后,您可以向logback.xml添加<thing1>thing2</thing1>spring-doc.cadn.net.cn

对于前面示例中所示的String属性,可以使用前面的技术。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

子类允许添加更丰富的属性(例如,添加Map或数值属性)。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

4.3.6. 提供自定义队列实现

AmqpAppenders 使用 BlockingQueue 异步地将日志事件发布到 RabbitMQ。 默认情况下,使用 LinkedBlockingQueue。 不过,你可以提供任何类型的自定义 BlockingQueue 实现。spring-doc.cadn.net.cn

此示例展示了如何为Logback执行此操作:spring-doc.cadn.net.cn

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}

Log4j 2 appender支持使用BlockingQueueFactory,如下面的例子所示:spring-doc.cadn.net.cn

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>

4.4. 示例应用程序

Spring AMQP 示例项目包含两个示例应用程序。第一个是一个简单的“Hello World”示例,用于演示同步和异步消息接收。它为理解核心组件提供了一个极佳的起点。第二个示例基于股票交易用例,以展示在实际应用中常见的交互类型。在本章中,我们将快速浏览每个示例,以便您能专注于最重要的组件。两个示例均基于 Maven,因此您可以直接将它们导入任何支持 Maven 的 IDE(例如 SpringSource 工具套件)。spring-doc.cadn.net.cn

4.4.1. "你好世界" 示例

“Hello World”示例演示了同步和异步消息接收方式。
您可以将 spring-rabbit-helloworld 示例导入 IDE,然后按照下方的说明进行操作。spring-doc.cadn.net.cn

同步示例

src/main/java 目录中,导航到 org.springframework.amqp.helloworld 包。
打开 HelloWorldConfiguration 类,注意其在类级别包含 @Configuration 注解,并注意某些方法级别包含 @Bean 注解。
这是 Spring 基于 Java 的配置的一个示例。
您可在此处了解更多信息:此处spring-doc.cadn.net.cn

以下列表显示了如何创建连接工厂:spring-doc.cadn.net.cn

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

配置中还包含一个 RabbitAdmin 实例,该实例默认会查找任何类型为 exchange、queue 或 binding 的 Bean,然后在 broker 上声明它们。实际上,HelloWorldConfiguration 中生成的 helloWorldQueue Bean 是一个示例,因为它是一个 Queue 的实例。spring-doc.cadn.net.cn

以下列表显示了 helloWorldQueue 个 Bean 定义:spring-doc.cadn.net.cn

@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}

回顾 rabbitTemplate Bean 配置,可以看到其 queue 属性(用于接收消息)被设置为 helloWorldQueue 的名称,而其 routingKey 属性(用于发送消息)则相应配置。spring-doc.cadn.net.cn

现在我们已经探索了配置,接下来可以查看实际使用这些组件的代码。首先,打开同一包内的 Producer 类。它包含一个 main() 方法,在该方法中创建了 Spring ApplicationContextspring-doc.cadn.net.cn

以下列表显示了 main 方法:spring-doc.cadn.net.cn

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 中所定义。spring-doc.cadn.net.cn

现在打开Consumer类。它实际上与相同的配置基类共享,这意味着它共享rabbitTemplate bean。这就是为什么我们使用routingKey(用于发送)和queue(用于接收)来配置该模板。AmqpTemplate中描述了,您可以将‘routingKey’参数传递给send方法,并将‘queue’参数传递给receive方法。Consumer代码基本上是生产者的镜像图像,调用receiveAndConvert()而不是convertAndSend()spring-doc.cadn.net.cn

以下列表显示了 Consumer 的主方法:spring-doc.cadn.net.cn

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 Worldspring-doc.cadn.net.cn

异步示例

同步示例 遍历了同步的“Hello World”示例。本节描述了一种稍显高级但功能显著更强的选项。通过少量修改,'Hello World' 示例可提供异步接收(也称为消息驱动型 POJO)的示例。事实上,存在一个子包,正好提供了这一点:org.springframework.amqp.samples.helloworld.asyncspring-doc.cadn.net.cn

再次,我们从发送端开始。
打开 ProducerConfiguration 类并注意它创建了一个 connectionFactory 和一个 rabbitTemplate Bean。
这一次,由于配置专用于消息发送端,我们甚至不需要任何队列定义,而 RabbitTemplate 仅设置了 'routingKey' 属性。
请回忆一下,消息是发送到交换机(exchange)而非直接发送到队列。
AMQP 默认交换机是一个无名的直接交换机(direct exchange)。
所有队列均绑定到该默认交换机,并以队列名称作为路由键(routing key)。
因此,我们在此处只需提供路由键即可。spring-doc.cadn.net.cn

以下列表显示了 rabbitTemplate 的定义:spring-doc.cadn.net.cn

public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}

由于本示例演示了异步消息接收,因此发送方被设计为持续发送消息(如果它采用每次执行发送一条消息的模型,如同步版本那样,则不太明显地表明它实际上是一个消息驱动型消费者)。负责持续发送消息的组件被定义为 ProducerConfiguration 内部的一个类。该组件被配置为每三秒运行一次。spring-doc.cadn.net.cn

以下列表显示了该组件:spring-doc.cadn.net.cn

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 的任务调度支持,可以在此 了解更多
简而言之,postProcessorProducerConfiguration 中的 Bean 会将任务注册到调度器上。spring-doc.cadn.net.cn

现在我们可以转向接收端。
为了强调消息驱动的 POJO 行为,我们首先从响应消息的组件开始。
该类名为 HelloWorldHandler,其代码示例如下所示:spring-doc.cadn.net.cn

public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}

该类是一个普通的 Java 对象(POJO)。
它不继承任何基类,不实现任何接口,甚至不包含任何导入语句。
Spring AMQP 的 MessageListenerAdapter 接口通过“适配”方式将其适配到 MessageListener 接口上。
然后,您可以将此适配器配置到 SimpleMessageListenerContainer 上。
在本示例中,容器是在 ConsumerConfiguration 类中创建的。
您可以在那里看到 POJO 被包装在适配器中。spring-doc.cadn.net.cn

以下列表显示了如何定义 listenerContainerspring-doc.cadn.net.cn

@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,并应能看到每三秒有消息被发送和接收。spring-doc.cadn.net.cn

4.4.2. 股票交易

股票交易示例演示了比“Hello World”示例更高级的消息传递场景。然而,其配置非常相似,只是略显复杂一些。由于我们已在“Hello World”配置中详细讲解过相关内容,此处我们将重点放在本示例的不同之处。有一个服务器会将市场数据(如股票报价)推送到一个主题交换机(topic exchange)。随后,客户端可通过绑定一个队列并使用路由模式(例如,app.stock.quotes.nasdaq.*)来订阅该市场数据流。此演示的另一主要功能是客户端发起、服务器处理的请求-响应式“股票交易”交互过程。这涉及一个由客户端在订单请求消息内部发送的私有replyTo队列。spring-doc.cadn.net.cn

服务器的核心配置位于 RabbitServerConfiguration 类中,该类位于 org.springframework.amqp.rabbit.stocks.config.server 包内。它继承自 AbstractStockAppRabbitConfiguration。此处定义了服务器与客户端共用的资源,包括市场数据主题交换(其名称为 'app.stock.marketdata')以及服务器为股票交易公开的队列(其名称为 'app.stock.request')。在该公共配置文件中,您还可以看到在 RabbitTemplate 上配置了一个 Jackson2JsonMessageConverterspring-doc.cadn.net.cn

服务器特定的配置包含两方面内容。首先,它在 RabbitTemplate 上配置了市场数据交易所,以便无需在每次调用发送 Message 时都提供该交易所名称。它通过在基础配置类中定义的一个抽象回调方法来实现这一点。以下列表展示了该方法:spring-doc.cadn.net.cn

public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}

第二,声明股票请求队列。
在此情况下,无需任何显式绑定,因为它已绑定到默认的无名交换机,并以自身名称作为路由键。
如前所述,AMQP 规范定义了这种行为。
以下列表显示了 stockRequestQueue bean 的定义:spring-doc.cadn.net.cn

@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示例,同时也能够发送回复消息。请注意,它本身并不依赖于该框架或任何AMQP概念。它接受一个 TradeRequest 并返回一个 TradeResponse。以下列表显示了 handleMessage 方法的定义:spring-doc.cadn.net.cn

public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}

现在,我们已经看到了服务器最重要的配置和代码,接下来可以转向客户端。最好的起点可能是 RabbitClientConfiguration,位于 org.springframework.amqp.rabbit.stocks.config.client 包中。注意,它声明了两个队列,但未提供显式名称。以下列表展示了这两个队列的 Bean 定义:spring-doc.cadn.net.cn

@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}

这些都是私有队列,且唯一名称会自动生成。第一个生成的队列将由客户端用于绑定服务器所暴露的市场数据交换机。请回忆一下,在 AMQP 中,消费者与队列交互,而生产者则与交换机交互。“队列与交换机的绑定”决定了代理(broker)应如何将消息从特定交换机路由到某个队列。由于市场数据交换机是一个主题交换机(topic exchange),其绑定可使用路由模式来表达。RabbitClientConfiguration 通过 Binding 对象实现这一点,而该对象则是利用 BindingBuilder 流式 API(fluent API)生成的。以下列表展示了 Bindingspring-doc.cadn.net.cn

@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 注解来注入该值。这通常是一个好主意。否则,该值将被硬编码在类中,且在不重新编译的情况下无法修改。在此情况下,轻松运行多个客户端版本,并对用于绑定的路由模式进行更改会更加便捷。我们现在可以尝试一下。spring-doc.cadn.net.cn

首先运行 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 实例。您会发现第一个客户端仍接收纳斯达克(NASDAQ)的报价,而第二个客户端则接收纽约证券交易所(NYSE)的报价。您也可以将模式更改为获取所有股票,甚至某个特定股票代码。spring-doc.cadn.net.cn

我们探讨的最后一个功能是从客户端视角出发的请求-响应交互。回想一下,我们已经看到过接受 TradeRequest 个对象并返回 TradeResponse 个对象的 ServerHandler。相应地,在 Client 端的代码位于 org.springframework.amqp.rabbit.stocks.gateway 包中的 RabbitStockServiceGateway。它通过调用 RabbitTemplate 来发送消息。以下列表展示了 send 方法:spring-doc.cadn.net.cn

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 定义生成的队列(如前面所示)。
以下列表显示了 @BeanStockServiceGateway 类本身的定义:spring-doc.cadn.net.cn

@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}

如果您不再运行服务器和客户端,请现在启动它们。尝试以 '100 TCKR' 格式发送请求。在短暂的人工延迟(模拟“处理”请求)之后,您应在客户端看到一条确认消息出现。spring-doc.cadn.net.cn

4.4.3. 从非Spring应用程序接收JSON

Spring 应用程序在发送 JSON 时,会将 TypeId 头部设置为完全限定类名,以协助接收应用程序将 JSON 转换回 Java 对象。spring-doc.cadn.net.cn

示例 spring-rabbit-json 探索了多种将 JSON 从非 Spring 应用中转换的技术。spring-doc.cadn.net.cn

4.5 测试支持

为异步应用程序编写集成代码必然比测试更简单的应用程序要复杂得多。当诸如 @RabbitListener 注解之类的抽象引入时,情况会变得更加复杂。问题在于:如何验证在发送消息后,监听器是否按预期收到了该消息。spring-doc.cadn.net.cn

该框架本身包含许多单元测试和集成测试。一些测试使用模拟对象,而另一些则使用带有实时 RabbitMQ 代理的集成测试。您可以查阅这些测试以获取一些测试场景的灵感。spring-doc.cadn.net.cn

Spring AMQP 版本 1.6 引入了 spring-rabbit-test jar 包,该包提供了对其中一些更复杂场景的测试支持。预计该项目将随时间逐步扩展,但我们需要社区反馈,以便就帮助测试所需的功能提出建议。请通过 JIRAGitHub Issues 提供此类反馈。spring-doc.cadn.net.cn

4.5.1. @SpringRabbitTest

使用此注释可向 Spring 测试添加基础 bean。当使用例如 @SpringBootTest 时不需要这样做,因为 Spring Boot 的自动配置将添加这些 bean。spring-doc.cadn.net.cn

已注册的 Bean 包括:spring-doc.cadn.net.cn

此外,与 @EnableRabbit 关联的 Bean(用于支持 @RabbitListener)也会被添加。spring-doc.cadn.net.cn

示例 8. 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)spring-doc.cadn.net.cn

4.5.2. MockitoAnswer<?>实现

目前有两种 Answer<?> 实现方式,用于辅助测试。spring-doc.cadn.net.cn

第一个,LatchCountDownAndCallRealMethodAnswer,提供了一个 Answer<Void>,它返回 null 并递减一个计数器。spring-doc.cadn.net.cn

LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();

第二种,LambdaAnswer<T> 提供了一种可选机制,用于调用实际方法,并根据 InvocationOnMock 和结果(如有)返回自定义结果。spring-doc.cadn.net.cn

考虑以下POJO:spring-doc.cadn.net.cn

public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}

以下类用于测试 Thing POJO:spring-doc.cadn.net.cn

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 开始,答案会捕获被测试方法抛出的任何异常。使用 answer.getExceptions() 可获取对这些异常的引用。spring-doc.cadn.net.cn

当与 @RabbitListenerTestRabbitListenerTestHarness 结合使用时,请使用 harness.getLambdaAnswerFor("listenerId", true, …​) 为监听器获取一个正确构造的响应。spring-doc.cadn.net.cn

4.5.3. @RabbitListenerTestRabbitListenerTestHarness

@Configuration 类中的一个类标注为 @RabbitListenerTest,会导致框架用一个名为 RabbitListenerTestHarness 的子类替换标准的 RabbitListenerAnnotationBeanPostProcessor(同时还能通过 @EnableRabbit 实现 @RabbitListener 的检测)。spring-doc.cadn.net.cn

RabbitListenerTestHarness 以两种方式增强了监听器。首先,它将监听器包装在一个 Mockito Spy 中,从而支持常规的 Mockito 模拟(stubbing)和验证操作。此外,它还可以向监听器添加一个 Advice,从而可访问调用的参数、结果以及可能抛出的异常。您可以通过 @RabbitListenerTest 上的属性来控制启用其中哪一项(或两项)。后一种方式旨在提供对调用底层数据的访问。它还支持在异步监听器被调用之前阻塞测试线程。spring-doc.cadn.net.cn

final @RabbitListener 方法无法被监视或增强。此外,只有带有 id 属性的监听器才能被监视或增强。

考虑一些示例。spring-doc.cadn.net.cn

以下示例使用了 spy:spring-doc.cadn.net.cn

@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 的引用,以便我们可以验证它是否按预期调用。因为这是一个发送和接收操作,所以不需要挂起测试线程,因为它已经在 0 等待回复时被挂起。
3 在这种情况下,我们仅使用发送操作,因此需要一个栅栏(latch)来等待在容器线程上调用监听器的异步调用。我们使用其中一个 Answer<?> 实现来协助完成此操作。重要提示:由于监听器是通过模拟(spy)方式被监控的,因此必须使用 harness.getLatchAnswerFor() 才能为该模拟获取一个正确配置的响应。
4 配置代理以调用 Answer

以下示例使用了捕获通知:spring-doc.cadn.net.cn

@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() 来获取调用数据——在此情况下,由于是请求/响应(request/reply)场景,无需等待任何时间,因为测试线程已在 RabbitTemplate 处被挂起,等待结果。
3 然后我们可以验证参数和结果是否符合预期。
4 这次我们需要一些时间来等待数据,因为这是在容器线程上的异步操作,我们需要挂起测试线程。
5 当监听器抛出异常时,该异常可在调用数据的 throwable 属性中获取。
当使用自定义 Answer<?> 与工具包配合时,为确保其正常运行,此类答案应继承 ForwardsInvocation,并从工具包中获取实际的监听器(而非模拟对象)(getDelegate("myListener")),然后调用 super.answer(invocation)。有关示例,请参阅提供的 Mockito Answer<?> 实现 源代码。

4.5.4. 使用TestRabbitTemplate

代码 TestRabbitTemplate 提供了基本的集成测试功能,无需依赖消息代理即可运行。当您在测试用例中将其作为 @Bean 添加时,它会自动发现上下文中的所有监听器容器,无论这些容器是通过 @Bean<bean/> 声明的,还是使用 @RabbitListener 注解配置的。目前它仅支持按队列名称进行路由。该模板会从容器中提取消息监听器,并在测试线程上直接调用它。对于返回回复的监听器,也支持请求-回复消息模式(sendAndReceive 方法)。spring-doc.cadn.net.cn

以下测试用例使用了该模板:spring-doc.cadn.net.cn

@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() {

                public void handleMessage(String in) {
                    smlc1In += in;
                }

            }));
            return container;
        }

    }

}

4.5.5. JUnit4@Rules

Spring AMQP 版本 1.7 及更高版本提供了一个名为spring-rabbit-junit的额外 jar 文件。这个jar文件包含了一些实用程序@Rule类,用于在运行JUnit4测试时使用。有关JUnit5测试的信息,请参见JUnit5条件spring-doc.cadn.net.cn

使用BrokerRunning

BrokerRunning 提供了一种机制,使得在消息代理未运行时(默认情况下在 localhost 上)测试仍能通过。spring-doc.cadn.net.cn

它还提供了实用方法来初始化和清空队列,以及删除队列和交换机。spring-doc.cadn.net.cn

以下示例展示了其用法:spring-doc.cadn.net.cn

@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(),用于验证代理是否启用了管理插件。spring-doc.cadn.net.cn

配置规则

有时你希望在没有消息代理(如夜间CI构建)时测试失败。 要在运行时禁用该规则,将环境变量名为RABBITMQ_SERVER_REQUIRED的变量设置为truespring-doc.cadn.net.cn

您可以使用 setter 或环境变量覆盖代理程序属性,例如主机名:spring-doc.cadn.net.cn

以下示例展示了如何通过setter方法重写属性:spring-doc.cadn.net.cn

@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
}

您还可以通过设置以下环境变量来重写属性:spring-doc.cadn.net.cn

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,管理 REST API 为 localhost:15672/api/)。spring-doc.cadn.net.cn

更改主机名会影响 amqpmanagement REST API 连接(除非已显式设置管理 URI)。spring-doc.cadn.net.cn

BrokerRunning 也提供了一个名为 staticsetEnvironmentVariableOverrides 方法,您可以通过该方法传入一个包含这些变量的映射(map)。spring-doc.cadn.net.cn

在您的测试用例中,您可以使用 brokerRunning 来创建连接工厂;getConnectionFactory() 返回规则的 RabbitMQ ConnectionFactory。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
使用LongRunningIntegrationTest

LongRunningIntegrationTest 是禁用长时间测试的规则。 你可能在开发系统上使用此规则,但在例如夜间 CI 构建上确保禁用该规则。spring-doc.cadn.net.cn

以下示例展示了其用法:spring-doc.cadn.net.cn

@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

要禁用运行时规则,请将名为 RUN_LONG_INTEGRATION_TESTS 的环境变量设置为 truespring-doc.cadn.net.cn

4.5.6. JUnit5 条件

版本 2.0.2 引入了对 JUnit5 的支持。spring-doc.cadn.net.cn

使用@RabbitAvailable注解

这个类级注解与在JUnit4 @Rules中讨论的BrokerRunning @Rule类似。 它由RabbitAvailableCondition处理。spring-doc.cadn.net.cn

注解有三个属性:spring-doc.cadn.net.cn

  • queues: 一组在每次测试前声明(并清除)并在所有测试完成后删除的队列。spring-doc.cadn.net.cn

  • management: 将此设置为 true,以表示您的测试也需要在代理上安装管理插件。spring-doc.cadn.net.cn

  • purgeAfterEach: (自版本 2.2 起) 当 true(默认值)时,queues 将在测试之间被清除。spring-doc.cadn.net.cn

它用于检查代理服务器是否可用,并在没有代理服务器时跳过测试。 如前所述,在“配置规则”中,如果环境变量名为“1”,其值为“2”,则如果没有代理服务器,则会导致测试失败。 您可以使用如“配置规则”中所述的环境变量来配置该条件。spring-doc.cadn.net.cn

此外,RabbitAvailableCondition 还支持参数化测试构造函数和方法的参数解析。支持两种参数类型:spring-doc.cadn.net.cn

以下示例展示了两者:<br/>spring-doc.cadn.net.cn

@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();
    }

}

前面的测试在框架本身中执行,用于验证参数注入,并且该条件正确地创建了队列。spring-doc.cadn.net.cn

一个实用的用户测试可能如下所示:spring-doc.cadn.net.cn

@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()的静态方法获取到条件的连接工厂引用。spring-doc.cadn.net.cn

(从版本2.2开始,getBrokerRunning()返回一个BrokerRunningSupport对象;以前,JUnit 4 BrokerRunnning实例被返回。 新类与BrokerRunning具有相同的API。)

下面的测试来自框架,并演示了它的用法。spring-doc.cadn.net.cn

@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注解

与JUnit4中的LongRunningIntegrationTest类似,此注解会使测试被跳过,除非设置了环境变量(或系统属性)为true
下面的示例展示了如何使用它:
spring-doc.cadn.net.cn

@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}

默认情况下,变量为 0 ,但在注释的 1 属性中可以指定变量名。 spring-doc.cadn.net.cn