4. 参考资料

参考文档的这一部分详细介绍了构成 Spring AMQP 的各种组件。 主要章节介绍了开发 AMQP 应用程序的核心类。 本部分还包括有关示例应用程序的一章。spring-doc.cn

4.1. 使用 Spring AMQP

本章探讨了接口和类,它们是使用 Spring AMQP 开发应用程序的基本组件。spring-doc.cn

4.1.1. AMQP 抽象

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

本概述假定您已经熟悉 AMQP 规范的基础知识。 如果没有,请查看 Other Resources 中列出的资源spring-doc.cn

Message

0-9-1 AMQP 规范未定义类或接口。 相反,在执行诸如 之类的操作时,内容将作为字节数组参数传递,而其他属性将作为单独的参数传入。 Spring AMQP 将类定义为更通用的 AMQP 域模型表示的一部分。 该类的目的是将 body 和 properties 封装在单个实例中,以便 API 反过来可以更简单。 以下示例显示了类定义:MessagebasicPublish()MessageMessageMessagespring-doc.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;
    }
}

该接口定义了几个常见属性,例如 'messageId'、'timestamp'、'contentType' 等。 您还可以通过调用该方法使用用户定义的 'headers' 来扩展这些属性。MessagePropertiessetHeader(String key, Object value)spring-doc.cn

从版本 、 、 和 、 开始,如果消息体是序列化的 java 对象,则在执行操作(例如在日志消息中)时(默认情况下)不再反序列化它。 这是为了防止不安全的反序列化。 默认情况下,只有 和 类被反序列化。 要恢复到以前的行为,您可以通过调用 . 支持简单的通配符,例如 com.something.. 无法反序列化的正文在 log messages 中表示。1.5.71.6.111.7.42.0.0SerializabletoString()java.utiljava.langMessage.addAllowedListPatterns(…​), *.MyClassbyte[<size>]
交换

该接口表示 AMQP Exchange,这是消息生成者发送到的对象。 代理的虚拟主机中的每个 Exchange 都有一个唯一的名称以及一些其他属性。 以下示例显示了该接口:ExchangeExchangespring-doc.cn

public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}

如您所见,an 也有一个由 中定义的常量表示的 'type'。 基本类型包括:、、 和 。 在 core 包中,您可以找到每种类型的接口实现。 这些类型在处理队列绑定的方式方面的行为各不相同。 例如,交换允许队列由固定的路由键(通常是队列的名称)绑定。 交换支持具有路由模式的绑定,这些模式可能包括分别表示“恰好一”和“零或多”的“*”和“#”通配符。 交换发布到绑定到它的所有队列,而不考虑任何路由键。 有关这些类型和其他 Exchange 类型的更多信息,请参阅其他资源ExchangeExchangeTypesdirecttopicfanoutheadersExchangeExchangeDirectTopicFanoutspring-doc.cn

AMQP 规范还要求任何代理提供没有名称的 “默认” 直接交换。 声明的所有队列都绑定到该默认值,其名称作为路由键。 你可以在AmqpTemplate中了解更多关于 Spring AMQP 中默认 Exchange 的用法。Exchange
队列

该类表示消息使用者从中接收消息的组件。 与各种类一样,我们的实现旨在成为此核心 AMQP 类型的抽象表示。 下面的清单显示了该类:QueueExchangeQueuespring-doc.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

}

请注意,构造函数采用队列名称。 根据实现, admin 模板可能会提供生成唯一命名队列的方法。 此类队列可用作 “reply-to” 地址或其他临时情况。 因此,自动生成的队列的 'exclusive' 和 'autoDelete' 属性都将设置为 'true'。spring-doc.cn

有关使用命名空间支持(包括队列参数)声明队列的信息,请参阅配置 Broker 中有关队列的部分。
捆绑

鉴于生产者向交换发送数据,而使用者从队列接收数据,则将队列连接到交换的绑定对于通过消息收发连接这些生产者和使用者至关重要。 在 Spring AMQP 中,我们定义了一个类来表示这些连接。 本节回顾将队列绑定到 exchanges 的基本选项。Bindingspring-doc.cn

您可以将队列绑定到具有固定路由键的 ,如下例所示:DirectExchangespring-doc.cn

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

您可以使用路由模式将队列绑定到 ,如下例所示:TopicExchangespring-doc.cn

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

您可以将队列绑定到没有路由键的 ,如下例所示:FanoutExchangespring-doc.cn

new Binding(someQueue, someFanoutExchange);

我们还提供了一个 a 来促进 “Fluent API” 样式,如下例所示:BindingBuilderspring-doc.cn

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

该类的实例本身仅保存有关连接的数据。 换句话说,它不是一个 “active” 组件。 但是,正如您稍后将在 配置 Broker 中看到的那样,该类可以使用实例来实际触发 Broker 上的绑定操作。 此外,正如你在同一节中看到的那样,你可以通过在类中使用 Spring 的 Comments 来定义实例。 还有一个方便的基类,它进一步简化了生成与 AMQP 相关的 bean 定义的方法,并识别队列、交换和绑定,以便在应用程序启动时在 AMQP 代理上声明它们。BindingAmqpAdminBindingBinding@Bean@Configurationspring-doc.cn

在 core 包中也定义了 。 作为实际 AMQP 消息传递中涉及的主要组件之一,它将在其自己的部分中详细讨论(参见 AmqpTemplate)。AmqpTemplatespring-doc.cn

4.1.2. 连接和资源管理

虽然我们在上一节中描述的 AMQP 模型是通用的,适用于所有实现,但当我们进入资源管理时,细节是特定于代理实现的。 因此,在本节中,我们重点介绍仅存在于 “spring-rabbit” 模块中的代码,因为此时 RabbitMQ 是唯一受支持的实现。spring-doc.cn

用于管理与 RabbitMQ 代理的连接的中心组件是接口。 实现的责任是提供 的实例,该实例是 的包装器 。ConnectionFactoryConnectionFactoryorg.springframework.amqp.rabbit.connection.Connectioncom.rabbitmq.client.Connectionspring-doc.cn

选择连接工厂

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

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

对于大多数用例,应该使用 the 。 如果要确保严格的消息排序,而无需使用 Scoped Operations,则可以使用 。 这与 for similar that that it use a single connection 和一个 pool of channels。 它的实现更简单,但它不支持相关的发布者确认。CachingConnectionFactoryThreadChannelConnectionFactoryPooledChannelConnectionFactoryCachingConnectionFactoryspring-doc.cn

这三个工厂都支持简单的发布者确认。spring-doc.cn

将 配置为使用单独的连接时,您现在可以从版本 2.3.2 开始,将发布连接工厂配置为其他类型。 默认情况下,发布工厂的类型相同,并且在主工厂上设置的任何属性也会传播到发布工厂。RabbitTemplatespring-doc.cn

PooledChannelConnectionFactory

此工厂基于 Apache Pool2 管理单个连接和两个通道池。 一个池用于事务通道,另一个池用于非事务通道。 池是 s 的默认配置;提供回调以配置池;有关更多信息,请参阅 Apache 文档。GenericObjectPoolspring-doc.cn

Apache jar 必须位于类路径上才能使用此工厂。commons-pool2spring-doc.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

此工厂管理单个连接和两个 s,一个用于事务通道,另一个用于非事务通道。 此工厂确保同一线程上的所有操作都使用相同的通道(只要它保持打开状态)。 这有助于对消息进行严格的排序,而无需 Scoped Operations。 为避免内存泄漏,如果您的应用程序使用许多短期线程,则必须调用工厂的线程以释放通道资源。 从版本 2.3.7 开始,线程可以将其通道传输给另一个线程。 有关更多信息,请参见多线程环境中的严格消息排序ThreadLocalcloseThreadChannel()spring-doc.cn

CachingConnectionFactory

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

从版本 1.3 开始,您可以配置 to cache connections 以及仅 channels。 在这种情况下,每次调用 都会创建一个新连接(或从缓存中检索一个空闲连接)。 关闭连接会将其返回到缓存中(如果尚未达到缓存大小)。 在此类连接上创建的通道也会被缓存。 在某些环境中,例如从 HA 集群使用,使用单独的连接可能很有用。 与负载均衡器结合使用,以连接到不同的集群成员等。 要缓存连接,请将 设置为 .CachingConnectionFactorycreateConnection()cacheModeCacheMode.CONNECTIONspring-doc.cn

这不会限制连接数。 相反,它指定允许的空闲打开连接数。

从版本 1.5.5 开始,提供了一个名为 . 设置此属性后,它将限制允许的连接总数。 设置后,如果达到限制,则用于等待连接变为空闲状态。 如果超过时间,则引发 an。connectionLimitchannelCheckoutTimeLimitAmqpTimeoutExceptionspring-doc.cn

当缓存模式为 时,自动声明队列等 (请参阅 Exchanges、Queues, and Bindings 的自动声明) 不受支持。CONNECTIONspring-doc.cn

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

重要的是要了解缓存大小(默认情况下)不是一个限制,而只是可以缓存的通道数。 如果缓存大小为 10,则实际上可以使用任意数量的通道。 如果使用的通道超过 10 个,并且它们都返回到缓存中,则 10 个通道进入缓存。 其余的都是实体关闭的。spring-doc.cn

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

从版本 1.4.2 开始,具有一个名为 的属性。 当此属性大于零时,这将限制可在连接上创建的通道数。 如果达到限制,则调用 threads 将阻塞,直到通道可用或达到此超时,在这种情况下,将抛出 a。CachingConnectionFactorychannelCheckoutTimeoutchannelCacheSizeAmqpTimeoutExceptionspring-doc.cn

框架中使用的通道(例如 )将可靠地返回到缓存中。 如果您在框架之外创建通道(例如 通过直接访问连接并调用 ),你必须可靠地返回它们(通过关闭),也许在一个块中,以避免用完通道。RabbitTemplatecreateChannel()finally

以下示例显示如何创建新的 :connectionspring-doc.cn

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

Connection connection = connectionFactory.createConnection();

使用 XML 时,配置可能类似于以下示例:spring-doc.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>
还有一个仅在框架的单元测试代码中可用的实现。 它比 , 更简单,因为它不缓存通道,但由于缺乏性能和弹性,它不打算用于简单测试之外的实际使用。 如果出于某种原因需要实现自己的 base 类,则 base class 可能是一个很好的起点。SingleConnectionFactoryCachingConnectionFactoryConnectionFactoryAbstractConnectionFactory

可以使用 rabbit 命名空间快速方便地创建 A,如下所示:ConnectionFactoryspring-doc.cn

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

在大多数情况下,此方法更可取,因为框架可以为您选择最佳默认值。 创建的实例是一个 . 请记住,通道的默认缓存大小为 25。 如果要缓存更多通道,请通过设置 'channelCacheSize' 属性来设置更大的值。 在 XML 中,它如下所示:CachingConnectionFactoryspring-doc.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.cn

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

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

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

您可以使用命名空间提供 host 和 port 属性,如下所示:spring-doc.cn

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

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

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

请参阅 连接到集群 以了解有关 的信息。address-shuffle-modespring-doc.cn

以下示例具有自定义线程工厂,该工厂在线程名称前面加上 :rabbitmq-spring-doc.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>
AddressResolver 地址解析器

从版本 2.1.15 开始,您现在可以使用 an 来解析连接地址。 这将覆盖 和 属性的任何设置。AddressResolveraddresseshost/portspring-doc.cn

命名连接

从版本 1.7 开始,提供了 a 用于将 . 生成的名称用于目标 RabbitMQ 连接的应用程序特定标识。 如果 RabbitMQ 服务器支持连接名称,则连接名称将显示在管理 UI 中。 此值不必是唯一的,并且不能用作连接标识符,例如,在 HTTP API 请求中。 此值应该是人类可读的,并且是 under the key 的一部分。 您可以使用简单的 Lambda,如下所示:ConnectionNameStrategyAbstractionConnectionFactoryClientPropertiesconnection_namespring-doc.cn

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

该参数可用于通过某种逻辑区分目标连接名称。 默认情况下,的 、表示对象的十六进制字符串和内部计数器用于生成 . namespace 组件也提供了 attribute 。ConnectionFactorybeanNameAbstractConnectionFactoryconnection_name<rabbit:connection-factory>connection-name-strategyspring-doc.cn

的实现将连接名称设置为应用程序属性。 您可以将其声明为 a 并将其注入到连接工厂中,如下例所示:SimplePropertyValueConnectionNameStrategy@Beanspring-doc.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;
}

该属性必须存在于应用程序上下文的 .Environmentspring-doc.cn

使用 Spring Boot 及其自动配置的连接工厂时,您只需声明 . Boot 会自动检测 bean 并将其连接到工厂。ConnectionNameStrategy@Bean
阻塞的连接和资源限制

该连接可能被阻止,无法与对应于 Memory Alarm 的代理进行交互。 从版本 2.0 开始,可以为 提供实例,以通知连接已阻止和未阻止事件。 此外,还通过其内部实现分别发出 a 和 。 这些允许您提供应用程序逻辑来对 broker 上的问题做出适当的反应,并(例如)采取一些纠正措施。org.springframework.amqp.rabbit.connection.Connectioncom.rabbitmq.client.BlockedListenerAbstractConnectionFactoryConnectionBlockedEventConnectionUnblockedEventBlockedListenerspring-doc.cn

当应用程序配置单个时,就像默认情况下使用 Spring Boot 自动配置一样,当连接被 Broker 阻止时,应用程序将停止工作。 当它被 Broker 阻止时,它的任何客户端都会停止工作。 如果我们在同一个应用程序中有 Producer 和 Consumer,那么当 Producer 阻止连接(因为 Broker 上不再有资源)并且 Consumer 无法释放它们(因为连接被阻止)时,我们最终可能会遇到死锁。 为了缓解此问题,我们建议再有一个具有相同选项的单独实例 — 一个用于生产者,一个用于使用者。 对于在使用者线程上执行的事务性生产者,不可能单独使用,因为它们应该重用与使用者事务关联的。CachingConnectionFactoryCachingConnectionFactoryCachingConnectionFactoryChannel

从版本 2.0.2 开始,除非正在使用事务,否则 具有自动使用第二个连接工厂的配置选项。 有关更多信息,请参阅使用单独的连接。 for the publisher 连接与主要策略相同,但附加到调用方法的结果中。RabbitTemplateConnectionNameStrategy.publisherspring-doc.cn

从版本 1.7.7 开始,提供了 an,当无法创建 a 时会抛出该 (例如,因为已达到限制并且缓存中没有可用的通道)。 您可以在某个回退后使用此异常来恢复操作。AmqpResourceNotAvailableExceptionSimpleConnection.createChannel()ChannelchannelMaxRetryPolicyspring-doc.cn

配置底层客户端连接工厂

使用 Rabbit client 的实例。 在 上设置等效属性时,会传递许多配置属性(例如)。 要设置其他属性(例如),您可以定义 Rabbit 工厂的实例,并使用 . 使用命名空间时(如前所述),您需要在属性中提供对已配置工厂的引用。 为方便起见,提供了一个工厂 Bean 来帮助在 Spring 应用程序上下文中配置连接工厂,如下一节所述。CachingConnectionFactoryConnectionFactoryhostportuserNamepasswordrequestedHeartBeatconnectionTimeoutCachingConnectionFactoryclientPropertiesCachingConnectionFactoryconnection-factoryspring-doc.cn

<rabbit:connection-factory
      id="connectionFactory" connection-factory="rabbitConnectionFactory"/>
默认情况下,4.0.x 客户端启用自动恢复。 虽然与此功能兼容,但 Spring AMQP 有自己的恢复机制,通常不需要 Client 端恢复功能。 我们建议禁用自动恢复,以避免在代理可用但连接尚未恢复时获取实例。 您可能会注意到此异常,例如,在 中配置 a 时,即使故障转移到集群中的其他代理也是如此。 由于自动恢复连接在计时器上恢复,因此可以使用 Spring AMQP 的恢复机制更快地恢复连接。 从版本 1.7.1 开始, Spring AMQP 禁用自动恢复,除非你显式创建自己的 RabbitMQ 连接工厂并将其提供给. 默认情况下,由 创建的 RabbitMQ 实例也具有禁用选项。amqp-clientAutoRecoverConnectionNotCurrentlyOpenExceptionRetryTemplateRabbitTemplateamqp-clientCachingConnectionFactoryConnectionFactoryRabbitConnectionFactoryBean
RabbitConnectionFactoryBean和配置 SSL

从版本 1.4 开始,通过使用依赖项注入,可以方便地在底层客户端连接工厂上配置 SSL 属性。 其他 setter 委托给底层工厂。 以前,您必须以编程方式配置 SSL 选项。 以下示例显示如何配置 :RabbitConnectionFactoryBeanRabbitConnectionFactoryBeanspring-doc.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;
}
引导 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>

有关配置 SSL 的信息,请参阅 RabbitMQ 文档。 省略 and 配置以通过 SSL 进行连接,而无需进行证书验证。 下一个示例显示如何提供密钥和信任存储配置。keyStoretrustStorespring-doc.cn

该属性是一个 Spring,指向包含以下键的属性文件:sslPropertiesLocationResourcespring-doc.cn

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

和 是 Spring 指向商店。 通常,此属性文件由操作系统保护,应用程序具有读取访问权限。keyStoretruststoreResourcesspring-doc.cn

从 Spring AMQP 版本 1.5 开始,您可以直接在工厂 bean 上设置这些属性。 如果同时提供了 discrete 属性 和 ,则后者中的 properties 会覆盖 discrete 值。sslPropertiesLocationspring-doc.cn

从版本 2.0 开始,默认情况下会验证服务器证书,因为它更安全。 如果出于某种原因希望跳过此验证,请将工厂 Bean 的属性设置为。 从版本 2.1 开始,now 默认调用。 要恢复到之前的行为,请将该属性设置为 。skipServerCertificateValidationtrueRabbitConnectionFactoryBeanenableHostnameVerification()enableHostnameVerificationfalse
从版本 2.2.5 开始,默认情况下,工厂 Bean 将始终使用 TLS v1.2;以前,它在某些情况下使用 v1.1,而在其他情况下使用 v1.2(取决于其他属性)。 如果出于某种原因需要使用 v1.1,请设置属性: 。sslAlgorithmsetSslAlgorithm("TLSv1.1")
连接到集群

要连接到集群,请在 :addressesCachingConnectionFactoryspring-doc.cn

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

从版本 3.0 开始,每当建立新连接时,底层连接工厂将尝试通过选择随机地址连接到主机。 要恢复到以前尝试从第一个到最后一个连接的行为,请将该属性设置为 。addressShuffleModeAddressShuffleMode.NONEspring-doc.cn

从版本 2.3 开始,添加了 shuffle 模式,这意味着在创建连接后,第一个地址将移动到末尾。 如果您希望从所有节点上的所有分片中使用,您可能希望将此模式与 RabbitMQ 分片插件一起使用,并具有适当的并发性。INORDERCacheMode.CONNECTIONspring-doc.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 开始,引入了 。 该工厂提供了一种机制,可以为多个映射配置映射,并在运行时由一些人确定目标。 通常,该实现会检查线程绑定的上下文。 为方便起见, Spring AMQP 提供了 ,它从 . 以下示例显示了如何在 XML 和 Java 中配置 a:AbstractRoutingConnectionFactoryConnectionFactoriesConnectionFactorylookupKeySimpleRoutingConnectionFactorylookupKeySimpleResourceHolderSimpleRoutingConnectionFactoryspring-doc.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());
    }

}

使用后取消绑定资源很重要。 有关更多信息,请参阅 JavaDoc for 。AbstractRoutingConnectionFactoryspring-doc.cn

从版本 1.4 开始,支持 SpEL 和属性,这些属性在每个 AMQP 协议交互操作(、或)上进行评估,解析为提供的 . 您可以使用 Bean 引用,例如在表达式中。 对于操作,要发送的消息是根评估对象。 对于操作,是根评估对象。RabbitTemplatesendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpressionsendsendAndReceivereceivereceiveAndReplylookupKeyAbstractRoutingConnectionFactory@vHostResolver.getVHost(#root)sendreceivequeueNamespring-doc.cn

路由算法如下:如果选择器表达式是 或 被计算为 或 提供的不是 instance of ,则一切都像以前一样工作,依赖于提供的实现。 如果评估结果不是 ,但没有目标,并且 配置了 ,则也会发生同样的情况。 对于 ,它确实回退到基于 的实现。 但是,如果 , an 被抛出。nullnullConnectionFactoryAbstractRoutingConnectionFactoryConnectionFactorynullConnectionFactorylookupKeyAbstractRoutingConnectionFactorylenientFallback = trueAbstractRoutingConnectionFactoryroutingdetermineCurrentLookupKey()lenientFallback = falseIllegalStateExceptionspring-doc.cn

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

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

从版本 1.6.9 开始,您可以通过在侦听器容器上使用 lookup key 向查找键添加限定符。 例如,这样做可以侦听具有相同名称但在不同虚拟主机中的队列(每个虚拟主机都有一个连接工厂)。setLookupKeyQualifierspring-doc.cn

例如,使用 lookup key 限定符和侦听 queue 的容器,您可以向其注册目标连接工厂的 lookup key 可以是 。thing1thing2thing1[thing2]spring-doc.cn

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

从版本 2.4.4 开始,可以禁用此验证。 如果您遇到 confirms 和 returns 之间的值需要不相等的情况,则可以使用 来关闭验证。 请注意,添加到 的第一个连接工厂将确定 和 的一般值。AbstractRoutingConnectionFactory#setConsistentConfirmsReturnsAbstractRoutingConnectionFactoryconfirmsreturnsspring-doc.cn

如果您遇到某些消息需要检查确认/返回而其他消息不确认/返回的情况,这可能会很有用。 例如:spring-doc.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);
}

这样,带有标头的消息将通过缓存连接发送,您可以确保消息送达。 有关确保邮件送达的更多信息,请参阅 Publisher Confirms and Returnsx-use-publisher-confirms: truespring-doc.cn

Queue Affinity 和LocalizedQueueConnectionFactory

在集群中使用 HA 队列时,为了获得最佳性能,您可能需要连接到物理代理 lead 队列所在的位置。 可以配置多个代理地址。 这是为了进行故障转移,客户端会尝试按照配置的顺序进行连接。 它使用管理插件提供的 REST API 来确定哪个节点是队列的潜在客户。 然后,它会创建(或从缓存中检索)仅连接到该节点的 a。 如果连接失败,则确定新的前导节点,并使用方连接到该节点。 配置了默认连接工厂,以防无法确定队列的物理位置,在这种情况下,它会正常连接到集群。CachingConnectionFactoryAddressShuffleModeLocalizedQueueConnectionFactoryCachingConnectionFactoryLocalizedQueueConnectionFactoryspring-doc.cn

是 a 和 ,它使用队列名称作为查找键,如上面的 路由连接工厂 中所述。LocalizedQueueConnectionFactoryRoutingConnectionFactorySimpleMessageListenerContainerspring-doc.cn

因此(使用队列名称进行查找),仅当容器配置为侦听单个队列时,才能使用 。LocalizedQueueConnectionFactory
必须在每个节点上启用 RabbitMQ 管理插件。
此连接工厂适用于长期连接,例如 . 它不适用于短连接使用,例如与 a 一起使用,因为在建立连接之前调用 REST API 会产生开销。 此外,对于发布操作,队列是未知的,并且消息无论如何都会发布到所有集群成员,因此查找节点的逻辑几乎没有价值。SimpleMessageListenerContainerRabbitTemplate

以下示例配置显示了如何配置工厂:spring-doc.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);
}

请注意,前三个参数是 、 和 的数组。 这些是位置性的,因为当容器尝试连接到队列时,它使用 admin API 来确定哪个节点是队列的引线,并连接到与该节点位于同一数组位置的地址。addressesadminUrisnodesspring-doc.cn

从版本 3.0 开始,RabbitMQ 不再用于访问 Rest API。 相反,默认情况下,如果在类路径上,则使用 from Spring Webflux;否则使用 a。http-clientWebClientspring-webfluxRestTemplate

要添加到类路径,请执行以下操作:WebFluxspring-doc.cn

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

您还可以通过实现和覆盖其方法(可选)来使用其他 REST 技术。LocalizedQueueConnectionFactory.NodeLocatorcreateClient, ``restCallclosespring-doc.cn

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

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

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

});

框架提供 和 ,默认值如上所述。WebFluxNodeLocatorRestTemplateNodeLocatorspring-doc.cn

发布者确认并返回

通过将属性设置为 'true' 和属性,支持已确认(带关联)和返回的消息。CachingConnectionFactorypublisherConfirmTypeConfirmType.CORRELATEDpublisherReturnsspring-doc.cn

设置这些选项后,工厂创建的实例将包装在 中,用于促进回调。 当获得这样的通道时,客户端可以向 注册 。 该实现包含用于将 confirm 或 return 路由到相应侦听器的逻辑。 这些功能将在以下各节中进一步说明。ChannelPublisherCallbackChannelPublisherCallbackChannel.ListenerChannelPublisherCallbackChannelspring-doc.cn

另请参阅 Correlated Publisher Confirms and ReturnsScoped Operations 中。simplePublisherConfirmsspring-doc.cn

有关更多背景信息,请参阅 RabbitMQ 团队的博客文章,标题为 Introducing Publisher Confirms
连接侦听器和通道侦听器

连接工厂支持注册和实现。 这允许您接收连接和通道相关事件的通知。 (A 用于在建立连接时执行声明 - 有关更多信息,请参阅 Automatic Declaration of Exchanges, Queues, and Bindings)。 下面的清单显示了接口定义:ConnectionListenerChannelListenerConnectionListenerRabbitAdminConnectionListenerspring-doc.cn

@FunctionalInterface
public interface ConnectionListener {

    void onCreate(Connection connection);

    default void onClose(Connection connection) {
    }

    default void onShutDown(ShutdownSignalException signal) {
    }

}

从版本 2.0 开始,可以为对象提供实例,以通知连接已阻止和未阻止事件。 以下示例显示了 ChannelListener 接口定义:org.springframework.amqp.rabbit.connection.Connectioncom.rabbitmq.client.BlockedListenerspring-doc.cn

@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}

请参阅发布是异步的 — 如何检测成功和失败,了解您可能希望注册 .ChannelListenerspring-doc.cn

记录通道关闭事件

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

它使用默认策略来记录 Channel Closures,如下所示:CachingConnectionFactoryspring-doc.cn

  • 正常通道关闭 (200 OK) 不会被记录。spring-doc.cn

  • 如果通道由于被动队列声明失败而关闭,则会在 debug 级别记录该通道。spring-doc.cn

  • 如果通道因独占消费者条件而被拒绝而关闭,则会在 INFO 级别。basic.consumespring-doc.cn

  • 所有其他记录都为 ERROR 级别。spring-doc.cn

要修改此行为,您可以将自定义注入其 in 其属性中。ConditionalExceptionLoggerCachingConnectionFactorycloseExceptionLoggerspring-doc.cn

运行时缓存属性

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

表 1.CacheMode.CHANNEL 的缓存属性
财产 意义
connectionName

由 生成的连接的名称。ConnectionNameStrategyspring-doc.cn

channelCacheSize

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

localPort

连接的本地端口(如果可用)。 这可用于与 RabbitMQ Admin UI 上的连接和通道相关联。spring-doc.cn

idleChannelsTx

当前处于空闲 (缓存) 状态的事务通道数。spring-doc.cn

idleChannelsNotTx

当前处于空闲 (缓存) 状态的非事务性通道数。spring-doc.cn

idleChannelsTxHighWater

已同时空闲(缓存)的事务通道的最大数量。spring-doc.cn

idleChannelsNotTxHighWater

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

下表描述了这些属性:CacheMode.CONNECTIONspring-doc.cn

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

由 生成的连接的名称。ConnectionNameStrategyspring-doc.cn

openConnections

表示与 broker 的连接的连接对象的数目。spring-doc.cn

channelCacheSize

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

connectionCacheSize

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

idleConnections

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

idleConnectionsHighWater

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

idleChannelsTx:<localPort>

此连接当前处于空闲 (缓存) 状态的事务通道数。 您可以使用属性名称的一部分与 RabbitMQ Admin UI 上的连接和通道相关联。localPortspring-doc.cn

idleChannelsNotTx:<localPort>

此连接当前处于空闲 (缓存) 状态的非事务性通道数。 属性名称的一部分可用于与 RabbitMQ Admin UI 上的连接和通道相关联。localPortspring-doc.cn

idleChannelsTxHighWater:<localPort>

已同时空闲(缓存)的事务通道的最大数量。 属性名称的 localPort 部分可用于与 RabbitMQ 管理 UI 上的连接和通道相关联。spring-doc.cn

idleChannelsNotTxHighWater:<localPort>

非事务性通道的最大数量已同时空闲(缓存)。 您可以使用属性名称的一部分与 RabbitMQ Admin UI 上的连接和通道相关联。localPortspring-doc.cn

属性 ( or ) 也包括在内。cacheModeCHANNELCONNECTIONspring-doc.cn

缓存统计信息
图 1.JVisualVM 示例
RabbitMQ 自动连接 / 拓扑恢复

自 Spring AMQP 的第一个版本以来,该框架在代理发生故障时提供了自己的连接和通道恢复。 此外,如 配置 Broker 中所述,在重新建立连接时,将重新声明任何基础结构 Bean(队列和其他)。 因此,它不依赖于磁带库现在提供的自动恢复。 默认情况下,, 已启用自动恢复。 两种恢复机制之间存在一些不兼容之处,因此,默认情况下, Spring 将底层的属性设置为 。 即使该属性是 ,Spring 也会通过立即关闭任何已恢复的连接来有效地禁用它。RabbitAdminamqp-clientamqp-clientautomaticRecoveryEnabledRabbitMQ connectionFactoryfalsetruespring-doc.cn

默认情况下,只有定义为 bean 的元素(queues、exchanges、bindings)才会在连接失败后被重新声明。 有关如何更改该行为,请参阅恢复自动删除声明

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

现在,允许您访问底层连接工厂,以允许,例如, 设置自定义客户端属性。 以下示例显示了如何执行此操作:CachingConnectionFactoryspring-doc.cn

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

查看连接时,这些属性将显示在 RabbitMQ Admin UI 中。spring-doc.cn

4.1.4.AmqpTemplate

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

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

添加重试功能

从版本 1.3 开始,您现在可以将 配置为使用 来帮助处理代理连接问题。 有关完整信息,请参见spring-retry项目。 以下只是一个使用指数回退策略和 default 的示例,该策略在将异常引发给调用方之前进行三次尝试。RabbitTemplateRetryTemplateSimpleRetryPolicyspring-doc.cn

以下示例使用 XML 命名空间:spring-doc.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 中的注释:@Configurationspring-doc.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 开始,除了属性之外,该选项还支持 . 它用作 .retryTemplaterecoveryCallbackRabbitTemplateRetryTemplate.execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)spring-doc.cn

这在一定程度上受到限制,因为重试上下文仅包含字段。 对于更复杂的用例,您应该使用 external,以便您可以通过上下文的属性将其他信息传达给 。 以下示例显示了如何执行此操作:RecoveryCallbacklastThrowableRetryTemplateRecoveryCallback
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;
        }
    });
}

在这种情况下,您不会将 a 注入 .RetryTemplateRabbitTemplatespring-doc.cn

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

发布消息是一种异步机制,默认情况下,RabbitMQ 会丢弃无法路由的消息。 要成功发布,您可以接收异步确认,如 Correlated Publisher Confirms and Returns 中所述。 请考虑两种故障情况:spring-doc.cn

第一种情况由发布者返回涵盖,如相关发布者确认和返回中所述。spring-doc.cn

对于第二种情况,消息被丢弃,并且不会生成任何返回。 底层通道因异常而关闭。 默认情况下,会记录此异常,但您可以向 注册 以获取此类事件的通知。 以下示例演示如何添加 :ChannelListenerCachingConnectionFactoryConnectionListenerspring-doc.cn

this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

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

});

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

要检测发送线程上的异常,您可以在 上检测到异常。 但是,事务会严重影响性能,因此在仅为这一个用例启用事务之前,请仔细考虑这一点。setChannelTransacted(true)RabbitTemplatetxCommit()spring-doc.cn

相关发布者确认并返回

的实现支持 publisher 确认并返回。RabbitTemplateAmqpTemplatespring-doc.cn

对于返回的消息,必须将模板的属性设置为或必须为特定消息的计算结果。 此功能需要 a 的 ,其属性设置为 (请参阅 发布者确认并返回)。 返回值通过调用注册 . 回调必须实现以下方法:mandatorytruemandatory-expressiontrueCachingConnectionFactorypublisherReturnstrueRabbitTemplate.ReturnsCallbacksetReturnsCallback(ReturnsCallback callback)spring-doc.cn

void returnedMessage(ReturnedMessage returned);

具有以下属性:ReturnedMessagespring-doc.cn

每个 只支持 1 个 。 另请参阅 回复超时ReturnsCallbackRabbitTemplatespring-doc.cn

对于 publisher confirms(也称为 publisher 确认),模板需要 a 的 property 设置为 。 确认通过调用注册 a 发送到客户端。 回调必须实现此方法:CachingConnectionFactorypublisherConfirmConfirmType.CORRELATEDRabbitTemplate.ConfirmCallbacksetConfirmCallback(ConfirmCallback callback)spring-doc.cn

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

这是客户端在发送原始消息时提供的对象。 对于 an 为 true,对于 为 false。 例如,原因可能包含 的原因,如果在生成 时可用。 例如,当向不存在的 exchange 发送消息时。 在这种情况下,代理将关闭通道。 关闭的原因包含在 中。 这是在 1.4 版本中添加的。CorrelationDataackacknacknacknacknackcausecausespring-doc.cn

只有一个受 .ConfirmCallbackRabbitTemplatespring-doc.cn

当 rabbit template send 操作完成时,通道将关闭。 当连接工厂缓存已满时,这排除了接收 confirms 或 returns (当缓存中有空间时,通道未物理关闭,返回和确认正常进行)。 当缓存已满时,框架会将关闭时间最多推迟 5 秒,以便有时间接收确认和返回。 使用 confirm 时,当收到最后一次确认时,通道将关闭。 当仅使用返回时,通道将保持打开状态整整 5 秒。 我们通常建议将 connection factory's 设置为足够大的值,以便将发布消息的通道返回到缓存,而不是关闭。 您可以使用 RabbitMQ 管理插件监控通道使用情况。 如果您看到通道正在快速打开和关闭,则应考虑增加高速缓存大小以减少服务器上的开销。channelCacheSize
在版本 2.1 之前,在收到确认之前,为发布者确认启用的渠道会返回到缓存中。 其他一些进程可以签出通道并执行一些导致通道关闭的操作 — 例如将消息发布到不存在的 exchange。 这可能会导致确认丢失。 版本 2.1 及更高版本在确认未完成时不再将通道返回到缓存。 每次操作后,都会在 channel 上执行 logical 操作。 通常,这意味着一次只有一个确认在通道上未完成。RabbitTemplateclose()
从版本 2.2 开始,回调是在连接工厂的线程之一上调用的。 这是为了避免在从回调中执行 Rabbit 操作时出现潜在的死锁。 在以前的版本中,回调直接在连接 I/O 线程上调用;如果您执行某些 RPC 操作(例如打开新通道),这将死锁,因为 I/O 线程阻塞等待结果,但结果需要由 I/O 线程本身处理。 对于这些版本,有必要将工作 (例如发送消息) 移交给回调中的另一个线程。 这不再是必需的,因为框架现在将回调调用移交给执行程序。executoramqp-client
只要返回回调在 60 秒或更短的时间内执行,就仍然保证在 ack 之前收到返回的消息。 确认计划在返回回传退出后或 60 秒后传递,以先到者为准。

该对象具有 a 可用于获取结果,而不是在模板上使用 a。 以下示例演示如何配置实例:CorrelationDataCompletableFutureConfirmCallbackCorrelationDataspring-doc.cn

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

由于它是一个 ,因此您可以在准备就绪时显示结果,也可以将其用于异步回调。 该对象是一个具有 2 个属性的简单 bean:和(例如)。 未为 broker 生成的实例填充原因。 它是为框架生成的实例填充的(例如,在实例未完成时关闭连接)。CompletableFuture<Confirm>get()whenComplete()Confirmackreasonnacknacknackackspring-doc.cn

此外,当同时启用 confirms 和 returns 时,如果无法将返回的消息路由到任何队列,则该属性将填充返回的消息。 可以保证在使用 . 返回 A with properties:CorrelationDatareturnackCorrelationData.getReturn()ReturnMessagespring-doc.cn

另请参阅 Scoped Operations 以获取等待发布者确认的更简单机制。spring-doc.cn

作用域操作

通常,在使用模板时,会从缓存中签出 (或创建) a ,用于操作,并返回到缓存以供重用。 在多线程环境中,不能保证下一个操作使用相同的通道。 但是,有时您可能希望对通道的使用进行更多控制,并确保在同一通道上执行许多操作。Channelspring-doc.cn

从版本 2.0 开始,提供了一个名为 的新方法,其中包含一个 . 在 callback 范围内和提供的参数上执行的任何操作都使用相同的 dedicated ,该操作将在结束时关闭(不会返回到缓存)。 如果渠道是 ,则在收到所有确认后,它会返回到缓存中(请参阅 Correlated Publisher Confirms and Returns)。invokeOperationsCallbackRabbitOperationsChannelPublisherCallbackChannelspring-doc.cn

@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}

您可能需要这样做的一个例子是,如果您希望在底层 . Spring API 以前没有公开此方法,因为如前所述,通道通常是缓存和共享的。 现在提供 和 ,它们委托给 在 . 出于显而易见的原因,这些方法不能在该范围之外使用。waitForConfirms()ChannelRabbitTemplatewaitForConfirms(long timeout)waitForConfirmsOrDie(long timeout)OperationsCallbackspring-doc.cn

请注意,其他位置提供了用于将 Confirm 与 requests 相关联的更高级别抽象(请参阅 Correlated Publisher Confirms and Returns)。 如果您只想等待 Broker 确认送达,则可以使用以下示例中所示的技术:spring-doc.cn

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

如果您希望在 scope of 的同一通道上调用操作,则必须使用用于操作的相同通道来构造 admin。RabbitAdminOperationsCallbackRabbitTemplateinvokespring-doc.cn

如果模板操作已经在现有事务的范围内执行,例如,在事务处理侦听器容器线程上运行并在事务处理模板上执行操作时,则前面的讨论没有意义。 在这种情况下,将在该通道上执行操作,并在线程返回到容器时提交。 在这种情况下,没有必要使用。invoke

以这种方式使用 confirms 时,实际上并不需要为将 confirm 与请求相关联而设置的大部分基础设施(除非还启用了 return)。 从版本 2.2 开始,连接工厂支持一个名为 . 当此设置为 时,将避免使用基础设施,并且确认处理可以更高效。publisherConfirmTypeConfirmType.SIMPLEspring-doc.cn

此外,还在 sent message 中设置属性。 如果您希望检查(或记录或以其他方式使用)特定的确认,您可以使用重载的方法来实现,如下例所示:RabbitTemplatepublisherSequenceNumberMessagePropertiesinvokespring-doc.cn

public <T> T invoke(OperationsCallback<T> action, com.rabbitmq.client.ConfirmCallback acks,
        com.rabbitmq.client.ConfirmCallback nacks);
这些对象(for 和 instances)是 Rabbit 客户端回调,而不是模板回调。ConfirmCallbackacknack

以下示例日志和实例:acknackspring-doc.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.cn

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

  • thread-1将消息发送到队列,并将工作移交给thread-2spring-doc.cn

  • thread-2将消息发送到同一队列spring-doc.cn

由于 RabbitMQ 的异步性质和缓存通道的使用;不确定是否会使用相同的通道,因此无法保证消息到达队列的顺序。 (在大多数情况下,它们会按顺序到达,但无序送达的概率不为零)。 要解决此用例,您可以使用大小为 size(与 a 一起使用)的有界通道缓存,以确保消息始终在同一通道上发布,并且保证顺序。 为此,如果连接工厂有其他用途(例如使用者),则应为模板使用专用连接工厂,或将模板配置为使用嵌入在主连接工厂中的 publisher 连接工厂(请参阅使用单独的连接)。1channelCheckoutTimeoutspring-doc.cn

这最好用一个简单的 Spring Boot 应用程序来说明:spring-doc.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.cn

从版本 2.3.7 开始,支持使用 and 方法将线程的通道转移到另一个线程。 第一个方法返回一个上下文,该上下文将传递给调用第二个方法的第二个线程。 线程可以具有绑定到非事务通道或事务通道(或每个通道中的一个)的 URL;除非使用两个连接工厂,否则无法单独传输它们。 示例如下:ThreadChannelConnectionFactoryprepareContextSwitchswitchContextspring-doc.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
消息传递集成

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

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

从版本 1.6 开始,模板现在支持 ( when using Java configuration)。 如果发送消息,则在评估此表达式后设置 user id 属性(如果尚未设置)。 评估的根对象是要发送的消息。user-id-expressionuserIdExpressionspring-doc.cn

以下示例演示如何使用该属性:user-id-expressionspring-doc.cn

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

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

第一个示例是 Literal 表达式。 第二个选项从应用程序上下文中的连接工厂 Bean 获取属性。usernamespring-doc.cn

使用单独的连接

从版本 2.0.2 开始,你可以将该属性设置为尽可能使用与侦听器容器使用的连接不同的连接。 这是为了避免当生产者因任何原因被阻止时,使用者被阻止。 为此,连接工厂维护第二个内部连接工厂;默认情况下,它与主工厂的类型相同,但如果您希望使用不同的工厂类型进行发布,则可以显式设置此类型。 如果 rabbit 模板在侦听器容器启动的事务中运行,则无论此设置如何,都会使用容器的通道。usePublisherConnectiontruespring-doc.cn

通常,您不应将 a 与将此设置为 的模板一起使用。 使用采用连接工厂的构造函数。 如果使用采用模板的其他构造函数,请确保模板的值为 。 这是因为,通常使用 admin 来声明侦听器容器的队列。 使用将属性设置为的模板意味着将在与侦听器容器使用的连接不同的连接上声明独占队列(例如 )。 在这种情况下,容器不能使用队列。RabbitAdmintrueRabbitAdminfalsetrueAnonymousQueue

4.1.5. 发送消息

发送消息时,您可以使用以下任一方法:spring-doc.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 交换名称(以及路由密钥)。 最后一个参数是负责实际创建 message 实例的回调。 使用此方法发送消息的示例可能如下所示: 以下示例演示如何使用该方法发送消息:sendspring-doc.cn

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

如果您计划在大部分或全部时间使用该模板实例发送到同一 exchange,则可以在模板本身上设置该属性。 在这种情况下,您可以使用前面清单中的第二种方法。 以下示例在功能上等同于前面的示例:exchangespring-doc.cn

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

如果在模板上同时设置了 和 属性,则可以使用仅接受 . 以下示例显示了如何执行此操作:exchangeroutingKeyMessagespring-doc.cn

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

考虑 exchange 和 routing key 属性的更好方法是显式方法参数始终覆盖模板的默认值。 事实上,即使您没有在模板上显式设置这些属性,也始终存在默认值。 在这两种情况下,默认值都是空的,但这实际上是一个明智的默认值。 就路由密钥而言,它并不总是必要的(例如,对于 一个交易所)。 此外,队列可以绑定到具有空 . 这些都是依赖模板的路由键属性的默认空值的合法方案。 就 exchange 名称而言,通常使用空,因为 AMQP 规范将 “default exchange” 定义为没有名称。 由于所有队列都自动绑定到该默认交换(这是直接交换),因此使用它们的名称作为绑定值,因此前面列表中的第二种方法可用于通过默认交换向任何队列进行简单的点对点消息传递。 您可以通过在运行时提供 method 参数来提供队列名称作为 。 以下示例显示了如何执行此操作:StringFanoutStringStringStringroutingKeyspring-doc.cn

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

或者,您可以创建一个模板,该模板可用于主要或专门发布到单个 Queue。 以下示例显示了如何执行此操作:spring-doc.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 开始,消息生成器 API 由 和 提供。 这些方法提供了一种方便的“Fluent”方法来创建消息或消息属性。 以下示例显示了 Fluent API 的运行情况:MessageBuilderMessagePropertiesBuilderspring-doc.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.cn

提供了五种静态方法来创建初始消息生成器:spring-doc.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 生成器创建的消息具有一个 body,该 body 是一个新数组,其中包含参数中的 bytes 副本。
3 生成器创建的消息具有一个 body,该 body 是一个新数组,其中包含参数中的字节范围。 有关更多详细信息,请参阅 Arrays.copyOfRange()
4 生成器创建的消息具有一个 body,该 body 是对参数 body 的直接引用。 参数的属性将复制到新对象。MessageProperties
5 生成器创建的消息具有一个 body,该 body 是一个包含参数 body 副本的新数组。 参数的属性将复制到新对象。MessageProperties

创建实例时,提供了三种静态方法:MessagePropertiesBuilderspring-doc.cn

public static MessagePropertiesBuilder newInstance() (1)

public static MessagePropertiesBuilder fromProperties(MessageProperties properties) (2)

public static MessagePropertiesBuilder fromClonedProperties(MessageProperties properties) (3)
1 使用默认值初始化新的 message properties 对象。
2 构建器使用提供的 properties 对象进行初始化,并将返回该对象。build()
3 参数的属性将复制到新对象。MessageProperties

通过 的实现,每个方法都有一个重载版本,该版本采用一个附加对象。 当 publisher 确认启用时,此对象将在 AmqpTemplate 中描述的回调中返回。 这允许发件人将确认 ( 或 ) 与发送的消息相关联。RabbitTemplateAmqpTemplatesend()CorrelationDataacknackspring-doc.cn

从版本 1.6.7 开始,引入了该接口,允许在转换消息后修改关联数据。 以下示例演示如何使用它:CorrelationAwareMessagePostProcessorspring-doc.cn

Message postProcessMessage(Message message, Correlation correlation);

在版本 2.0 中,此接口已弃用。 该方法已移至 默认实现,该实现委托给 .MessagePostProcessorpostProcessMessage(Message message)spring-doc.cn

同样从版本 1.6.7 开始,提供了一个名为 的新回调接口。 这是在所有实例之后调用的(在 method 中提供以及 中提供的实例)。 实现可以更新或替换方法中提供的相关数据(如果有)。 和 original (如果有) 作为参数提供。 以下示例演示如何使用该方法:CorrelationDataPostProcessorMessagePostProcessorsend()setBeforePublishPostProcessors()send()MessageCorrelationDatapostProcessspring-doc.cn

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

当模板的 property 为 时,返回的消息由 AmqpTemplate 中描述的回调提供。mandatorytruespring-doc.cn

从版本 1.4 开始,支持 SPEL 属性,该属性作为根评估对象针对每个请求消息进行评估,并解析为一个值。 可以在表达式中使用 Bean 引用(如 )。RabbitTemplatemandatoryExpressionboolean@myBean.isMandatory(#root)spring-doc.cn

发布者返回也可以由 in send 和 receive 操作在内部使用。 有关更多信息,请参阅 Reply Timeout (回复超时)。RabbitTemplatespring-doc.cn

配料

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

public interface BatchingStrategy {

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

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

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

提供 A。 它支持将消息发送到单个 exchange 或 routing key。 它具有以下属性:SimpleBatchingStrategyspring-doc.cn

  • batchSize:在发送批次之前,批次中的消息数。spring-doc.cn

  • bufferLimit:批处理消息的最大大小。 如果超出,这将抢占 ,并导致发送部分批处理。batchSizespring-doc.cn

  • timeout:当没有新活动向批次添加消息时,将发送部分批次的时间。spring-doc.cn

该命令通过在每条嵌入消息前面使用四字节的二进制长度来格式化批处理。 通过将 message 属性设置为 ,将此消息传达给接收系统。SimpleBatchingStrategyspringBatchFormatlengthHeader4spring-doc.cn

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

但是,有关更多信息,请参阅使用 Batching @RabbitListenerspring-doc.cn

4.1.6. 接收消息

消息接收总是比发送要复杂一些。 有两种方法可以接收 . 更简单的选项是使用 polling 方法调用一次轮询一个。 更复杂但更常见的方法是注册一个按需异步接收的侦听器。 在接下来的两个小节中,我们将考虑每种方法的示例。MessageMessageMessagesspring-doc.cn

轮询消费者

本身可用于轮询接收。 默认情况下,如果没有可用的消息,则立即返回。 没有阻塞。 从版本 1.5 开始,您可以设置 , 以毫秒为单位,并且 receive methods 块最多保持该长度,等待消息。 小于零的值表示无限期阻止(或至少在与 broker 的连接丢失之前)。 版本 1.6 引入了允许在每次调用时传入超时的方法的变体。AmqpTemplateMessagenullreceiveTimeoutreceivespring-doc.cn

由于 receive 操作会为每条消息创建一个新消息,因此此技术并不真正适用于高容量环境。 对于这些使用案例,请考虑使用异步使用者或 a 为零。QueueingConsumerreceiveTimeout

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

有四种简单的方法可用。 与发送端一样,有一个方法需要设置默认队列属性 直接放在模板本身上,并且有一个在运行时接受 queue 参数的方法。 版本 1.6 引入了 accept 变体,以基于每个请求覆盖。 下面的清单显示了这四种方法的定义:receiveExchangetimeoutMillisreceiveTimeoutspring-doc.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;

与发送消息的情况一样,它有一些方便的方法来接收 POJO 而不是实例,并且实现提供了一种自定义用于创建返回结果的方法: 下面的清单显示了这些方法:AmqpTemplateMessageMessageConverterObjectspring-doc.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 开始,这些方法有一些变体,它们采用额外的参数来转换复杂类型。 模板必须配置有 . 有关更多信息,请参阅使用 RabbitTemplate消息转换ParameterizedTypeReferenceSmartMessageConverterspring-doc.cn

与 methods 类似,从 1.3 版本开始,它有几种用于同步接收、处理和回复消息的便捷方法。 下面的清单显示了这些方法定义:sendAndReceiveAmqpTemplatereceiveAndReplyspring-doc.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;

实施负责 and 阶段。 在大多数情况下,您应该只提供 的实现,以便为收到的消息执行一些业务逻辑,并在需要时构建回复对象或消息。 请注意,a 可能会返回 . 在这种情况下,不会发送任何回复,其工作方式与该方法类似。 这允许将同一队列用于混合消息,其中一些消息可能不需要回复。AmqpTemplatereceivereplyReceiveAndReplyCallbackReceiveAndReplyCallbacknullreceiveAndReplyreceivespring-doc.cn

仅当提供的回调不是 的实例时,才会应用自动消息(请求和回复)转换,该实例提供原始消息交换协定。ReceiveAndReplyMessageCallbackspring-doc.cn

对于需要自定义逻辑在运行时根据收到的消息确定地址并从 . 默认情况下,请求消息中的信息用于路由回复。ReplyToAddressCallbackreplyToReceiveAndReplyCallbackreplyTospring-doc.cn

下面的清单显示了基于 POJO 的接收和回复的示例:spring-doc.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 还通过使用 Comments 来支持带 Comments 的侦听器端点,并提供了一个开放的基础设施来以编程方式注册端点。 这是迄今为止设置 asynchronous consumer 的最便捷方法。 有关更多详细信息,请参阅 Annotation-driven Listener Endpoints@RabbitListener

prefetch 默认值以前为 1,这可能导致高效消费者的利用率不足。 从版本 2.0 开始,默认的 prefetch 值现在是 250,在大多数常见情况下,这应该让消费者忙碌起来,并且 从而提高吞吐量。spring-doc.cn

但是,在某些情况下,预取值应该较低:spring-doc.cn

  • 对于大型消息,尤其是在处理速度较慢的情况下(消息可能会在 Client 端进程中累积大量内存)spring-doc.cn

  • 当需要严格的消息排序时(在这种情况下,prefetch 值应设置回 1)spring-doc.cn

  • 其他特殊情况spring-doc.cn

此外,对于低容量消息收发和多个使用者(包括单个侦听器容器实例中的并发),您可能希望减少预取,以便在使用者之间更均匀地分配消息。spring-doc.cn

有关预取的更多背景信息,请参阅这篇关于 RabbitMQ 中的使用者利用率的博文和这篇关于排队理论的博文。spring-doc.cn

消息侦听器

对于异步接收,涉及专用组件(不是 )。 该组件是 -consuming 回调的容器。 我们将在本节后面讨论容器及其属性。 不过,首先,我们应该查看回调,因为这是您的应用程序代码与消息传递系统集成的地方。 回调有几个选项,从接口的实现开始,下面的清单显示了:MessageAmqpTemplateMessageMessageListenerspring-doc.cn

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

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

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

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

版本 1.5 引入了一种更灵活的 POJO 消息传递机制,即 Annotation。 有关更多信息,请参阅 Annotation-driven Listener Endpoints@RabbitListener

使用适配器时,只需提供对适配器本身应调用的实例的引用。 以下示例显示了如何执行此操作:spring-doc.cn

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

您可以将适配器子类化并提供 的实现,以根据消息动态选择不同的方法。 此方法有两个参数,而 和 ,后者是任何转换的结果。 默认情况下,配置了 a。 参见 SimpleMessageConverter 以获取有关其他可用转换器的更多信息。getListenerMethodName()originalMessageextractedMessageSimpleMessageConverterspring-doc.cn

从版本 1.4.2 开始,原始消息具有 and 属性,可用于确定从中接收消息的队列。consumerQueueconsumerTagspring-doc.cn

从版本 1.5 开始,你可以配置消费者队列或标签到方法名称的映射,以动态选择要调用的方法。 如果 map 中没有条目,我们将回退到默认的 listener 方法。 默认侦听器方法(如果未设置)为 .handleMessagespring-doc.cn

从版本 2.0 开始,提供了方便的。 以下清单显示了 的定义:FunctionalInterfaceFunctionalInterfacespring-doc.cn

@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}

此接口有助于使用 Java 8 lambda 方便地配置适配器,如下例所示:spring-doc.cn

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

从版本 2.2 开始,已弃用,并引入了新的。 新方法可帮助侦听器获取和参数执行更多操作,例如在手动确认模式下调用。 下面的清单显示了最基本的示例:buildListenerArguments(Object)buildListenerArguments(Object, Channel, Message)ChannelMessagechannel.basicReject(long, boolean)spring-doc.cn

public class ExtendedListenerAdapter extends MessageListenerAdapter {

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

}

现在你可以像需要接收 “channel” 和 “message” 一样进行配置。 listener 的参数应设置为 returned,如下 listener 示例所示:ExtendedListenerAdapterMessageListenerAdapterbuildListenerArguments(Object, Channel, Message)spring-doc.cn

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

现在您已经看到了 -listening 回调的各种选项,我们可以将注意力转向容器。 基本上,容器处理 “主动” 职责,以便侦听器回调可以保持被动状态。 容器是 “lifecycle” 组件的一个示例。 它提供了启动和停止的方法。 在配置容器时,您实际上是弥合了 AMQP 队列和实例之间的差距。 您必须提供对 和 队列名称或 Queue 实例的引用,该侦听器应从中使用消息。MessageMessageListenerConnectionFactoryspring-doc.cn

在 2.0 版本之前,有一个侦听器容器,即 . 现在有第二个容器 . 选择容器中介绍了选择要使用的容器和可能应用的容器之间的差异。SimpleMessageListenerContainerDirectMessageListenerContainerspring-doc.cn

下面的清单显示了最基本的示例,该示例通过使用 :SimpleMessageListenerContainerspring-doc.cn

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

作为一个 “活动” 组件,最常见的是使用 bean 定义创建侦听器容器,以便它可以在后台运行。 下面的示例演示了使用 XML 实现此目的的一种方法:spring-doc.cn

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

下面的清单显示了使用 XML 实现此目的的另一种方法:spring-doc.cn

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

前面的两个示例都创建了一个 (注意属性 — 它默认为 )。DirectMessageListenerContainertypesimplespring-doc.cn

或者,您可能更喜欢使用 Java 配置,它看起来类似于前面的代码片段:spring-doc.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 中使用使用者优先级)。 这是通过在 consumer 上设置参数来启用的。 现在支持设置使用者参数,如下例所示:x-prioritySimpleMessageListenerContainerspring-doc.cn

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

为方便起见,命名空间在元素上提供了属性,如下例所示:prioritylistenerspring-doc.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.cn

auto-delete队列

当容器配置为侦听队列时,队列具有选项,或者在 Broker 上配置了 Time-To-Live 策略,则当容器停止时(即,当最后一个使用者被取消时),代理将删除队列。 在版本 1.3 之前,由于缺少队列,无法重新启动容器。 当连接关闭或打开时,唯一会自动重新声明队列等,这在容器停止和启动时不会发生。auto-deletex-expiresRabbitAdminspring-doc.cn

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

你也可以将条件声明(参见 条件声明)与 admin 一起使用,将队列声明推迟到容器启动。 以下示例显示了如何执行此操作:auto-startup="false"spring-doc.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" />

在这种情况下,queue 和 exchange 由 声明,因此在上下文初始化期间不会声明元素。 此外,容器由于同样的原因未启动。 稍后启动容器时,它使用其引用来声明元素。containerAdminauto-startup="false"containerAdminspring-doc.cn

批量消息

批处理的消息(由创建者创建)由侦听器容器(使用消息标头)自动取消批处理。 拒绝来自批处理的任何消息将导致整个批处理被拒绝。 有关批处理的更多信息,请参阅批处理springBatchFormatspring-doc.cn

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

设置 container 属性以启用此功能。 还必须为 true,以便容器负责处理这两种类型的批处理。 Implement 或 when 为 true。 从版本 2.2.7 开始,创建者 和 都可以取消批处理创建者将批处理创建为 . 有关将此功能与 @RabbitListener 一起使用的信息,请参阅 使用批处理consumerBatchEnableddeBatchingEnabledBatchMessageListenerChannelAwareBatchMessageListenerconsumerBatchEnabledSimpleMessageListenerContainerDirectMessageListenerContainerList<Message>@RabbitListenerspring-doc.cn

消费者事件

每当侦听器出现 (消费者)经历某种失败。 该事件具有以下属性:ListenerContainerConsumerFailedEventspring-doc.cn

  • container:使用者遇到问题的侦听器容器。spring-doc.cn

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

  • fatal:一个布尔值,指示失败是否为致命故障。 对于非致命异常,容器会尝试根据 或 (对于 ) 或 (对于 ) 重新启动使用者。recoveryIntervalrecoveryBackoffSimpleMessageListenerContainermonitorIntervalDirectMessageListenerContainerspring-doc.cn

  • throwable: 被抓住的。Throwablespring-doc.cn

这些事件可以通过实施来使用。ApplicationListener<ListenerContainerConsumerFailedEvent>spring-doc.cn

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

如果使用者因队列被独占使用而失败(默认情况下),并且发布事件时,将发出日志。 要更改此日志记录行为,请在实例的 property 中提供 custom。 另请参阅记录通道关闭事件WARNConditionalExceptionLoggerSimpleMessageListenerContainerexclusiveConsumerExceptionLoggerspring-doc.cn

致命错误始终记录在 级别。 这是不可修改的。ERRORspring-doc.cn

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

  • AsyncConsumerStartedEvent:当使用者启动时。spring-doc.cn

  • AsyncConsumerRestartedEvent:仅当使用者在失败后重新启动时 - 仅限。SimpleMessageListenerContainerspring-doc.cn

  • AsyncConsumerTerminatedEvent:当消费者正常停止时。spring-doc.cn

  • AsyncConsumerStoppedEvent:当使用者停止时 - 仅。SimpleMessageListenerContainerspring-doc.cn

  • ConsumeOkEvent:当从代理收到 a 时,包含队列名称和consumeOkconsumerTagspring-doc.cn

  • ListenerContainerIdleEvent:请参阅 检测空闲的异步使用者spring-doc.cn

  • MissingQueueEvent:检测到缺失队列时。spring-doc.cn

消费者标签

您可以提供生成消费者标签的策略。 默认情况下,consumer 标签由 broker 生成。 下面的清单显示了接口定义:ConsumerTagStrategyspring-doc.cn

public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}

队列可用,因此可以(可选)在标签中使用。spring-doc.cn

注释驱动的侦听器端点

异步接收消息的最简单方法是使用带注释的侦听器终端节点基础设施。 简而言之,它允许您将托管 bean 的方法公开为 Rabbit 侦听器终端节点。 以下示例演示如何使用注释:@RabbitListenerspring-doc.cn

@Component
public class MyService {

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

}

前面示例的思路是,每当名为 的队列上有消息可用时,就会相应地调用该方法(在本例中,使用消息的有效负载)。myQueueprocessOrderspring-doc.cn

带注释的终端节点基础结构在幕后为每个带注释的方法创建一个消息侦听器容器,方法是使用 .RabbitListenerContainerFactoryspring-doc.cn

在前面的示例中, 必须已经存在并绑定到某个 exchange。 只要 存在于应用程序上下文中,就可以自动声明和绑定队列。myQueueRabbitAdminspring-doc.cn

可以为 Comments 属性 ( etc) 指定属性占位符 () 或 SPEL 表达式 () 。 有关为什么可以使用 SPEL 而不是属性占位符的示例,请参见侦听多个队列。 下面的清单显示了如何声明 Rabbit 侦听器的三个示例:${some.property}#{someExpression}queues
@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) {
      ...
  }

}

在第一个示例中,如果需要,队列将与 exchange 一起自动声明 (durable) 并使用路由密钥绑定到 Exchange。 在第二个示例中,声明并绑定了一个匿名(独占、自动删除)队列;队列名称由框架使用 . 您不能使用此技术声明代理命名的队列;它们需要声明为 bean 定义;请参阅 容器和 Broker-Named 队列。 可以提供多个条目,让侦听器侦听多个队列。 在第三个示例中,如有必要,将声明具有 name retrieved from 属性的队列,并使用队列名称作为路由键将 default 绑定到 default exchange。myQueueBase64UrlNamingStrategyQueueBindingmy.queuespring-doc.cn

从 2.0 版本开始,该 annotation 支持任何 exchange 类型,包括 custom。 有关更多信息,请参阅 AMQP 概念@Exchangespring-doc.cn

当您需要更高级的配置时,可以使用普通定义。@Beanspring-doc.cn

请注意第一个例子中的 exchange。 例如,这允许绑定到可能具有不同设置的现有交换(例如 )。 默认情况下,现有 Exchange 的属性必须匹配。ignoreDeclarationExceptionsinternalspring-doc.cn

从版本 2.0 开始,您现在可以将队列绑定到具有多个路由键的 Exchange,如下例所示:spring-doc.cn

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

您还可以在 queues、exchanges、 和 bindings,如下例所示:@QueueBindingspring-doc.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) {
    ...
}

请注意,队列的参数设置为 10 秒。 由于参数类型不是 ,我们必须指定其类型 — 在本例中为 。 与所有此类声明一样,如果队列已存在,则参数必须与队列中的参数匹配。 对于标头交换,我们设置绑定参数以匹配标头设置为 的消息,并且 标头必须与任何值一起存在。 该参数意味着必须满足这两个条件。x-message-ttlStringIntegerthing1somevaluething2x-matchspring-doc.cn

参数名称、值和类型可以是属性占位符 () 或 SPEL 表达式 ()。 必须解析为 . 的表达式 must resolve 为 a 或类的完全限定名称。 必须解析为可由 the 类型转换为 (如前面示例中的 the ) 。${…​}#{…​}nameStringtypeClassvalueDefaultConversionServicex-message-ttlspring-doc.cn

如果名称解析为或为空 ,则将被忽略。nullString@Argumentspring-doc.cn

元注释

有时,您可能希望对多个侦听器使用相同的配置。 要减少样板配置,您可以使用 meta-annotations 创建自己的侦听器注释。 以下示例显示了如何执行此操作:spring-doc.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) {
        ...
    }

}

在前面的示例中,注释创建的每个侦听器都绑定了一个匿名的自动删除 queue 发送到 fanout 交换 。 从版本 2.2.3 开始,支持允许覆盖元注释 Comments 上的属性。 此外,用户注释现在可以为 ,允许为一个方法创建多个容器。@MyAnonFanoutListenermetaFanout@AliasFor@Repeatablespring-doc.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();

}
启用 Listener Endpoint Annotations

要启用对 annotation 的支持,您可以添加到其中一个类中。 以下示例显示了如何执行此操作:@RabbitListener@EnableRabbit@Configurationspring-doc.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 版本开始,a 也可用。 它创建实例。DirectMessageListenerContainerFactoryDirectMessageListenerContainerspring-doc.cn

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

从版本 2.2.2 开始,您可以提供一个 implementation (如上所示)。 这可用于在创建和配置容器后进一步配置容器;例如,您可以使用它来设置容器工厂未公开的属性。ContainerCustomizerspring-doc.cn

版本 2.4.8 提供了 适用于您希望应用多个定制器的情况。CompositeContainerCustomizerspring-doc.cn

默认情况下,基础结构会查找名为 Bean 的 Bean,作为工厂的源,以用于创建消息侦听器容器。 在这种情况下,忽略 RabbitMQ 基础设施设置,可以使用核心轮询大小为 3 个线程和最大池大小为 10 个线程来调用该方法。rabbitListenerContainerFactoryprocessOrderspring-doc.cn

您可以自定义侦听器容器工厂以用于每个注释,也可以通过实现接口来配置显式默认值。 仅当注册了至少一个端点而没有特定的容器工厂时,才需要默认值。 请参阅 Javadoc 以获取完整的详细信息和示例。RabbitListenerConfigurerspring-doc.cn

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

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

从版本 2.0.6 开始,你可以将 and 添加到侦听器容器工厂中。 它在发送回复时使用。 当重试次数用尽时,将调用 。 您可以使用 a 从上下文中获取信息。 以下示例显示了如何执行此操作:RetryTemplateRecoveryCallbackRecoveryCallbackSendRetryContextAccessorspring-doc.cn

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

如果您更喜欢 XML 配置,则可以使用 element. 将检测到任何带有 Comments 的 bean。<rabbit:annotation-driven>@RabbitListenerspring-doc.cn

例如,您可以使用类似于以下内容的 XML:SimpleRabbitListenerContainerspring-doc.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>

例如,您可以使用类似于以下内容的 XML:DirectMessageListenerContainerspring-doc.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 开始,注解具有一个属性。 它支持 SpEL 表达式 () 和属性占位符 ()。 其含义和允许的值取决于容器类型,如下所示:@RabbitListenerconcurrency#{…​}${…​}spring-doc.cn

  • 对于 ,该值必须是单个整数值,该值设置容器上的属性。DirectMessageListenerContainerconsumersPerQueuespring-doc.cn

  • 对于 ,该值可以是单个整数值,用于设置容器上的属性,也可以具有格式 ,其中 是属性 和 是属性。SimpleRabbitListenerContainerconcurrentConsumersm-nmconcurrentConsumersnmaxConcurrentConsumersspring-doc.cn

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

该注解还允许通过 和 (自 2.2 起) 注解属性覆盖 factory 和 properties。 对每个执行程序使用不同的执行程序可能有助于识别与日志和线程转储中的每个侦听器关联的线程。autoStartuptaskExecutorautoStartupexecutorspring-doc.cn

版本 2.2 还添加了 property ,它允许您覆盖容器工厂的属性。ackModeacknowledgeModespring-doc.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);
}
带注释的方法的消息转换

在调用侦听器之前,管道中有两个转换步骤。 第一步使用 a 将传入的 Spring AMQP 转换为 Spring-messaging 。 调用目标方法时,如有必要,消息负载将转换为 method 参数类型。MessageConverterMessageMessagespring-doc.cn

第一步的默认值是 Spring AMQP,它处理到 and 对象的转换。 所有其他 Cookie 仍为 . 在下面的讨论中,我们将其称为 “消息转换器”。MessageConverterSimpleMessageConverterStringjava.io.Serializablebyte[]spring-doc.cn

第二步的默认转换器是 a ,它委托给转换服务 (的实例)。 在下面的讨论中,我们将其称为 “method argument converter”。GenericMessageConverterDefaultFormattingConversionServicespring-doc.cn

要更改消息转换器,可以将其作为属性添加到容器工厂 Bean 中。 以下示例显示了如何执行此操作:spring-doc.cn

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

这将配置一个 Jackson2 转换器,该转换器需要存在 Headers 信息以指导转换。spring-doc.cn

您还可以使用 ,它可以处理不同内容类型的转换。ContentTypeDelegatingMessageConverterspring-doc.cn

从版本 2.3 开始,你可以通过在属性中指定 bean 名称来覆盖工厂转换器。messageConverterspring-doc.cn

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

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

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

在大多数情况下,没有必要自定义方法参数转换器,除非你想使用 自定义 .ConversionServicespring-doc.cn

在 1.6 之前的版本中,必须在消息标头中提供用于转换 JSON 的类型信息,或者 需要定制。 从版本 1.6 开始,如果没有类型信息 headers,则可以从目标推断类型 method 参数。ClassMapperspring-doc.cn

此类型推断仅适用于方法级别。@RabbitListener

有关更多信息,请参见Jackson2JsonMessageConverterspring-doc.cn

如果您希望自定义方法参数转换器,可以按如下方式进行:spring-doc.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());
    }

    ...

}
对于多方法侦听器(请参阅多方法侦听器),方法选择基于消息转换后消息的有效负载。 只有在选择方法后,才会调用方法参数转换器。
将自定义添加到@RabbitListenerHandlerMethodArgumentResolver

从版本 2.3.7 开始,您可以添加自己的和解析自定义方法参数。 你只需要实现和使用类 中的方法。HandlerMethodArgumentResolverRabbitListenerConfigurersetCustomMethodArgumentResolvers()RabbitListenerEndpointRegistrarspring-doc.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 终端节点的模型,并负责为该模型配置容器。 除了注释检测到的终端节点之外,基础设施还允许您以编程方式配置终端节点。 以下示例显示了如何执行此操作:RabbitListenerspring-doc.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);
    }
}

在前面的示例中,我们使用了 ,它提供了实际的调用,但您也可以构建自己的终端节点变体来描述自定义调用机制。SimpleRabbitListenerEndpointMessageListenerspring-doc.cn

应该注意的是,您也可以完全跳过 的使用,并通过 以编程方式注册您的端点。@RabbitListenerRabbitListenerConfigurerspring-doc.cn

带注释的端点方法签名

到目前为止,我们已经在端点中注入了一个 simple,但它实际上可以有一个非常灵活的方法签名。 以下示例重写它以注入自定义标头:StringOrderspring-doc.cn

@Component
public class MyService {

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

以下列表显示了可与侦听器终端节点中的参数匹配的参数:spring-doc.cn

  • 原始的 .org.springframework.amqp.core.Messagespring-doc.cn

  • 从原始 .MessagePropertiesMessagespring-doc.cn

  • 接收消息的 。com.rabbitmq.client.Channelspring-doc.cn

  • 从传入的 AMQP 消息转换而来。org.springframework.messaging.Messagespring-doc.cn

  • @Header-annotated 方法参数来提取特定的标头值,包括标准 AMQP 标头。spring-doc.cn

  • @Headers-annotated 参数,该参数也必须可分配给才能访问所有标头。java.util.Mapspring-doc.cn

  • 转换后的有效负载spring-doc.cn

不是受支持类型之一(即 , , 和 )的非注释元素与有效负载匹配。 您可以通过使用 . 您还可以通过添加额外的 .MessageMessagePropertiesMessage<?>Channel@Payload@Validspring-doc.cn

注入 Spring 的消息抽象的能力特别有用,它可以从存储在特定于传输的消息中的所有信息中受益,而无需依赖特定于传输的 API。 以下示例显示了如何执行此操作:spring-doc.cn

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

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

例如,如果我们想在处理之前确保我们的 payload 是有效的,我们可以用 payload 注释并配置必要的 validator,如下所示:Order@Validspring-doc.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 开始,现在更容易添加 to validate 和 arguments。 现在,您只需将验证器添加到注册商本身即可。Validator@RabbitListener@RabbitHandler@Payloadspring-doc.cn

@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
当将 Spring Boot 与验证Starters一起使用时,会自动配置 a:LocalValidatorFactoryBean
@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    @Autowired
    private LocalValidatorFactoryBean validator;
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(this.validator);
    }
}

验证:spring-doc.cn

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) -> {
        ...
    };
}
侦听多个队列

使用该属性时,您可以指定关联的容器可以侦听多个队列。 您可以使用注释使从中接收消息的队列名称可供 POJO 使用 方法。 以下示例显示了如何执行此操作:queues@Headerspring-doc.cn

@Component
public class MyService {

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

}

从版本 1.5 开始,您可以使用属性占位符和 SPEL 来外部化队列名称。 以下示例显示了如何执行此操作:spring-doc.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.cn

回复管理

中的现有支持已经允许您的方法具有非 void 返回类型。 在这种情况下,调用的结果将封装在发送到原始邮件报头中指定的地址或侦听程序上配置的默认地址的消息中。 您可以使用消息传递抽象的注释来设置该默认地址。MessageListenerAdapterReplyToAddress@SendTospring-doc.cn

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

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

如果需要以与传输无关的方式设置其他标头,则可以返回 a,如下所示:Messagespring-doc.cn

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

或者,您可以使用 in the container factory 属性添加更多标头。 从版本 2.2.3 开始,被调用的 bean/方法在回复消息中可用,它可以在消息后处理器中使用,将信息传回给调用者:MessagePostProcessorbeforeSendReplyMessagePostProcessorsspring-doc.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 开始,您可以配置 a 以在发送回复消息之前对其进行修改;在将标头设置为匹配请求后调用它。ReplyPostProcessorcorrelationIdspring-doc.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 开始,您可以在容器工厂而不是 Comments 上配置后处理器。spring-doc.cn

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

参数为侦听器 ID。idspring-doc.cn

注释上的设置将取代出厂设置。spring-doc.cn

该值被假定为遵循模式 其中,可以省略其中一个部分。 取值如下:@SendToexchangeroutingKeyexchange/routingKeyspring-doc.cn

  • thing1/thing2:交换和 .:交换和默认(空)。 或 :和默认(空)交易所。 or empty:默认 exchange 和默认 。replyToroutingKeything1/replyToroutingKeything2/thing2replyToroutingKey/replyToroutingKeyspring-doc.cn

此外,您可以在没有 attribute 的情况下使用。 这种情况等于空模式。 仅在入站消息没有属性时使用。@SendTovaluesendTo@SendToreplyToAddressspring-doc.cn

从版本 1.5 开始,该值可以是 bean 初始化 SPEL 表达式,如以下示例所示:@SendTospring-doc.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";
}

表达式的计算结果必须为 ,它可以是简单的队列名称(发送到默认交换)或使用 前面示例之前讨论的表单。Stringexchange/routingKeyspring-doc.cn

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

对于动态回复路由,邮件发件人应包含 message 属性或使用备用 运行时 SpEL 表达式(在下一个示例后描述)。reply_tospring-doc.cn

从版本 1.6 开始,可以是在运行时根据请求评估的 SPEL 表达式 和 reply,如下例所示:@SendTospring-doc.cn

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

SPEL 表达式的运行时性质用分隔符表示。 表达式的 evaluation context 对象具有三个属性:!{…​}#rootspring-doc.cn

上下文有一个 map 属性访问器、一个标准类型转换器和一个 bean 解析器,它允许 上下文(例如,)。@someBeanName.determineReplyQ(request, result)spring-doc.cn

总之,在初始化期间评估一次,对象是应用程序上下文。 Bean 由其名称引用。 在运行时针对每条消息进行评估,其中根对象具有前面列出的属性。 Bean 以其名称引用,前缀为 .#{…​}#root!{…​}@spring-doc.cn

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

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

如果您使用的是复杂的消息转换器(如 ),则可以通过在侦听器上设置属性来控制回复的内容类型。 这允许转换器为回复选择合适的 delegate converter。ContentTypeDelegatingMessageConverterreplyContentTypespring-doc.cn

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

默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被此值覆盖。 转换器(如 )使用回复类型而不是内容类型来确定所需的转换,并在回复消息中适当地设置内容类型。 这可能不是所需的操作,可以通过将属性设置为 来覆盖。 例如,如果返回包含 JSON 的 JSON,则会将回复中的内容类型设置为 。 以下配置将确保正确设置内容类型,即使使用了 也是如此。SimpleMessageConverterconverterWinsContentTypefalseStringSimpleMessageConvertertext/plainSimpleMessageConverterspring-doc.cn

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

当返回类型为 Spring AMQP 或 Spring Messaging 时,这些属性( 和 )不适用。 在第一种情况下,不涉及转换;只需设置 message 属性即可。 在第二种情况下,使用消息标头控制行为:replyContentTypeconverterWinsContentTypeMessageMessage<?>contentTypespring-doc.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();
}

此内容类型将在 中传递到转换器。 默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被此值覆盖。 如果您希望覆盖该行为,还可以设置 to ,并且转换器设置的任何值都将被保留。MessagePropertiesAmqpHeaders.CONTENT_TYPE_CONVERTER_WINStruespring-doc.cn

多方法侦听器

从版本 1.5.0 开始,您可以在类级别指定注释。 与新注解一起,这允许单个侦听器根据 传入消息的有效负载类型。 最好用一个例子来描述这一点:@RabbitListener@RabbitHandlerspring-doc.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) {
        ...
    }

}

在这种情况下,如果转换的有效负载是 a 、 a 或 a ,则会调用各个方法。 您应该了解,系统必须能够根据负载类型识别唯一方法。 检查类型是否可分配给没有注释或带有注释的单个参数。 请注意,相同的方法签名适用,如方法级别(如前所述)中所述。@RabbitHandlerThing2CatHat@Payload@RabbitListenerspring-doc.cn

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

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

从版本 1.6 开始,批注标有 . 这意味着 Comments 可以多次出现在同一个带 Comments 的元素 (方法或类) 上。 在这种情况下,将为每个注释创建一个单独的侦听器容器,每个注释都调用相同的侦听器。 可重复的注释可以与 Java 8 或更高版本一起使用。@RabbitListener@Repeatable@Beanspring-doc.cn

代理和泛型@RabbitListener

如果您的服务是要代理的(例如,在 的情况下),则应记住一些注意事项,当 该接口具有泛型参数。 请考虑以下示例:@Transactionalspring-doc.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 目标类代理,因为接口方法的实际实现是桥接方法。 在事务管理的情况下,CGLIB 的使用是通过 注释选项: . 在这种情况下,必须在实现中的目标方法上声明所有 Comments,如下例所示:handle@EnableTransactionManagement(proxyTargetClass = true)spring-doc.cn

static class TxServiceImpl implements TxService<Foo> {

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

}
处理异常

默认情况下,如果带注释的侦听器方法引发异常,则会将其抛出到容器中,并且消息将被重新排队并重新传送、丢弃或路由到死信交换,具体取决于容器和代理配置。 不会向发件人返回任何内容。spring-doc.cn

从版本 2.0 开始,注释具有两个新属性: 和 .@RabbitListenererrorHandlerreturnExceptionsspring-doc.cn

默认情况下,这些未配置。spring-doc.cn

您可以使用 来提供实现的 bean 名称。 该功能接口有一种方法,如下所示:errorHandlerRabbitListenerErrorHandlerspring-doc.cn

@FunctionalInterface
public interface RabbitListenerErrorHandler {

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

}

如您所见,您可以访问从容器接收的原始消息、消息转换器生成的 spring-messaging 对象以及侦听器抛出的异常(包装在 a 中)。 错误处理程序可以返回一些结果(作为回复发送)或引发原始异常或新异常(根据设置引发到容器或返回给发送方)。Message<?>ListenerExecutionFailedExceptionreturnExceptionsspring-doc.cn

属性 when 会导致向发送方返回异常。 异常包装在对象中。 在发送方,有一个 available ,如果将其配置到 中,则会重新引发服务器端异常,包装在 . 服务器异常的堆栈跟踪是通过合并服务器和客户端堆栈跟踪来合成的。returnExceptionstrueRemoteInvocationResultRemoteInvocationAwareMessageConverterAdapterRabbitTemplateAmqpRemoteExceptionspring-doc.cn

此机制通常仅适用于使用 Java 序列化的 default。 异常通常不是“Jackson 友好”的,并且不能序列化为 JSON。 如果您使用 JSON,请考虑在引发异常时使用 an 返回其他一些 Jackson 友好对象。SimpleMessageConvertererrorHandlerError
在版本 2.1 中,此接口从 package 移至 .o.s.amqp.rabbit.listenero.s.amqp.rabbit.listener.api

从版本 2.1.7 开始,在消息 Headers 中可用;这允许你在使用 时对失败的消息进行 ACK 或 NACK :ChannelAcknowledgeMode.MANUALspring-doc.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 开始,如果引发消息转换异常,将调用错误处理程序,并在参数中使用。 这允许应用程序向调用者发送一些结果,指示收到了格式错误的消息。 以前,此类错误由容器引发和处理。nullmessagespring-doc.cn

容器管理

为注释创建的容器未注册到应用程序上下文。 您可以通过调用 bean 来获取所有容器的集合。 然后,您可以迭代此集合,例如,停止或启动所有容器或调用方法 在 Registry 本身上,它将调用每个容器上的操作。getListenerContainers()RabbitListenerEndpointRegistryLifecyclespring-doc.cn

您还可以通过使用其 、 using — for 来获取对单个容器的引用 example,对于上述代码段创建的容器。idgetListenerContainer(String id)registry.getListenerContainer("multi")spring-doc.cn

从版本 1.5.2 开始,您可以使用 .idgetListenerContainerIds()spring-doc.cn

从版本 1.5 开始,您现在可以将 a 分配给终端节点上的容器。 这提供了一种机制来获取对容器子集的引用。 添加属性会导致 Bean 类型使用组名注册到上下文中。groupRabbitListenergroupCollection<MessageListenerContainer>spring-doc.cn

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

使用 Batching 进行@RabbitListener

当收到一批消息时,取消批处理通常由容器执行,并且一次使用一条消息调用侦听器。 从版本 2.2 开始,你可以将侦听器容器工厂和侦听器配置为在一次调用中接收整个批处理,只需设置工厂的属性,并将方法有效负载参数设为或:batchListenerListCollectionspring-doc.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) {
    ...
}

将属性设置为 true 会自动关闭工厂创建的容器中的 container 属性(除非是 - 见下文)。实际上,取消批处理从容器移动到侦听器适配器,并且适配器会创建传递给侦听器的列表。batchListenerdeBatchingEnabledconsumerBatchEnabledtruespring-doc.cn

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

同样从 2.2 版本开始。当一次接收一条批量消息时,最后一条消息包含一个设置为 的布尔标头。 可以通过将布尔值 last' 参数添加到您的侦听器方法来获取此标头。 标头是从 映射而来的。 此外, 还填充了每个消息片段中批处理的大小。true@Header(AmqpHeaders.LAST_IN_BATCH)MessageProperties.isLastInBatch()AmqpHeaders.BATCH_SIZEspring-doc.cn

此外,还向 . 如果为 true,容器将创建一批消息,最多 ;如果已过且没有新消息到达,则传送部分批处理。 如果收到生产者创建的批处理,则会对其进行 Debatch 并将其添加到使用者端批处理中;因此,实际传送的消息数可能会超过 ,这表示从代理接收的消息数。 当为 true 时,必须为 true;Container Factory 将强制执行此要求。consumerBatchEnabledSimpleMessageListenerContainerbatchSizereceiveTimeoutbatchSizedeBatchingEnabledconsumerBatchEnabledspring-doc.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@RabbitListenerspring-doc.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) {
    ...
}
  • 第一个是用接收到的 Raw 的、未转换的 S 来调用的。org.springframework.amqp.core.Messagespring-doc.cn

  • 第二个是使用 S 调用的,其中包含已转换的有效负载和映射的标头/属性。org.springframework.messaging.Message<?>spring-doc.cn

  • 第三个是使用转换后的有效负载调用的,无法访问标头/属性。spring-doc.cn

您还可以添加一个参数,通常在使用 ack 模式时使用。 这对于第三个示例不是很有用,因为您无权访问该属性。ChannelMANUALdelivery_tagspring-doc.cn

Spring Boot 为 和 提供了配置属性,但不为 . 从版本 3.0 开始,在容器工厂上设置为 也会设置为 。 当 is 时,侦听器必须是批处理侦听器。consumerBatchEnabledbatchSizebatchListenerconsumerBatchEnabledtruebatchListenertrueconsumerBatchEnabledtruespring-doc.cn

从版本 3.0 开始,侦听器方法可以使用 或 。Collection<?>List<?>spring-doc.cn

使用容器工厂

引入侦听器容器工厂是为了支持 并向 注册容器,如编程端点注册中所述。@RabbitListenerRabbitListenerEndpointRegistryspring-doc.cn

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

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

以下示例显示了如何使用 a 创建侦听器容器:SimpleRabbitListenerEndpointspring-doc.cn

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

以下示例演示如何在创建后添加侦听器:spring-doc.cn

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

在任何一种情况下,侦听器也可以是 ,因为它现在是 的子接口。ChannelAwareMessageListenerMessageListenerspring-doc.cn

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

以这种方式创建的容器是普通实例,不会在 .@BeanRabbitListenerEndpointRegistry
异步返回类型@RabbitListener

@RabbitListener(和 )方法可以使用异步返回类型 和 指定,从而异步发送回复。 不再受支持;它已被 Spring Framework 弃用。@RabbitHandlerCompletableFuture<?>Mono<?>ListenableFuture<?>spring-doc.cn

必须配置侦听器容器工厂,以便使用者线程不会确认消息;相反,异步完成将在异步操作完成时对消息进行 ack 或 nack 处理。 当异步结果完成但出现错误时,消息是否重新排队取决于引发的异常类型、容器配置和容器错误处理程序。 默认情况下,消息将重新排队,除非容器的属性设置为 (默认情况下)。 如果异步结果以 完成 ,则不会将消息重新排队。 如果容器的 property 是 ,则可以通过将 future 的 exception 设置为 a 来覆盖该 API,并且消息将重新排队。 如果侦听器方法中发生某些异常,阻止创建异步结果对象,则必须捕获该异常并返回适当的返回对象,该对象将导致消息被确认或重新排队。AcknowledgeMode.MANUALdefaultRequeueRejectedfalsetrueAmqpRejectAndDontRequeueExceptiondefaultRequeueRejectedfalseImmediateRequeueException

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

从版本 3.0.5 开始,可以使用 Kotlin 标记 (和 ) 方法,并且整个处理过程和回复生成(可选)都在相应的 Kotlin 协程上进行。 所有提到的规则仍然适用。 依赖项必须存在于 Classpath 中才能允许函数调用。@RabbitListener@RabbitHandlersuspendAcknowledgeMode.MANUALorg.jetbrains.kotlinx:kotlinx-coroutines-reactorsuspendspring-doc.cn

同样从版本 3.0.5 开始,如果在具有异步返回类型(包括 Kotlin 挂起函数)的侦听器上配置了 a,则会在失败后调用错误处理程序。 有关此错误处理程序及其用途的更多信息,请参阅处理异常RabbitListenerErrorHandlerspring-doc.cn

线程和异步使用者

异步使用者涉及许多不同的线程。spring-doc.cn

当 传递新消息时,来自 配置的线程用于调用 。 如果未配置,则使用 a。 如果您使用池化执行程序,则需要确保池大小足以处理配置的并发。 使用 ,直接在线程上调用 。 在这种情况下, the 用于监视使用者的任务。TaskExecutorSimpleMessageListenerContainerMessageListenerRabbitMQ ClientSimpleAsyncTaskExecutorDirectMessageListenerContainerMessageListenerRabbitMQ ClienttaskExecutorspring-doc.cn

使用默认 时,对于调用侦听器的线程,侦听器容器将在 . 这对于日志分析非常有用。 我们通常建议始终在 logging appender 配置中包含线程名称。 当 a 通过容器上的属性专门提供时,它将按原样使用,无需修改。 建议您使用类似的技术来命名由自定义 Bean 定义创建的线程,以帮助在日志消息中识别线程。SimpleAsyncTaskExecutorbeanNamethreadNamePrefixTaskExecutortaskExecutorTaskExecutor

在创建连接时将 configured 传递到 中,其线程用于将新消息投递到侦听器容器。 如果未配置,则 Client 端使用内部线程池执行程序,其池大小(在撰写本文时)每个连接为 。ExecutorCachingConnectionFactoryRabbitMQ ClientRuntime.getRuntime().availableProcessors() * 2spring-doc.cn

如果您有大量工厂或正在使用 ,您可能希望考虑使用具有足够线程的共享来满足您的工作负载。CacheMode.CONNECTIONThreadPoolTaskExecutorspring-doc.cn

使用 ,您需要确保连接工厂配置了任务执行程序,该执行程序具有足够的线程来支持使用该工厂的所有侦听器容器之间的所需并发性。 默认池大小(在撰写本文时)为 .DirectMessageListenerContainerRuntime.getRuntime().availableProcessors() * 2

它使用 a 为低级 I/O (套接字) 操作创建线程。 要修改此工厂,您需要配置底层 RabbitMQ ,如配置底层客户端连接工厂中所述。RabbitMQ clientThreadFactoryConnectionFactoryspring-doc.cn

选择容器

版本 2.0 引入了 (DMLC)。 以前,只有 (SMLC) 可用。 SMLC 为每个使用者使用内部队列和专用线程。 如果容器配置为侦听多个队列,则使用相同的使用者线程来处理所有队列。 并发性由 and 其他属性控制。 当消息从 RabbitMQ 客户端到达时,客户端线程会通过队列将它们传递给使用者线程。 这种架构是必需的,因为在早期版本的 RabbitMQ 客户端中,不可能进行多个并发交付。 较新版本的客户端具有修订后的线程模型,现在可以支持并发。 这允许引入 DMLC,现在直接在 RabbitMQ 客户端线程上调用侦听器。 因此,它的架构实际上比 SMLC “更简单”。 但是,这种方法存在一些限制,并且 SMLC 的某些功能在 DMLC 中不可用。 此外,并发性由 (和 Client 端库的线程池) 控制。 和 关联的属性不可用于此容器。DirectMessageListenerContainerSimpleMessageListenerContainerconcurrentConsumersconsumersPerQueueconcurrentConsumersspring-doc.cn

SMLC 提供以下功能,但 DMLC 不提供:spring-doc.cn

  • batchSize:使用 SMLC,您可以设置此项以控制事务中传送的消息数或减少确认数,但这可能会导致失败后重复传送的数量增加。 (DMLC 确实有 ,你可以用它来减少 acks,与 with 和 SMLC 相同,但它不能用于事务 — 每条消息都在单独的事务中传递和 ack )。messagesPerAckbatchSizespring-doc.cn

  • consumerBatchEnabled:支持在使用者中对离散消息进行批处理;有关更多信息,请参阅 Message Listener Container Configuration (消息侦听器容器配置)。spring-doc.cn

  • maxConcurrentConsumers和使用者扩展间隔或触发器 — DMLC 中没有自动扩展。 但是,它确实允许您以编程方式更改属性,并相应地调整使用者。consumersPerQueuespring-doc.cn

但是,与 SMLC 相比,DMLC 具有以下优势:spring-doc.cn

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

  • 避免了 RabbitMQ Client 线程和使用者线程之间的上下文切换。spring-doc.cn

  • 线程在使用者之间共享,而不是为 SMLC 中的每个使用者提供专用线程。 但是,请参阅线程和异步使用者中有关连接工厂配置的重要说明。spring-doc.cn

有关哪些配置属性适用于每个容器的信息,请参阅消息侦听器容器配置spring-doc.cn

检测空闲的异步使用者

虽然效率很高,但异步使用者的一个问题是检测它们何时空闲 — 用户可能希望采用 如果一段时间内没有消息到达,则执行一些操作。spring-doc.cn

从版本 1.6 开始,现在可以将侦听器容器配置为在一段时间后没有消息传递时发布 。 当容器处于空闲状态时,每毫秒发布一次事件。ListenerContainerIdleEventidleEventIntervalspring-doc.cn

要配置此功能,请在容器上设置。 以下示例显示了如何在 XML 和 Java 中执行此操作(对于 a 和 a ):idleEventIntervalSimpleMessageListenerContainerSimpleRabbitListenerContainerFactoryspring-doc.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.cn

事件消耗

您可以通过实现 — 通用侦听器或缩小到仅 接收此特定事件。 您还可以使用 Spring Framework 4.2 中引入的 。ApplicationListener@EventListenerspring-doc.cn

下面的示例将 和 合并到一个类中。 您需要了解应用程序侦听器获取所有容器的事件,因此您可能需要 如果要根据哪个容器处于空闲状态执行特定操作,请检查侦听器 ID。 您也可以将 用于此目的。@RabbitListener@EventListener@EventListenerconditionspring-doc.cn

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

以下示例演示如何使用 和 annotation 创建侦听器:@RabbitListener@EventListenerspring-doc.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 缩小接收的事件范围。
如果您希望使用 idle 事件来停止 lister 容器,则不应在调用侦听器的线程上调用。 这样做总是会导致延迟和不必要的日志消息。 相反,您应该将事件移交给其他线程,然后该线程可以停止容器。container.stop()
监视侦听器性能

从版本2.2开始,如果在类路径上检测到,并且应用程序上下文中存在单个(或者恰好有一个被注释,例如在使用 Spring Boot 时),侦听器容器将自动为侦听器创建和更新Micrometer。 可以通过将 container 属性设置为 来禁用计时器。TimerMicrometerMeterRegistry@PrimarymicrometerEnabledfalsespring-doc.cn

维护两个计时器 - 一个用于成功调用侦听器,另一个用于失败。 使用 simple ,每个配置的队列都有一对计时器。MessageListenerspring-doc.cn

计时器已命名并具有以下标签:spring.rabbitmq.listenerspring-doc.cn

  • listenerId:(侦听器 ID 或容器 Bean 名称)spring-doc.cn

  • queue:(简单侦听器的队列名称或配置的队列名称列表,因为一个批次可能包含来自多个队列的消息)consumerBatchEnabledtruespring-doc.cn

  • result:或successfailurespring-doc.cn

  • exception:或noneListenerExecutionFailedExceptionspring-doc.cn

您可以使用 container 属性添加其他标记。micrometerTagsspring-doc.cn

千分尺观察

从版本 3.0 开始,现在支持对 和 listener 容器使用 Micrometer 进行观察。RabbitTemplatespring-doc.cn

在每个组件上设置以启用观察;这将禁用 Micrometer Timers,因为计时器现在将随每个观测一起管理。 使用带注释的侦听器时,请在容器工厂上设置。observationEnabledobservationEnabledspring-doc.cn

有关更多信息,请参阅 Micrometer Tracingspring-doc.cn

要向计时器/跟踪添加标签,请分别配置自定义或模板或侦听器容器。RabbitTemplateObservationConventionRabbitListenerObservationConventionspring-doc.cn

默认实施会添加 template observations 的标签和 containers 的标签。namelistener.idspring-doc.cn

您可以子类化 or or 提供全新的实现。DefaultRabbitTemplateObservationConventionDefaultRabbitListenerObservationConventionspring-doc.cn

有关更多详细信息,请参阅千分尺观测文档spring-doc.cn

4.1.7. 容器和 Broker-Named 队列

虽然最好将实例用作自动删除队列,但从版本 2.1 开始,您可以将代理命名队列与侦听器容器一起使用。 以下示例显示了如何执行此操作:AnonymousQueuespring-doc.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;
}

请注意名称的空值。 当 declares queues 时,它会使用代理返回的名称更新属性。 您必须在配置容器时使用,以便容器可以在运行时访问声明的名称。 仅仅设置名称是不够的。StringRabbitAdminQueue.actualNamesetQueues()spring-doc.cn

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

4.1.8. 消息转换器

还定义了几种用于发送和接收委托给 . 为每个方向提供一种方法:一种用于转换为 a,另一种用于 . 请注意,在转换为 时,除了对象之外,还可以提供属性。 该参数通常对应于 Message body。 下面的清单显示了接口定义:AmqpTemplateMessageConverterMessageConverterMessageMessageMessageobjectMessageConverterspring-doc.cn

public interface MessageConverter {

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

    Object fromMessage(Message message) throws MessageConversionException;

}

上的相关 -sending 方法比我们之前讨论的方法更简单,因为它们不需要实例。 相反,它负责通过将提供的对象转换为正文的字节数组,然后添加任何提供的任何 . 下面的清单显示了各种方法的定义:MessageAmqpTemplateMessageMessageConverterMessageMessageMessagePropertiesspring-doc.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” 属性。 下面的清单显示了这两种方法的定义:spring-doc.cn

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;
异步 Consumer 中提到的也使用了 .MessageListenerAdapterMessageConverter
SimpleMessageConverter

该策略的默认实现称为 。 这是 的实例使用的转换器(如果您未显式配置替代项)。 它处理基于文本的内容、序列化的 Java 对象和字节数组。MessageConverterSimpleMessageConverterRabbitTemplatespring-doc.cn

Message

如果输入的内容类型以 “text” 开头(例如, “text/plain”),它还会检查 content-encoding 属性,以确定在将 body 字节数组转换为 Java 时要使用的字符集。 如果未在 input 上设置 content-encoding 属性,则默认情况下,它使用 UTF-8 字符集。 如果需要覆盖该默认设置,则可以配置 的实例 ,设置其属性,并将其注入到实例中。MessageMessageStringMessageSimpleMessageConverterdefaultCharsetRabbitTemplatespring-doc.cn

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

对于所有其他内容类型,the 将 body 内容直接作为字节数组返回。SimpleMessageConverterMessagespring-doc.cn

有关重要信息,请参阅 Java 反序列化spring-doc.cn

转换为Message

从任意 Java 对象转换为 a 时,同样会处理字节数组、字符串和可序列化实例。 它将每个 Cookie 转换为 bytes (在 byte 数组的情况下,没有要转换的内容),并相应地设置 content-type 属性。 如果要转换的 the 与这些类型之一不匹配,则 body 为 null。MessageSimpleMessageConverterObjectMessagespring-doc.cn

SerializerMessageConverter

这个转换器与 Obvious Similar to the 不同之处在于它可以配置其他 Spring Framework 和实现进行转换。SimpleMessageConverterSerializerDeserializerapplication/x-java-serialized-objectspring-doc.cn

有关重要信息,请参阅 Java 反序列化spring-doc.cn

Jackson2JsonMessage转换器

本节介绍如何使用 与 转换为 . 它包含以下部分:Jackson2JsonMessageConverterMessagespring-doc.cn

转换为Message

如上一节所述,通常不建议依赖 Java 序列化。 JSON 是一种相当常见的替代方案,它在不同语言和平台之间更加灵活和可移植 (JavaScript 对象表示法)。 可以在任何实例上配置转换器以覆盖其对默认值的使用。 使用 2.x 库。 以下示例配置了一个 :RabbitTemplateSimpleMessageConverterJackson2JsonMessageConvertercom.fasterxml.jacksonJackson2JsonMessageConverterspring-doc.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>

如上所示,默认情况下使用 a。 类型信息将添加到 (和检索) 。 如果入站消息在 中不包含类型信息,但您知道预期的类型,则 可以使用 property 配置 static 类型,如下例所示:Jackson2JsonMessageConverterDefaultClassMapperMessagePropertiesMessagePropertiesdefaultTypespring-doc.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>

此外,您还可以根据标头中的值提供自定义映射。 以下示例显示了如何执行此操作:TypeIdspring-doc.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;
}

现在,如果发送系统将标头设置为 ,则转换器将创建一个对象,依此类推。 有关从非 Spring 应用程序转换消息的完整讨论,请参阅从非 Spring 应用程序接收 JSON 示例应用程序。thing1Thing1spring-doc.cn

从版本 2.4.3 开始,如果 具有参数,则转换器将不会添加 message 属性;这也用于编码。 添加了一个新方法:contentEncodingsupportedMediaTypecharsetsetSupportedMediaTypespring-doc.cn

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

入站消息根据发送系统添加到 Headers 的类型信息转换为对象。spring-doc.cn

从版本 2.4.3 开始,如果没有 message 属性,转换器将尝试检测 message 属性中的参数并使用该参数。 如果两者都不存在,如果 具有参数,则它将用于解码,并最终回退到该属性。 添加了一个新方法:contentEncodingcharsetcontentTypesupportedMediaTypecharsetdefaultCharsetsetSupportedMediaTypespring-doc.cn

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

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

此外,从版本 1.6 开始,当您使用注释(在方法上)时,推断的类型信息将添加到 . 这允许转换器转换为目标方法的参数类型。 仅当存在一个没有注释的参数或单个参数带有注释时,这才适用。 在分析过程中,将忽略 type 参数。@RabbitListenerMessageProperties@PayloadMessagespring-doc.cn

默认情况下,推断的类型信息将覆盖创建的入站和相关标头 通过发送系统。 这允许接收系统自动转换为不同的域对象。 仅当参数类型是具体的(不是抽象的或接口的)或来自包时,这才适用。 在所有其他情况下,使用 和 related 标头。 在某些情况下,您可能希望覆盖默认行为并始终使用该信息。 例如,假设您有一个 a 接受一个参数,但消息包含一个 that 是 (which is concrete) 的子类。 推断的类型将不正确。 要处理这种情况,请将 上的 属性设置为 的默认值 . (该属性实际上位于 converter 的 上,但在 converter 上提供了一个 setter 为方便起见。 如果你注入了一个自定义类型 mapper,你应该在 mapper 上设置 property 。TypeIdjava.utilTypeIdTypeId@RabbitListenerThing1Thing2Thing1TypePrecedenceJackson2JsonMessageConverterTYPE_IDINFERREDDefaultJackson2JavaTypeMapper
从 转换时,传入的内容必须符合 JSON 标准( 用于检查)。 从版本 2.2 开始,如果没有 property 或它具有默认值,则假定为 。 要恢复到之前的行为(返回 unconverted),请将转换器的属性设置为 。 如果内容类型不受支持,则会发出一条日志消息 ,并按原样返回 — 作为 . 因此,为了满足使用者端的要求,生成者必须添加 message 属性 — 例如,as 或 or 通过使用 ,它会自动设置标头。 下面的清单显示了许多 converter 调用:MessageMessageProperties.getContentType()contentType.contains("json")application/jsoncontentTypeapplication/octet-streambyte[]assumeSupportedContentTypefalseWARNCould not convert incoming message with content-type […​]message.getBody()byte[]Jackson2JsonMessageConvertercontentTypeapplication/jsontext/x-jsonJackson2JsonMessageConverter
@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) {...}

在前面清单中的前四种情况下,转换器尝试转换为 type。 第五个示例无效,因为我们无法确定哪个参数应该接收消息有效负载。 在第六个示例中,由于泛型类型是 .Thing1WildcardTypespring-doc.cn

但是,您可以创建自定义转换器并使用 message 属性来决定要转换的类型 JSON 到targetMethodspring-doc.cn

只有在方法级别声明 Comments 时,才能实现这种类型推断。 使用 class-level ,转换后的类型用于选择要调用的方法。 因此,基础架构提供了 message 属性,您可以在自定义 converter 来确定类型。@RabbitListener@RabbitListener@RabbitHandlertargetObject
从版本 1.6.11 开始,因此 () 提供了克服序列化小工具漏洞的选项。 默认情况下,为了向后兼容,s 信任所有软件包 — 也就是说,它用于 option。Jackson2JsonMessageConverterDefaultJackson2JavaTypeMapperDefaultClassMappertrustedPackagesJackson2JsonMessageConverter*

从版本 2.4.7 开始,可以将转换器配置为在 Jackson 在反序列化消息正文后返回时返回。 这有助于 s 以两种方式接收 null 有效负载:Optional.empty()null@RabbitListenerspring-doc.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));
}

要启用此功能,请设置为 ;when (default) 时,转换器将回退到原始消息正文 ()。setNullAsOptionalEmptytruefalsebyte[]spring-doc.cn

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

在版本 2.2.8 之前,如果 a 的推断类型是抽象类(包括接口),则转换器将回退到在 Headers 中查找类型信息,如果存在,则使用该信息;如果不存在,它将尝试创建 Abstract 类。 当使用配置了自定义反序列化器来处理抽象类的自定义,但传入消息具有无效的类型标头时,这会导致问题。@RabbitListenerObjectMapperspring-doc.cn

从版本 2.2.8 开始,默认情况下保留之前的行为。如果您有此类自定义,并且希望忽略类型标头,并始终使用推断的类型进行转换,请将 设置为 。 这是向后兼容所必需的,并避免在转换失败时尝试转换的开销(使用 standard )。ObjectMapperalwaysConvertToInferredTypetrueObjectMapperspring-doc.cn

使用 Spring Data Projection 接口

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

interface SomeSample {

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

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

默认情况下,将使用访问器方法在接收到的 JSON 文档中查找属性名称作为字段。 该表达式允许自定义值查找,甚至定义多个 JSON 路径表达式,以便从多个位置查找值,直到表达式返回实际值。@JsonPathspring-doc.cn

要启用此功能,请在消息转换器上设置 to。 还必须将 and 添加到类路径中。useProjectionForInterfacestruespring-data:spring-data-commonscom.jayway.jsonpath:json-pathspring-doc.cn

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

从 a 转换 withMessageRabbitTemplate

如前所述,类型信息在消息标头中传达,以帮助转换器从消息转换。 这在大多数情况下效果很好。 但是,当使用泛型类型时,它只能转换简单对象和已知的 “容器” 对象(列表、数组和映射)。 从版本 2.0 开始, implements ,这允许它与采用参数的新方法一起使用。 这允许转换复杂的泛型类型,如以下示例所示:Jackson2JsonMessageConverterSmartMessageConverterRabbitTemplateParameterizedTypeReferencespring-doc.cn

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

另一个选项是 . 它委托给 Spring OXM 库的 和 strategy 接口的实现。 您可以在此处阅读有关该库的更多信息。 在配置方面,最常见的是只提供 constructor 参数,因为大多数 的实现也实现 。 以下示例显示如何配置 :MarshallingMessageConverterMarshallerUnmarshallerMarshallerUnmarshallerMarshallingMessageConverterspring-doc.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.cn

两者具有相同的基类:.Jackson2XmlMessageConverterJackson2JsonMessageConverterAbstractJackson2MessageConverterspring-doc.cn

引入该类是为了替换已删除的类 .AbstractJackson2MessageConverterAbstractJsonMessageConverter

使用 2.x 库。Jackson2XmlMessageConvertercom.fasterxml.jacksonspring-doc.cn

您可以像 一样使用它,只是它支持 XML 而不是 JSON。 以下示例配置了一个 :Jackson2JsonMessageConverterJackson2JsonMessageConverterspring-doc.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>

有关更多信息,请参见Jackson2JsonMessageConverterspring-doc.cn

从版本 2.2 开始,如果没有 property 或它具有默认值,则假定为 。 要恢复到之前的行为(返回 unconverted),请将转换器的属性设置为 。application/xmlcontentTypeapplication/octet-streambyte[]assumeSupportedContentTypefalse
ContentTypeDelegatingMessageConverter

此类是在版本 1.4.2 中引入的,它允许根据 . 默认情况下,如果没有属性或存在与任何已配置的转换器都不匹配的值,则它会委托给 a。 以下示例配置了一个 :MessageConverterMessagePropertiesSimpleMessageConvertercontentTypeContentTypeDelegatingMessageConverterspring-doc.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.cn

从不受信任的来源反序列化 java 对象时可能存在漏洞。spring-doc.cn

如果您接受来自不受信任的来源的邮件,并且 为 ,则应 请考虑配置允许反序列化的包和类。 这适用于 和 当它配置为隐式或通过配置使用 a 时。content-typeapplication/x-java-serialized-objectSimpleMessageConverterSerializerMessageConverterDefaultDeserializerspring-doc.cn

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

您可以设置模式列表,例如 thing1.thing2.Cat.thing1..MySafeClassspring-doc.cn

按顺序检查模式,直到找到匹配项。 如果没有匹配项,则抛出 a。SecurityExceptionspring-doc.cn

您可以使用这些转换器上的属性设置模式。 或者,如果您信任所有消息发起方,则可以将环境变量或 system 属性设置为 。allowedListPatternsSPRING_AMQP_DESERIALIZATION_TRUST_ALLspring.amqp.deserialization.trust.alltruespring-doc.cn

消息属性转换器

策略接口用于在 Rabbit Client 和 Spring AMQP 之间进行转换。 默认实现 () 通常足以满足大多数用途,但如果需要,您可以实现自己的实现。 默认 properties 转换器在大小不大于 bytes 时将 type 元素转换为实例。 较大的实例不会被转换(请参阅下一段)。 可以使用 constructor 参数覆盖此限制。MessagePropertiesConverterBasicPropertiesMessagePropertiesDefaultMessagePropertiesConverterBasicPropertiesLongStringString1024LongStringspring-doc.cn

从版本 1.6 开始,长于长字符串限制(默认值:1024)的标头现在默认由 . 您可以通过 、 或 方法访问内容。LongStringDefaultMessagePropertiesConvertergetBytes[]toString()getStream()spring-doc.cn

以前,将此类标头“转换”为 a(实际上它只是引用了实例的 )。 在输出时,此标头未被转换(除了转换为 String — 例如,通过调用流)。DefaultMessagePropertiesConverterDataInputStreamLongStringDataInputStreamjava.io.DataInputStream@1d057a39toString()spring-doc.cn

大型传入标头现在也可以在输出时正确 “转换” (默认情况下)。LongStringspring-doc.cn

提供了一个新的构造函数,允许您将转换器配置为像以前一样工作。 下面的清单显示了该方法的 Javadoc 注释和声明:spring-doc.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 开始,一个名为 的新属性已添加到 . 以前,当 RabbitMQ 客户端使用和 from 进行转换时,会执行不必要的转换,因为 是 ,但使用 . (最终,RabbitMQ 客户端使用 UTF-8 将 to bytes 转换为 bytes 以放入协议消息中)。correlationIdStringMessagePropertiesBasicPropertiesbyte[] <→ StringMessageProperties.correlationIdbyte[]BasicPropertiesStringStringspring-doc.cn

为了提供最大的向后兼容性,已将名为 的新属性添加到 . 这需要 enum 参数。 默认情况下,它设置为 ,这将复制以前的行为。correlationIdPolicyDefaultMessagePropertiesConverterDefaultMessagePropertiesConverter.CorrelationIdPolicyBYTESspring-doc.cn

对于入站消息:spring-doc.cn

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

此外,从版本 1.6 开始,入站属性不再映射到 。 而是映射到它。 此外,入站属性不再映射到 。 而是映射到它。 这些更改是为了避免在将同一对象用于出站消息时意外传播这些属性。deliveryModeMessageProperties.deliveryModeMessageProperties.receivedDeliveryModeuserIdMessageProperties.userIdMessageProperties.receivedUserIdMessagePropertiesspring-doc.cn

从版本 2.2 开始,将转换任何值为 using 而不是 ;这避免了 Consumer 应用程序必须从表示中解析类名。 对于滚动升级,您可能需要更改使用者以理解这两种格式,直到所有创建器都升级为止。DefaultMessagePropertiesConverterClass<?>getName()toString()toString()spring-doc.cn

4.1.9. 修改消息 - 压缩等

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

消息转换器中可以看出,操作中就有这样一个扩展点,您可以在其中提供 . 例如,转换 POJO 后,您可以在 .AmqpTemplateconvertAndReceiveMessagePostProcessorMessagePostProcessorMessagespring-doc.cn

从版本 1.4.2 开始,已向 - 和 . 第一个选项使后处理器能够在发送到 RabbitMQ 之前立即运行。 使用批处理时(请参阅 批处理),在组装批处理之后和发送批处理之前调用此函数。 第二个请求在收到消息后立即调用。RabbitTemplatesetBeforePublishPostProcessors()setAfterReceivePostProcessors()spring-doc.cn

这些扩展点用于压缩等功能,为此,提供了多种实现。,并在发送前压缩消息,以及 和 解压缩收到的消息。MessagePostProcessorGZipPostProcessorZipPostProcessorDeflaterPostProcessorGUnzipPostProcessorUnzipPostProcessorInflaterPostProcessorspring-doc.cn

从版本 2.1.5 开始,可以配置用于复制原始消息属性的选项。 默认情况下,出于性能原因,这些属性会重复使用,并使用压缩内容编码和可选标头进行修改。 如果保留对原始出站消息的引用,则其属性也将更改。 因此,如果您的应用程序保留了带有这些消息后处理器的出站消息的副本,请考虑打开该选项。GZipPostProcessorcopyProperties = trueMessageProperties.SPRING_AUTO_DECOMPRESScopyProperties
从版本 2.2.12 开始,您可以配置压缩后处理器在内容编码元素之间使用的分隔符。 在版本 2.2.11 及更早版本中,这被硬编码为 ,现在它被设置为压缩器上的属性。 当您的使用者升级到 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:

同样,它也有一个方法,允许在容器收到消息后执行解压缩。SimpleMessageListenerContainersetAfterReceivePostProcessors()spring-doc.cn

从版本 2.1.4 开始,已添加到 中,以允许将新的后处理器分别附加到 before publish 和 after receive 后处理器列表中。 此外,还提供了一些方法来删除后处理器。 同样,还添加了 和 methods。 有关更多详细信息,请参阅 和 的 Javadoc。addBeforePublishPostProcessors()addAfterReceivePostProcessors()RabbitTemplateAbstractMessageListenerContaineraddAfterReceivePostProcessors()removeAfterReceivePostProcessor()RabbitTemplateAbstractMessageListenerContainerspring-doc.cn

4.1.10. 请求/回复消息

还提供了多种方法,这些方法接受前面描述的单向发送操作(、 和 )的相同参数选项。 这些方法对于请求-答复方案非常有用,因为它们在发送之前处理必要属性的配置,并且可以在内部为此目的创建的独占队列上侦听答复消息。AmqpTemplatesendAndReceiveexchangeroutingKeyMessagereply-tospring-doc.cn

类似的请求-回复方法也可用于同时应用于请求和回复的情况。 这些方法被命名为 . 有关更多详细信息,请参见 AmqpTemplate 的 JavadocMessageConverterconvertSendAndReceivespring-doc.cn

从版本 1.5.0 开始,每个方法变体都有一个重载版本,该版本采用 . 与正确配置的连接工厂一起,这允许接收操作发送端的发布者确认。 有关更多信息,请参见Correlated Publisher Confirms and ReturnsRabbitOperations的 JavadocsendAndReceiveCorrelationDataspring-doc.cn

从版本 2.0 开始,这些方法 () 有一些变体,它们需要一个额外的参数来转换复杂的返回类型。 模板必须配置有 . 有关更多信息,请参阅使用 RabbitTemplate消息转换convertSendAndReceiveAsTypeParameterizedTypeReferenceSmartMessageConverterspring-doc.cn

从版本 2.1 开始,您可以配置 with 选项来控制回复使用者的标志。 这是默认的。RabbitTemplatenoLocalReplyConsumernoLocalfalsespring-doc.cn

回复超时

默认情况下,发送和接收方法会在 5 秒后超时并返回 null。 您可以通过设置该属性来修改此行为。 从版本 1.5 开始,如果将属性设置为(或将特定消息的计算结果设置为),则如果无法将消息传送到队列,则会引发 an。 此异常具有 、 、 和 属性,以及用于发送的 和。replyTimeoutmandatorytruemandatory-expressiontrueAmqpMessageReturnedExceptionreturnedMessagereplyCodereplyTextexchangeroutingKeyspring-doc.cn

此功能使用发布者返回。 您可以通过在 上设置为 来启用它(请参阅 Publisher Confirms and Returns)。 此外,您不得在 .publisherReturnstrueCachingConnectionFactoryReturnCallbackRabbitTemplate

从版本 2.1.2 开始,添加了一个方法,让 subclasses 被通知超时,以便它们可以清理任何保留的状态。replyTimedOutspring-doc.cn

从版本 2.0.11 和 2.1.3 开始,当您使用 default 时,可以通过设置模板的属性来添加错误处理程序。 对于任何失败的投放,如延迟回复和在没有关联标头的情况下收到的消息,都会调用此错误处理程序。 传入的异常是 ,它具有属性。DirectReplyToMessageListenerContainerreplyErrorHandlerListenerExecutionFailedExceptionfailedMessagespring-doc.cn

RabbitMQ 直接回复
从版本 3.4.0 开始,RabbitMQ 服务器支持直接回复。 这消除了固定回复队列的主要原因(以避免需要为每个请求创建临时队列)。 从 Spring AMQP 版本 1.4.1 开始,默认情况下使用直接回复(如果服务器支持)而不是创建临时回复队列。 当 no 未提供(或设置为名称 )时,会自动检测是否支持直接回复,并使用它或回退到使用临时回复队列。 使用直接回复时,a 不是必需的,也不应配置。replyQueueamq.rabbitmq.reply-toRabbitTemplatereply-listener

命名队列(而不是 )仍支持回复侦听器,允许控制回复并发等。amq.rabbitmq.reply-tospring-doc.cn

从版本 1.6 开始,如果您希望为每个 reply 中,将属性设置为 。 如果将 .useTemporaryReplyQueuestruereplyAddressspring-doc.cn

您可以通过 subclassing 和 override 来更改指示是否使用 direct reply-to 的标准,以检查不同的标准。 该方法仅在发送第一个请求时调用一次。RabbitTemplateuseDirectReplyTo()spring-doc.cn

在 2.0 版本之前,为每个请求创建一个新的使用者,并在收到回复(或超时)时取消该使用者。 现在,模板改用 a,让使用者被重用。 该模板仍然负责关联回复,因此不存在延迟回复发送给其他发件人的危险。 如果要恢复到以前的行为,请将 ( when using XML configuration) 属性设置为 false。RabbitTemplateDirectReplyToMessageListenerContaineruseDirectReplyToContainerdirect-reply-to-containerspring-doc.cn

没有这样的选项。 当使用 direct reply-to 时,它总是使用 a for replies 。AsyncRabbitTemplateDirectReplyToContainerspring-doc.cn

从版本 2.3.7 开始,模板具有新属性 。 如果为 ,则服务器不必将相关 ID 从请求消息标头复制到回复消息。 相反,用于发送请求的通道用于将回复与请求相关联。useChannelForCorrelationtruespring-doc.cn

Message Correlation With A Reply Queue

使用固定回复队列(而不是 )时,必须提供关联数据,以便将回复与请求相关联。 请参阅 RabbitMQ 远程过程调用 (RPC)。 默认情况下,standard 属性用于保存关联数据。 但是,如果你希望使用自定义属性来保存相关数据,则可以在<rabbit-template/>上设置该属性。 显式设置 attribute 与省略 attribute 相同。 客户端和服务器必须对关联数据使用相同的标头。amq.rabbitmq.reply-tocorrelationIdcorrelation-keycorrelationIdspring-doc.cn

Spring AMQP 版本 1.1 使用了一个名为此数据的自定义属性。 如果您希望在当前版本中恢复此行为(可能是为了保持与使用 1.1 的其他应用程序的兼容性),则必须将该属性设置为 .spring_reply_correlationspring_reply_correlation

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

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

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

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

下面的示例定义了一个带有连接工厂的 rabbit 模板:spring-doc.cn

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

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

在版本 1.5.0 之前,该属性不可用。 回复始终使用默认 exchange 和 name 作为路由键进行路由。 这仍然是默认值,但您现在可以指定 new 属性。 该 可以包含具有格式的地址,并且回复将路由到指定的交换,并路由到与路由密钥绑定的队列。 的 优先于 。 当 only 正在使用时,必须将其配置为单独的组件。 and(或 上的属性)必须在逻辑上引用同一队列。reply-addressreply-queuereply-addressreply-address<exchange>/<routingKey>reply-addressreply-queuereply-address<reply-listener><listener-container>reply-addressreply-queuequeues<listener-container>

通过此配置,a 用于接收回复,其中 . 使用 namespace 元素定义模板时,如前面的示例所示,解析器将模板中的容器和连线定义为侦听器。SimpleListenerContainerRabbitTemplateMessageListener<rabbit:template/>spring-doc.cn

当模板不使用固定的(或使用直接回复 — 请参阅 RabbitMQ 直接回复)时,不需要侦听器容器。 Direct 是使用 RabbitMQ 3.4.0 或更高版本时的首选机制。replyQueuereply-to

如果您将 YOUR 定义为 OR 使用类将其定义为 OR 当您以编程方式创建模板时,您需要自己定义和连接回复侦听器容器。 如果您未能执行此操作,则模板永远不会收到回复,最终会超时并返回 null 作为对方法调用的回复。RabbitTemplate<bean/>@Configuration@BeansendAndReceivespring-doc.cn

从版本 1.5 开始,检测是否已 配置为 A 以接收回复。 否则,尝试使用回复地址发送和接收消息 fail 替换为 an (因为从未收到回复)。RabbitTemplateMessageListenerIllegalStateExceptionspring-doc.cn

此外,如果使用 simple (队列名称),则回复侦听器容器会验证它是否正在侦听 添加到具有相同名称的队列中。 如果回复地址是交换和路由密钥,并且写入了调试日志消息,则无法执行此检查。replyAddressspring-doc.cn

自己连接回复侦听器和模板时,请务必确保模板和容器的(或)属性引用同一队列。 该模板将回复地址插入到出站消息属性中。replyAddressqueuesqueueNamesreplyTo

下面的清单显示了如何手动连接 bean 的示例:spring-doc.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");
    }

此测试用例显示了一个具有固定回复队列的 wired 的完整示例,以及处理请求并返回回复的 “remote” 侦听器容器。RabbitTemplatespring-doc.cn

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

在版本 1.3.6 之前,仅记录超时消息的延迟回复。 现在,如果收到延迟的回复,则会被拒绝(模板会引发 )。 如果回复队列配置为将被拒绝的消息发送到死信交换,则可以检索回复以供以后分析。 为此,请将队列绑定到配置的死信交换,其路由键等于回复队列的名称。AmqpRejectAndDontRequeueExceptionspring-doc.cn

有关配置死信的更多信息,请参阅 RabbitMQ 死信文档。 您还可以查看测试用例作为示例。FixedReplyQueueDeadLetterTestsspring-doc.cn

异步 Rabbit 模板

版本 1.6 引入了 . 这与 AmqpTemplate 上的方法类似(和)方法。 但是,它们不是阻塞,而是返回一个 .AsyncRabbitTemplatesendAndReceiveconvertSendAndReceiveCompletableFuturespring-doc.cn

这些方法返回一个 . 这些方法返回一个 .sendAndReceiveRabbitMessageFutureconvertSendAndReceiveRabbitConverterFuturespring-doc.cn

您可以稍后通过调用 future 来同步检索结果,也可以注册一个与结果异步调用的回调。 下面的清单显示了这两种方法:get()spring-doc.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
        }
    });

    ...

}

如果设置了该消息,并且无法传递消息,则 future 会引发一个原因为 ,该消息封装了返回的消息和有关返回的信息。mandatoryExecutionExceptionAmqpMessageReturnedExceptionspring-doc.cn

如果设置,则 future 具有一个名为 的属性,该属性本身是 a,表示发布成功。 如果确认 future 为 ,则具有一个名为 的进一步属性,其中包含失败的原因(如果可用)。enableConfirmsconfirmCompletableFuture<Boolean>truefalseRabbitFuturenackCausespring-doc.cn

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

您可以在模板上设置该属性以超时回复(默认为 - 30 秒)。 如果发生超时,则 future 将以 .receiveTimeout30000AmqpReplyTimeoutExceptionspring-doc.cn

该模板实现 . 在有待处理回复时停止模板会导致待处理实例被取消。SmartLifecycleFuturespring-doc.cn

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

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

public AsyncRabbitTemplate(RabbitTemplate template)

请参阅 RabbitMQ Direct reply-to 以将直接回复与同步 一起使用。RabbitTemplatespring-doc.cn

版本 2.0 引入了这些方法 () 的变体,这些方法采用额外的参数来转换复杂的返回类型。 您必须使用 . 有关更多信息,请参阅使用 RabbitTemplate消息转换convertSendAndReceiveAsTypeParameterizedTypeReferenceRabbitTemplateSmartMessageConverterspring-doc.cn

从版本 3.0 开始,这些方法现在返回 s 而不是 s。AsyncRabbitTemplateCompletableFutureListenableFuture
使用 AMQP 的 Spring 远程处理

Spring 远程处理不再受支持,因为该功能已从 Spring Framework 中删除。spring-doc.cn

使用 (client side) 和 代替。sendAndReceiveRabbitTemplate@RabbitListenerspring-doc.cn

4.1.11. 配置 Broker

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

该接口基于使用 Spring AMQP 域抽象,如下面的清单所示:AmqpAdminspring-doc.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);

}

该方法返回有关队列的一些有限信息(消息计数和使用者计数)。 返回的属性的键可用作 (、 和 ) 中的常量。 RabbitMQ REST API 在对象中提供了更多信息。getQueueProperties()RabbitTemplateQUEUE_NAMEQUEUE_MESSAGE_COUNTQUEUE_CONSUMER_COUNTQueueInfospring-doc.cn

no-arg 方法使用自动生成的名称定义代理上的队列。 此自动生成的队列的其他属性包括 、 和 。declareQueue()exclusive=trueautoDelete=truedurable=falsespring-doc.cn

该方法采用一个对象并返回已声明队列的名称。 如果 provided 的属性为空,则代理使用生成的名称声明队列。 该名称将返回给调用方。 该名称也会添加到 . 您只能通过直接调用 在应用程序上下文中以声明方式定义队列时,如果管理员使用 auto-declaration 时,您可以将 name 属性设置为(空字符串)。 然后,代理创建名称。 从版本 2.1 开始,侦听器容器可以使用这种类型的队列。 有关更多信息,请参阅容器和以 Broker-Named 命名的队列。declareQueue(Queue queue)QueuenameQueueStringactualNameQueueRabbitAdmin""spring-doc.cn

这与框架生成唯一的 () 名称并将 和 、 设置为 形成鲜明对比。 具有空(或缺失)属性的 A 始终会创建一个 .AnonymousQueueUUIDdurablefalseexclusiveautoDeletetrue<rabbit:queue/>nameAnonymousQueuespring-doc.cn

请参阅 AnonymousQueue 以了解为什么 优先于代理生成的队列名称以及 如何控制名称的格式。 从版本 2.1 开始,匿名队列的声明参数默认设置为 。 这可确保在应用程序连接到的节点上声明队列。 声明式队列必须具有固定的名称,因为它们可能会在上下文中的其他位置引用,例如在 listener 中所示的 Listener 示例:AnonymousQueueQueue.X_QUEUE_LEADER_LOCATORclient-localspring-doc.cn

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

此接口的 RabbitMQ 实现是,当使用 Spring XML 进行配置时,它类似于以下示例:RabbitAdminspring-doc.cn

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

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

当缓存模式为(默认)时,实现会自动延迟声明在同一 . 这些组件在向 broker 打开 后立即声明。 有一些命名空间功能使这非常方便 — 例如, 在 Stocks 示例应用程序中,我们有以下内容:CachingConnectionFactoryCHANNELRabbitAdminApplicationContextConnectionspring-doc.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.cn

<rabbit:queue name="stocks.trade.queue"/>
您可以同时提供 and 属性。 这允许您使用独立于队列名称的 ID 来引用队列(例如,在绑定中)。 它还允许标准的 Spring 功能(例如队列名称的属性占位符和 SPEL 表达式)。 当您使用名称作为 Bean 标识符时,这些功能不可用。idname

可以使用其他参数配置队列 — 例如 . 当您使用命名空间支持时,它们以 argument-name/argument-value 对的形式提供,这些对使用 element 定义。 以下示例显示了如何执行此操作:x-message-ttlMap<rabbit:queue-arguments>spring-doc.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.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>

在提供混合类型的参数时,必须为每个 entry 元素提供类型。 以下示例显示了如何执行此操作:spring-doc.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.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 配置时,支持通过类上的方法将参数作为 first class 属性。 从版本 2.1 开始,匿名队列的声明将此属性设置为 by default。 这可确保在应用程序连接到的节点上声明队列。Queue.X_QUEUE_LEADER_LOCATORsetLeaderLocator()Queueclient-localspring-doc.cn

RabbitMQ 代理不允许声明具有不匹配参数的队列。 例如,如果 a 已经存在且没有参数,并且您尝试使用 (例如) 声明它,则会引发异常。queuetime to livekey="x-message-ttl" value="100"

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

可以通过将 instance 属性设置为来修改此行为。 此选项指示 记录异常并继续声明其他元素。 配置 using Java 时,此属性称为 。 这是适用于所有元素的全局设置。 Queues、exchanges 和 bindings 具有类似的属性,该属性仅适用于这些元素。ignore-declaration-exceptionstrueRabbitAdminRabbitAdminRabbitAdminignoreDeclarationExceptionsspring-doc.cn

在 1.6 版本之前,此属性仅在通道上发生时生效,例如当前属性和所需属性不匹配时。 现在,此属性对任何异常(包括 and others)生效。IOExceptionTimeoutExceptionspring-doc.cn

此外,任何声明异常都会导致发布 ,该 是上下文中的 any 都可以使用的 。 该事件包含对 admin、正在声明的元素以及 .DeclarationExceptionEventApplicationEventApplicationListenerThrowablespring-doc.cn

标头交换

从版本 1.3 开始,您可以将 配置为在多个 Headers 上匹配。 您还可以指定是否必须匹配任何或所有标头。 以下示例显示了如何执行此操作:HeadersExchangespring-doc.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 开始,您可以使用标志(默认为 )进行配置,并且可以通过 (如果应用程序上下文中存在标志)在 Broker 上正确配置。 如果该标志用于交换,则 RabbitMQ 不允许客户端使用该交换。 这对于死信交换或 exchange-to-exchange 绑定非常有用,在这种情况下,您不希望使用 exchange 直接由出版商提供。ExchangesinternalfalseExchangeRabbitAdmininternaltruespring-doc.cn

要了解如何使用 Java 配置 AMQP 基础结构,请查看 Stock 示例应用程序 其中有类 ,它反过来又有 和 子类。 下面的清单显示了 的代码:@ConfigurationAbstractStockRabbitConfigurationRabbitClientConfigurationRabbitServerConfigurationAbstractStockRabbitConfigurationspring-doc.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

}

在 Stock 应用程序中,使用以下类配置服务器:@Configurationspring-doc.cn

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

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

这是整个类继承链的结束。 最终结果是 和 在应用程序启动时向代理声明。 服务器配置中没有绑定到队列的绑定,因为这是在 Client 端应用程序中完成的。 但是,股票请求队列会自动绑定到 AMQP 默认交易所。 此行为由规范定义。@ConfigurationTopicExchangeQueueTopicExchangespring-doc.cn

client 类更有趣一些。 其声明如下:@Configurationspring-doc.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()AmqpAdminspring-doc.cn

用于队列和交换的 Builder API

版本 1.6 引入了一个方便的 Fluent API,用于在使用 Java 配置时进行配置和对象。 以下示例演示如何使用它:QueueExchangespring-doc.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 开始,现在默认创建持久交换,以便与各个类上的简单构造函数保持一致。 要与构建器进行非持久交换,请使用 before ininvoke . 不再提供不带参数的方法。ExchangeBuilderAbstractExchange.durable(false).build()durable()spring-doc.cn

2.2 版本引入了 Fluent API 来添加“众所周知的”exchange 和 queue 参数......spring-doc.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();
}
声明 Exchanges、Queues 和 Bindings 的集合

您可以将对象 (、 和 ) 的集合包装在对象中。 它会在应用程序上下文中检测此类 bean(以及离散 bean),并在建立连接时(最初和连接失败后)在代理上声明包含的对象。 以下示例显示了如何执行此操作:DeclarableQueueExchangeBindingDeclarablesRabbitAdminDeclarablespring-doc.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 之前的版本中,您可以通过定义 bean 类型的 bean 来声明多个实例。 在某些情况下,这可能会导致不良的副作用,因为管理员必须迭代所有 bean。DeclarableCollection<Declarable>Collection<?>

版本 2.2 向 ;这可以用作一种方便,例如,在声明侦听器容器 bean 时。getDeclarablesByTypeDeclarablesspring-doc.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;
}
条件声明

默认情况下,所有 queues、exchanges 和 bindings 都由应用程序上下文中的所有实例(假设它们有)声明。RabbitAdminauto-startup="true"spring-doc.cn

从版本 2.1.9 开始,它有一个新属性(默认情况下);当设置为 时,管理员将仅声明显式配置为由该管理员声明的 bean。RabbitAdminexplicitDeclarationsOnlyfalsetruespring-doc.cn

从 1.2 版本开始,您可以有条件地声明这些元素。 当应用程序连接到多个代理并需要指定应使用哪个代理声明特定元素时,这特别有用。

表示这些元素的类 implement ,它有两个方法: 和 。 使用这些方法来确定特定实例是否实际应处理其 .DeclarableshouldDeclare()getDeclaringAdmins()RabbitAdminConnectionspring-doc.cn

这些属性可用作命名空间中的属性,如以下示例所示:spring-doc.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>
默认情况下,属性为 and,如果未提供(或为空),则所有实例都声明该对象(只要 admin 的 attribute 是 ,默认值,并且 admin 的 attribute 是 false)。auto-declaretruedeclared-byRabbitAdminauto-startuptrueexplicit-declarations-only

同样,您也可以使用 基于 Java 来达到相同的效果。 在以下示例中,组件由 声明,而不是 由 :@Configurationadmin1admin2spring-doc.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;
}
关于 and 属性的说明idname

属性 on 和 elements 反映 broker 中实体的名称。 对于队列,如果省略 the,则创建一个匿名队列(请参阅 AnonymousQueue)。name<rabbit:queue/><rabbit:exchange/>namespring-doc.cn

在 2.0 之前的版本中,它也被注册为 bean 名称别名(类似于 on elements)。namename<bean/>spring-doc.cn

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

  • 它阻止了具有相同名称的 queue 和 exchange 的声明。spring-doc.cn

  • 如果别名包含 SPEL 表达式 (),则不会解析别名。#{…​}spring-doc.cn

从版本 2.0 开始,如果同时使用 an a 属性声明其中一个元素,则该名称不再声明为 Bean 名称别名。 如果您希望声明一个队列并与相同的 交换,则必须提供一个 .idnamenameidspring-doc.cn

如果元素只有一个属性,则不会发生更改。 bean 仍然可以被 引用 — 例如,在绑定声明中。 但是,如果名称包含 SPEL,则仍然无法引用它 - 您必须提供 a 以供参考。namenameidspring-doc.cn

AnonymousQueue

通常,当您需要名称唯一、独占的自动删除队列时,我们建议您使用 而不是代理定义的队列名称(使用作为名称会导致代理生成队列 name) 的 S SAnonymousQueue""Queuespring-doc.cn

这是因为:spring-doc.cn

  1. 队列实际上是在建立与代理的连接时声明的。 这是在 bean 创建并连接在一起很久之后。 使用队列的 bean 需要知道它的名称。 事实上,在应用程序启动时,代理甚至可能没有运行。spring-doc.cn

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

您可以控制实例使用的队列名称的格式。AnonymousQueuespring-doc.cn

默认情况下,队列名称的前缀为 ,后跟 base64 表示形式 — 例如:。spring.gen-UUIDspring.gen-MRBv9sqISkuCiPfOYfpo4gspring-doc.cn

您可以在 constructor 参数中提供实现。 以下示例显示了如何执行此操作:AnonymousQueue.NamingStrategyspring-doc.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 生成一个队列名称,前缀为 — for 例:。 第二个 Bean 生成一个队列名称,前缀为 ,后跟 的 base64 表示形式。 第三个 bean 仅使用 UUID(无 base64 转换)生成名称 — 例如,.spring.gen-UUIDspring.gen-MRBv9sqISkuCiPfOYfpo4gsomething-UUIDf20c818a-006b-4416-bf91-643590fedb0espring-doc.cn

base64 编码使用 RFC 4648 中的“URL 和文件名安全字母表”。 尾随填充字符 () 将被删除。=spring-doc.cn

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

您可以在使用 XML 配置时指定命名策略。 该属性存在于元素上 对于实现 . 以下示例说明如何以各种方式指定命名策略:naming-strategy<rabbit:queue>AnonymousQueue.NamingStrategyspring-doc.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>

第一个示例创建名称,例如 . 第二个示例使用 UUID 的 String 表示形式创建名称。 第三个示例创建名称,例如 .spring.gen-MRBv9sqISkuCiPfOYfpo4gcustom.gen-MRBv9sqISkuCiPfOYfpo4gspring-doc.cn

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

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

恢复自动删除声明

通常,这些 (s) 仅恢复在应用程序上下文中声明为 bean 的队列/交换/绑定;如果任何此类声明是自动删除的,则当连接丢失时,代理将删除这些声明。 重新建立连接后,管理员将重新声明实体。 通常,通过调用 创建的实体 不会被恢复。RabbitAdminadmin.declareQueue(…​)admin.declareExchange(…​)admin.declareBinding(…​)spring-doc.cn

从版本 2.4 开始,admin 有一个新属性;when 时,管理员将恢复这些实体以及应用程序上下文中的 bean。redeclareManualDeclarationstruespring-doc.cn

如果调用 , 或 ,则不会执行单个声明的恢复。 删除队列和交换时,将从可恢复实体中删除关联的绑定。deleteQueue(…​)deleteExchange(…​)removeBinding(…​)spring-doc.cn

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

4.1.12. 代理事件监听器

启用 Event Exchange Plugin 后,如果将 类型的 bean 添加到应用程序上下文中,它会将选定的代理事件发布为实例,这些实例可以与普通的 Spring 或方法一起使用。 事件由代理发布到主题交换,每种事件类型具有不同的路由键。 侦听器使用事件键,这些键用于将 绑定到 交换,以便侦听器仅接收选定的事件。 由于它是主题交换,因此可以使用通配符(以及显式请求特定事件),如下例所示:BrokerEventListenerBrokerEventApplicationListener@EventListeneramq.rabbitmq.eventAnonymousQueuespring-doc.cn

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

通过使用常规的 Spring 技术,可以进一步缩小单个事件侦听器中接收的事件的范围,如下例所示:spring-doc.cn

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

4.1.13. 延迟消息交换

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

该插件目前被标记为实验性,但已经可用一年多了(在撰写本文时)。 如果对插件的更改是必要的,我们计划尽快添加对此类更改的支持。 出于这个原因,Spring AMQP 中的这种支持也应该被认为是实验性的。 此功能已使用 RabbitMQ 3.6.0 和插件版本 0.0.1 进行了测试。

要使用 a 将交换声明为 delayed,可以将 exchange bean 上的属性设置为。 它使用 exchange 类型(、 和 等)来设置参数和 声明类型为 的 Exchange。RabbitAdmindelayedtrueRabbitAdminDirectFanoutx-delayed-typex-delayed-messagespring-doc.cn

使用 XML 配置 exchange bean 时,该属性 (default: ) 也可用。 以下示例演示如何使用它:delayedfalsespring-doc.cn

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

要发送延迟消息,您可以通过 设置标头,如下例所示:x-delayMessagePropertiesspring-doc.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;
    }

});

要检查消息是否延迟,请使用 . 它是一个单独的属性,用于避免意外传播到从 input 消息生成的输出消息。getReceivedDelay()MessagePropertiesspring-doc.cn

4.1.14. RabbitMQ REST API

启用管理插件后,RabbitMQ 服务器将公开一个 REST API 来监视和配置代理。 现在提供了 API 的 Java 绑定。 这是一个标准的、即时的、因此是阻塞的 API。 它基于 Spring Web 模块及其实现。 另一方面,这是一个基于 Reactor Netty 项目的响应式、非阻塞实现。com.rabbitmq.http.client.ClientRestTemplatecom.rabbitmq.http.client.ReactorNettyClientspring-doc.cn

跃点依赖关系 () 现在也是 。com.rabbitmq:http-clientoptionalspring-doc.cn

有关更多信息,请参阅他们的 Javadoc。spring-doc.cn

4.1.15. 异常处理

使用 RabbitMQ Java 客户端的许多操作可能会引发检查异常。 例如,在很多情况下可能会引发实例。 , , 和其他 Spring AMQP 组件捕获这些异常,并将它们转换为层次结构中的异常之一。 这些是在'org.springframework.amqp'包中定义的,并且是层次结构的基础。IOExceptionRabbitTemplateSimpleMessageListenerContainerAmqpExceptionAmqpExceptionspring-doc.cn

当侦听器引发异常时,它被包装在 . 通常,消息会被拒绝并被代理重新排队。 设置为 会导致消息被丢弃(或路由到死信交换)。 正如消息侦听器和异步情况中所述,侦听器可以抛出 (or ) 来有条件地控制此行为。ListenerExecutionFailedExceptiondefaultRequeueRejectedfalseAmqpRejectAndDontRequeueExceptionImmediateRequeueAmqpExceptionspring-doc.cn

但是,有一类错误是侦听器无法控制行为的。 当遇到无法转换的消息(例如,无效的 header)时,会在消息到达用户代码之前引发一些异常。 设置为 (default) (或throwing an )后,此类消息将一遍又一遍地重新传递。 在版本 1.3.2 之前,用户需要编写自定义 ,如 异常处理 中所述,以避免这种情况。content_encodingdefaultRequeueRejectedtrueImmediateRequeueAmqpExceptionErrorHandlerspring-doc.cn

从版本 1.3.2 开始,现在的默认值是 a,它拒绝(并且不会重新排队)因不可恢复的错误而失败的消息。 具体而言,它会拒绝失败并显示以下错误的消息:ErrorHandlerConditionalRejectingErrorHandlerspring-doc.cn

  • o.s.amqp…​MessageConversionException:在使用 .MessageConverterspring-doc.cn

  • o.s.messaging…​MessageConversionException:如果在映射到方法时需要额外的转换,则转换服务可以引发此消息。@RabbitListenerspring-doc.cn

  • o.s.messaging…​MethodArgumentNotValidException:如果在侦听器中使用了验证(例如 ),并且验证失败,则可以引发。@Validspring-doc.cn

  • o.s.messaging…​MethodArgumentTypeMismatchException:如果入站消息已转换为不适合目标方法的类型,则可能会引发此错误。 例如,参数声明为 but is received。Message<Foo>Message<Bar>spring-doc.cn

  • java.lang.NoSuchMethodException:在 1.6.3 版本中添加。spring-doc.cn

  • java.lang.ClassCastException:在 1.6.3 版本中添加。spring-doc.cn

你可以使用 a 配置此错误处理程序的实例,以便用户可以为条件消息拒绝提供自己的规则 - 例如,从 Spring 重试(消息侦听器和异步情况)的委托实现。 此外,现在还有一个可在决策中使用的属性。 如果该方法返回 ,则错误处理程序将引发 . 默认情况下,当异常被确定为致命异常时,将记录一条警告消息。FatalExceptionStrategyBinaryExceptionClassifierListenerExecutionFailedExceptionfailedMessageFatalExceptionStrategy.isFatal()trueAmqpRejectAndDontRequeueExceptionFatalExceptionStrategyspring-doc.cn

从版本 1.6.3 开始,将用户异常添加到致命列表的一种便捷方法是 subclass 并覆盖该方法以返回致命异常。ConditionalRejectingErrorHandler.DefaultExceptionStrategyisUserCauseFatal(Throwable cause)truespring-doc.cn

处理 DLQ 消息的常见模式是在这些消息上设置 a 以及其他 DLQ 配置,以便这些消息过期并路由回主队列进行重试。 这种技术的问题在于,导致致命异常的消息会永远循环。 从版本 2.1 开始,它会在消息上检测到导致引发致命异常的 Headers。 该消息将被记录并丢弃。 您可以通过将 上的属性设置为 来恢复到以前的行为。time-to-liveConditionalRejectingErrorHandlerx-deathdiscardFatalsWithXDeathConditionalRejectingErrorHandlerfalsespring-doc.cn

从版本 2.1.9 开始,默认情况下,具有这些致命异常的消息将被拒绝并且不会重新排队,即使容器确认模式为 MANUAL。 这些异常通常发生在调用侦听器之前,因此侦听器没有机会对消息进行 ack 或 nack,因此它以 un-acked 状态保留在队列中。 要恢复到以前的行为,请将 上的属性设置为 。rejectManualConditionalRejectingErrorHandlerfalse

4.1.16. 事务

Spring Rabbit 框架支持同步和异步用例中的自动事务管理,具有许多不同的语义,这些语义可以通过声明方式进行选择,正如 Spring 事务的现有用户所熟悉的那样。 这使得许多(如果不是最常见的)消息传递模式易于实现。spring-doc.cn

有两种方法可以向框架发出所需的事务语义信号。 在 和 中都有一个标志,如果 ,该标志告诉框架使用事务通道,并以提交或回滚(取决于结果)结束所有操作(发送或接收),并发出回滚信号的异常。 另一个信号是提供一个外部事务,其中包含 Spring 的实现之一作为正在进行的操作的上下文。 如果在框架发送或接收消息时已经有一个事务正在进行中,并且标志为 ,则消息收发事务的提交或回滚将推迟到当前事务结束。 如果标志是 ,则没有事务语义适用于消息传递操作(它是自动确认的)。RabbitTemplateSimpleMessageListenerContainerchannelTransactedtruePlatformTransactionManagerchannelTransactedtruechannelTransactedfalsespring-doc.cn

该标志是配置时间设置。 它在创建 AMQP 组件时(通常在应用程序启动时)声明和处理一次。 外部事务原则上是动态的,因为系统在运行时响应当前线程状态。 但是,在实践中,当事务以声明方式分层到应用程序上时,它通常也是一种配置设置。channelTransactedspring-doc.cn

对于具有的同步用例,外部事务由调用者根据口味(通常的 Spring 事务模型)以声明或命令方式提供。 以下示例显示了一种声明性方法(通常是首选的,因为它是非侵入性的),其中模板已配置了 :RabbitTemplatechannelTransacted=truespring-doc.cn

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}

在前面的示例中,有效负载在标记为 . 如果数据库处理失败并出现异常,则传入消息将返回给代理,并且不会发送传出消息。 这适用于在事务方法链内部的任何操作(例如,除非直接操作以提前提交事务)。String@TransactionalRabbitTemplateChannelspring-doc.cn

对于具有 的异步用例,如果需要外部事务,则容器必须在设置侦听器时请求该事务。 为了表示需要外部事务,用户在配置容器时提供容器的实现。 以下示例显示了如何执行此操作:SimpleMessageListenerContainerPlatformTransactionManagerspring-doc.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 定义(未显示)注入的依赖项,并且该标志也被设置为 。 其效果是,如果侦听器失败并出现异常,则事务将回滚,并且消息也会返回给代理。 值得注意的是,如果事务提交失败(例如,由于 数据库约束错误或连接问题),则 AMQP 事务也会回滚,并将消息返回给代理。 这有时被称为“尽力而为的 1 阶段提交”,是一种非常强大的可靠消息传递模式。 如果在前面的示例中将标志设置为(默认值),则仍将为侦听器提供外部事务,但所有消息传递操作都将自动确认,因此效果是即使在业务操作回滚时也提交消息传递操作。channelTransactedtruechannelTransactedfalsespring-doc.cn

条件回滚

在版本 1.6.6 之前,在使用外部事务管理器(例如 JDBC)时,向容器添加回滚规则不起作用。 异常始终回滚事务。transactionAttributespring-doc.cn

此外,当在容器的通知链中使用事务通知时,条件回滚不是很有用,因为所有侦听器异常都包装在一个 .ListenerExecutionFailedExceptionspring-doc.cn

第一个问题已得到纠正,规则现已正确应用。 此外,现在提供了 。 它是 的子类,唯一的区别是它知道 并针对规则使用此类异常的原因。 此 transaction 属性可以直接在容器中使用,也可以通过 transaction advice 使用。ListenerFailedRuleBasedTransactionAttributeRuleBasedTransactionAttributeListenerExecutionFailedExceptionspring-doc.cn

以下示例使用此规则:spring-doc.cn

@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =
        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(
        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}
关于回滚已接收消息的说明

AMQP 事务仅适用于发送到 broker 的消息和 ack。 因此,当 Spring 事务回滚并且已经收到消息时, Spring AMQP 不仅必须回滚事务,还必须手动拒绝该消息(有点诀窍,但这不是规范所说的)。 对消息拒绝执行的操作独立于 transactions 并取决于属性 (default: )。 有关拒绝失败消息的更多信息,请参阅消息侦听器和异步情况defaultRequeueRejectedtruespring-doc.cn

有关 RabbitMQ 事务及其限制的更多信息,请参阅 RabbitMQ 代理语义spring-doc.cn

在 RabbitMQ 2.7.0 之前,此类消息(以及在通道关闭或中止时未确认的任何消息)将放在 Rabbit 代理队列的后面。 从 2.7.0 开始,被拒绝的消息将排在队列的前面,其方式与 JMS 回滚消息类似。
以前,事务回滚时的消息重新排队在本地事务和提供 a 时不一致。 在前一种情况下,应用了正常的重新排队逻辑 ( 或 )(请参阅 消息侦听器和异步情况)。 使用事务管理器,消息在回滚时无条件地重新排队。 从版本 2.0 开始,行为是一致的,并且在这两种情况下都应用了正常的 requeue 逻辑。 要恢复到以前的行为,您可以将容器的属性设置为 。 请参阅 消息侦听器容器配置TransactionManagerAmqpRejectAndDontRequeueExceptiondefaultRequeueRejected=falsealwaysRequeueWithTxManagerRollbacktrue
RabbitTransactionManager

RabbitTransactionManager是在外部事务中执行 Rabbit 操作并与外部事务同步的替代方法。 此事务管理器是 PlatformTransactionManager 接口的实现,应与单个 Rabbit 一起使用。ConnectionFactoryspring-doc.cn

此策略无法提供 XA 事务 — 例如,为了在消息传递和数据库访问之间共享事务。

需要应用程序代码来检索事务性 Rabbit 资源,而不是在后续通道创建时进行标准调用。 当使用 Spring AMQP 的RabbitTemplate时,它将自动检测线程绑定的Channel并自动参与其事务。ConnectionFactoryUtils.getTransactionalResourceHolder(ConnectionFactory, boolean)Connection.createChannel()spring-doc.cn

使用 Java 配置,您可以使用以下 bean 设置新的RabbitTransactionManager:spring-doc.cn

@Bean
public RabbitTransactionManager rabbitTransactionManager() {
    return new RabbitTransactionManager(connectionFactory);
}

如果您更喜欢 XML 配置,则可以在 XML Application Context 文件中声明以下 Bean:spring-doc.cn

<bean id="rabbitTxManager"
      class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>
事务同步

将 RabbitMQ 事务与其他事务(例如 DBMS)同步可提供“尽力而为的一阶段提交”语义。 RabbitMQ 事务可能会在事务同步的完成后阶段提交失败。 此事件由基础结构记录为错误,但不会向调用代码引发异常。 从版本 2.3.10 开始,您可以在事务提交到处理事务的同一线程上后调用。 如果未发生异常,它将简单地返回;否则,它将抛出一个 WHICH 将具有一个表示完成同步状态的属性。spring-txConnectionUtils.checkAfterCompletion()AfterCompletionFailedExceptionspring-doc.cn

通过调用 ;这是一个全局标志,适用于所有线程。ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true)spring-doc.cn

4.1.17. 消息侦听器容器配置

配置与事务和服务质量相关的 (SMLC) 和 (DMLC) 的选项相当多,其中一些选项是相互交互的。 适用于 SMLC、DMLC 或 (StLC) 的属性(请参阅使用 RabbitMQ Stream 插件)由相应列中的复选标记指示。 请参阅选择容器,了解可帮助您确定哪个容器适合您的应用程序的信息。SimpleMessageListenerContainerDirectMessageListenerContainerStreamListenerContainerspring-doc.cn

下表显示了使用命名空间配置 . 该元素上的属性可以是 (default) 或分别指定 or。 某些属性不由命名空间公开。 这些由 for the attribute 表示。<rabbit:listener-container/>typesimpledirectSMLCDMLCN/Aspring-doc.cn

表 3.消息侦听器容器的配置选项
财产 (属性) 描述 SMLC 公司 DMLC StLC

ackTimeout
(不适用)
spring-doc.cn

设置后,此超时将用作发送 ack 的替代方法。 当新消息到达时,将未确认的消息计数与 进行比较,并将自上次确认以来的时间与此值进行比较。 如果任一条件为 ,则确认消息。 当没有新消息到达并且有未确认的消息时,此超时是近似值,因为仅检查每个条件。 另请参阅 和 在此表中。messagesPerAckmessagesPerAcktruemonitorIntervalmessagesPerAckmonitorIntervalspring-doc.cn

刻度线

acknowledgeMode
(确认)
spring-doc.cn

  • NONE:不发送确认(不兼容)。 RabbitMQ 将此称为“autoack”,因为代理假定所有消息都已确认,而无需使用者执行任何操作。channelTransacted=truespring-doc.cn

  • MANUAL:侦听器必须通过调用 来确认所有消息。Channel.basicAck()spring-doc.cn

  • AUTO:容器会自动确认消息,除非引发异常。 请注意,这是对 — 如果通道是事务处理的,则 broker 除了 ack 之外还需要提交通知。 这是默认模式。 另请参阅 。MessageListeneracknowledgeModechannelTransactedbatchSizespring-doc.cn

刻度线
刻度线

建议链
(advice-chain)
spring-doc.cn

应用于侦听器执行的 AOP Advice 数组。 这可用于应用其他横切关注点,例如在 broker 死亡时自动重试。 请注意,只要代理仍然处于活动状态,AMQP 错误后的简单重新连接就会由 处理。CachingConnectionFactoryspring-doc.cn

刻度线
刻度线

afterReceivePostProcessors
(不适用)
spring-doc.cn

在调用侦听器之前调用的实例的数组。 后处理器可以实现 或 . 数组使用最后调用的无序成员进行排序。 如果后处理器返回 ,则丢弃消息(并确认,如果合适)。MessagePostProcessorPriorityOrderedOrderednullspring-doc.cn

刻度线
刻度线

alwaysRequeueWithTxManagerRollback
(不适用)
spring-doc.cn

设置为在配置事务管理器时始终在回滚时重新排队消息。truespring-doc.cn

刻度线
刻度线

autoDeclare
(自动声明)
spring-doc.cn

当设置为 (default) 时,如果容器在启动期间检测到至少一个队列丢失,则容器使用 a 重新声明所有 AMQP 对象(队列、交换、绑定),这可能是因为它是队列或过期的队列,但如果队列由于任何原因丢失,则重新声明将继续。 要禁用此行为,请将此属性设置为 。 请注意,如果容器的所有队列都缺失,则容器将无法启动。trueRabbitAdminauto-deletefalsespring-doc.cn

在版本 1.6 之前,如果上下文中有多个 admin,则容器将随机选择一个。 如果没有管理员,它将在内部创建一个管理员。 无论哪种情况,都可能导致意外结果。 从版本 1.6 开始,要使上下文中只有一个,或者必须使用该属性在容器上配置对特定实例的引用。autoDeclareRabbitAdminrabbitAdmin
刻度线
刻度线

autoStartup
(自动启动)
spring-doc.cn

标志,指示容器应在启动时启动(作为回调的一部分,在所有 bean 初始化后发生)。 默认为 ,但您可以将其设置为如果您的代理在启动时可能不可用,并在知道代理准备就绪时稍后手动调用。ApplicationContextSmartLifecycletruefalsestart()spring-doc.cn

刻度线
刻度线
刻度线

batchSize
(交易大小) (批量大小)
spring-doc.cn

当与 set to 一起使用时,容器会尝试在发送确认之前处理最多此数量的消息(等待每条消息直到接收超时设置)。 这也是提交事务通道时。 如果 小于 ,则增加 以匹配 。acknowledgeModeAUTOprefetchCountbatchSizebatchSizespring-doc.cn

刻度线

batchingStrategy
(不适用)
spring-doc.cn

对消息进行 debatchng 时使用的策略。 违约。 请参阅批处理使用 Batching 进行@RabbitListenerSimpleDebatchingStrategyspring-doc.cn

刻度线
刻度线

channelTransacted
(channel-transacted)
spring-doc.cn

Boolean 标志,表示应在事务中确认所有消息(手动或自动)。spring-doc.cn

刻度线
刻度线

并发
(N/A)
spring-doc.cn

m-n每个侦听器的并发使用者范围(最小值、最大值)。 如果 only 提供,则为固定数量的使用者。 请参阅 侦听器并发nnspring-doc.cn

刻度线

concurrentConsumers
(并发)
spring-doc.cn

每个侦听器最初要启动的并发使用者数。 请参阅 侦听器并发。 对于 ,并发是通过重载方法控制的;请参阅使用具有单个活动使用者的 Super StreamsStLCsuperStreamspring-doc.cn

刻度线
刻度线

connectionFactory
(连接工厂)
spring-doc.cn

对 . 使用 XML 名称空间进行配置时,默认引用的 Bean 名称为 。ConnectionFactoryrabbitConnectionFactoryspring-doc.cn

刻度线
刻度线

consecutiveActiveTrigger
(min-consecutive-active)
spring-doc.cn

在考虑启动新使用者时,使用者在未发生接收超时的情况下接收的连续消息的最小数量。 也受 'batchSize' 影响。 请参阅 侦听器并发。 默认值:10。spring-doc.cn

刻度线

consecutiveIdleTrigger
(min-consecutive-idle)
spring-doc.cn

使用者在考虑停止使用者之前必须经历的最小接收超时次数。 也受 'batchSize' 影响。 请参阅 侦听器并发。 默认值:10。spring-doc.cn

刻度线

consumerBatchEnabled
(支持批处理)
spring-doc.cn

如果支持,则将其设置为 true 可启用离散消息的批处理,最高可达 ;如果没有新消息到达 ,将传送部分批处理。 如果为 false,则仅支持由 producer 创建的批处理;请参阅 批处理MessageListenerbatchSizereceiveTimeoutspring-doc.cn

刻度线

consumerCustomizer
(不适用)
spring-doc.cn

用于修改容器创建的流使用者的 Bean。ConsumerCustomizerspring-doc.cn

刻度线

consumerStartTimeout
(不适用)
spring-doc.cn

等待使用者线程启动的时间(以毫秒为单位)。 如果超过此时间,则会写入错误日志。 发生这种情况的一个示例是,如果配置的线程不足以支持 container 。taskExecutorconcurrentConsumersspring-doc.cn

请参阅 线程处理和异步使用者。 默认值:60000(1 分钟)。spring-doc.cn

刻度线

consumerTagStrategy
(消费者标签策略)
spring-doc.cn

设置 ConsumerTagStrategy 的实现,以便为每个使用者创建一个(唯一)标签。spring-doc.cn

刻度线
刻度线

consumersPerQueue
(每个队列的使用者)
spring-doc.cn

要为每个配置的队列创建的使用者数量。 请参阅 侦听器并发spring-doc.cn

刻度线

consumeDelay
(不适用)
spring-doc.cn

RabbitMQ Sharding Plugin 与 一起使用时,存在争用条件,该条件可能会阻止使用者在分片之间均匀分布。 使用此属性可在使用者启动之间添加一个小的延迟,以避免这种争用条件。 您应该尝试各种值以确定适合您环境的延迟。concurrentConsumers > 1spring-doc.cn

刻度线
刻度线

debatching已启用
(N/A)
spring-doc.cn

如果为 true,则侦听器容器将对批处理消息进行 Debatch,并使用批处理中的每条消息调用侦听器。 从版本 2.2.7 开始,如果侦听器是 或 ,则生产者创建的批处理将被解除批处理。 否则,将一次显示一个来自批处理的消息。 默认为 true。 请参阅批处理使用 Batching 进行@RabbitListenerList<Message>BatchMessageListenerChannelAwareBatchMessageListenerspring-doc.cn

刻度线
刻度线

declarationRetries
(declaration-retries)
spring-doc.cn

被动队列声明失败时的重试尝试次数。 被动队列声明发生在使用者启动时,或者从多个队列中使用时,当初始化期间并非所有队列都可用时。 当重试用尽后无法被动声明任何已配置的队列时(出于任何原因),容器行为由前面描述的“missingQueuesFatal”属性控制。 默认值:重试 3 次(总共 4 次尝试)。spring-doc.cn

刻度线

defaultRequeueRejected
(重新排队-已拒绝)
spring-doc.cn

确定是否应将因侦听器引发异常而被拒绝的消息重新排队。 违约:。truespring-doc.cn

刻度线
刻度线

errorHandler
(错误处理程序)
spring-doc.cn

对处理在执行 MessageListener 期间可能发生的任何未捕获异常的策略的引用。 违约:ErrorHandlerConditionalRejectingErrorHandlerspring-doc.cn

刻度线
刻度线

Exclusive
(独家)
spring-doc.cn

确定此容器中的单个使用者是否具有对队列的独占访问权限。 当 为 时,容器的并发数必须为 1。 如果另一个使用者具有独占访问权限,则容器会尝试根据 or 恢复使用者。 使用命名空间时,此属性与队列名称一起显示在元素上。 违约:。truerecovery-intervalrecovery-back-off<rabbit:listener/>falsespring-doc.cn

刻度线
刻度线

exclusiveConsumerExceptionLogger
(不适用)
spring-doc.cn

当独占使用者无法访问队列时使用的异常记录器。 默认情况下,此记录在 级别.WARNspring-doc.cn

刻度线
刻度线

failedDeclarationRetryInterval
(failed-declaration -重试间隔)
spring-doc.cn

被动队列声明重试尝试之间的间隔。 被动队列声明发生在使用者启动时,或者从多个队列中使用时,当初始化期间并非所有队列都可用时。 默认值:5000(5 秒)。spring-doc.cn

刻度线
刻度线

forceCloseChannel
(不适用)
spring-doc.cn

如果使用者在 中未响应 shutdown ,则为 ,则通道将关闭,从而导致任何未确认的消息重新排队。 默认为 2.0 起。 您可以将其设置为 以恢复到之前的行为。shutdownTimeouttruetruefalsespring-doc.cn

刻度线
刻度线

forceStop
(不适用)
spring-doc.cn

设置为 true 可在处理当前记录后停止(当容器停止时);导致所有预取的消息重新排队。 默认情况下,容器会取消 Consumer 并在停止之前处理所有预取的消息。 版本 2.4.14、3.0.6 的最新版本 默认为 。falsespring-doc.cn

刻度线
刻度线

globalQos
(global-qos)
spring-doc.cn

如果为 true,则 the 将全局应用于通道,而不是应用于通道上的每个使用者。 有关更多信息,请参阅 basicQos.globalprefetchCountspring-doc.cn

刻度线
刻度线

(群展)spring-doc.cn

这仅在使用 namespace 时可用。 指定后,将使用此名称注册 类型的 Bean,并且 容器 将添加到集合中。 例如,这允许通过迭代集合来启动和停止容器组。 如果多个元素具有相同的 group 值,则集合表单中的容器 如此指定的所有容器的集合。Collection<MessageListenerContainer><listener/><listener-container/>spring-doc.cn

刻度线
刻度线

idleEventInterval
(空闲事件间隔)
spring-doc.cn

请参阅 检测空闲的异步使用者spring-doc.cn

刻度线
刻度线

javaLangErrorHandler
(不适用)
spring-doc.cn

当容器线程捕获 . 默认实现调用 ;要恢复到之前的行为(不执行任何操作),请添加 no-op 处理程序。AbstractMessageListenerContainer.JavaLangErrorHandlerErrorSystem.exit(99)spring-doc.cn

刻度线
刻度线

maxConcurrentConsumers
(最大并发)
spring-doc.cn

如果需要,按需启动的最大并发使用者数。 必须大于或等于 'concurrentConsumers'。 请参阅 侦听器并发spring-doc.cn

刻度线

messagesPerAck
(不适用)
spring-doc.cn

ack 之间要接收的消息数。 使用此选项可以减少发送到代理的 ack 数(以增加重新传送消息的可能性为代价)。 通常,您应该仅在高容量侦听器容器上设置此属性。 如果设置了此选项并且消息被拒绝(引发异常),则会确认待处理的确认并拒绝失败的消息。 不允许用于事务处理通道。 如果 小于 ,则增加 以匹配 。 默认值:ack every message 另请参阅此表中的内容。prefetchCountmessagesPerAckmessagesPerAckackTimeoutspring-doc.cn

刻度线

mismatchedQueuesFatal
(mismatched-queues-fatal)
spring-doc.cn

当容器启动时,如果此属性为 (default: ),则容器将检查上下文中声明的所有队列是否与代理上已有的队列兼容。 如果存在不匹配的属性(例如 ) 或参数 (skuch as ),则容器(和应用程序上下文)无法启动并出现致命异常。truefalseauto-deletex-message-ttlspring-doc.cn

如果在恢复过程中检测到问题(例如,在丢失连接后),则容器将停止。spring-doc.cn

应用程序上下文中必须有一个 single (或使用 property 在容器上专门配置的 single )。 否则,此属性必须为 。RabbitAdminrabbitAdminfalsespring-doc.cn

如果代理在初始启动期间不可用,则容器将启动,并在建立连接时检查条件。
将针对上下文中的所有队列进行检查,而不仅仅是针对特定侦听器配置为使用的队列。 如果您希望将检查限制为容器使用的那些队列,则应为容器配置单独的队列,并使用属性提供对它的引用。 有关更多信息,请参阅 条件声明RabbitAdminrabbitAdmin
在标记为 的 bean 中为 a 启动容器时,将禁用不匹配的队列参数检测。 这是为了避免潜在的死锁,这可能会使此类容器的启动延迟长达 60 秒。 使用惰性侦听器 bean 的应用程序应该在获取对惰性 bean 的引用之前检查队列参数。@RabbitListener@Lazy
刻度线
刻度线

missingQueuesFatal
(missing-queues-fatal)
spring-doc.cn

当设置为 (default) 时,如果代理上没有任何配置的队列可用,则将其视为致命队列。 这会导致应用程序上下文在启动期间无法初始化。 此外,在容器运行时删除队列时,默认情况下,使用者会重试 3 次以连接到队列(间隔 5 秒),并在这些尝试失败时停止容器。truespring-doc.cn

这在以前的版本中是不可配置的。spring-doc.cn

设置为 时,在进行 3 次重试后,容器将进入恢复模式,就像其他问题(如代理关闭)一样。 容器尝试根据属性进行恢复。 在每次恢复尝试期间,每个使用者再次尝试四次,以 5 秒的间隔被动声明队列。 这个过程无限期地持续下去。falserecoveryIntervalspring-doc.cn

您还可以使用 properties Bean 为所有容器全局设置属性,如下所示:spring-doc.cn

<util:properties
        id="spring.amqp.global.properties">
    <prop key="mlc.missing.queues.fatal">
        false
    </prop>
</util:properties>

此全局属性不适用于设置了 explicit 属性的任何容器。missingQueuesFatalspring-doc.cn

默认重试属性(每隔 5 秒重试 3 次)可以通过设置以下属性来覆盖。spring-doc.cn

在标记为 的 bean 中为 a 启动容器时,将禁用缺少队列检测。 这是为了避免潜在的死锁,这可能会使此类容器的启动延迟长达 60 秒。 使用惰性侦听器 bean 的应用程序应该在获取对惰性 bean 的引用之前检查队列。@RabbitListener@Lazy
刻度线
刻度线

monitorInterval
(监视器间隔)
spring-doc.cn

使用 DMLC 时,将计划在此间隔运行任务,以监控使用者的状态并恢复任何失败的状态。spring-doc.cn

刻度线

非本地
(N/A)
spring-doc.cn

设置为 to 以禁止从服务器向在同一通道的连接上发布的使用者消息传送。truespring-doc.cn

刻度线
刻度线

phase
(相位)
spring-doc.cn

When is ,此容器应在其中启动和停止的生命周期阶段。 值越低,此容器开始得越早,停止得越晚。 默认值为 ,表示容器尽可能晚地启动并尽快停止。autoStartuptrueInteger.MAX_VALUEspring-doc.cn

刻度线
刻度线

possibleAuthenticationFailureFatal
(可能的身份验证失败致命)
spring-doc.cn

当设置为 (SMLC 的默认值) 时,如果在连接期间引发 a,则将其视为致命的。 这会导致应用程序上下文在启动期间无法初始化(如果容器配置了自动启动)。truePossibleAuthenticationFailureExceptionspring-doc.cn

2.0 版本开始。spring-doc.cn

DirectMessageListenerContainerspring-doc.cn

当设置为 (default) 时,每个使用者将尝试根据 .falsemonitorIntervalspring-doc.cn

SimpleMessageListenerContainer (简单消息监听器容器)spring-doc.cn

设置为 时,在进行 3 次重试后,容器将进入恢复模式,就像其他问题(如代理关闭)一样。 容器将尝试根据属性进行恢复。 在每次恢复尝试期间,每个使用者将再次尝试 4 次以启动。 这个过程将无限期地持续下去。falserecoveryIntervalspring-doc.cn

您还可以使用 properties Bean 为所有容器全局设置属性,如下所示:spring-doc.cn

<util:properties
    id="spring.amqp.global.properties">
  <prop
    key="mlc.possible.authentication.failure.fatal">
     false
  </prop>
</util:properties>

此全局属性不会应用于设置了 explicit 属性的任何容器。missingQueuesFatalspring-doc.cn

默认重试属性(3 次重试,间隔 5 秒)可以使用此属性之后的属性覆盖。spring-doc.cn

刻度线
刻度线

prefetchCount
(预取)
spring-doc.cn

每个使用者可以未完成的未确认消息数。 此值越高,消息的传递速度就越快,但非顺序处理的风险就越高。 如果 为 ,则忽略 。 如有必要,将增加此值以匹配 或 。 自 2.0 起默认为 250。 您可以将其设置为 1 以恢复到之前的行为。acknowledgeModeNONEbatchSizemessagePerAckspring-doc.cn

在某些情况下,prefetch 值应 低 — 例如,对于大型消息,尤其是在处理速度较慢的情况下(消息可能会累积 添加到客户端进程中的大量内存),并且如果需要严格的消息排序 (在这种情况下,prefetch 值应设置回 1)。 此外,对于低容量消息收发和多个使用者(包括单个侦听器容器实例中的并发),您可能希望减少预取,以便在使用者之间更均匀地分配消息。

另请参阅 。globalQosspring-doc.cn

刻度线
刻度线

rabbitAdmin
(管理员)
spring-doc.cn

当侦听器容器侦听至少一个自动删除队列并且在启动期间发现该队列缺失时,该容器将使用 a 来声明该队列以及任何相关的绑定和交换。 如果此类元素配置为使用条件声明(请参阅条件声明),则容器必须使用配置为声明这些元素的 admin。 在此处指定该管理员。 仅当使用带有条件声明的自动删除队列时,才需要它。 如果您不希望在容器启动之前声明自动删除队列,请在 admin 上设置为 。 默认为声明所有非条件元素的 a。RabbitAdminauto-startupfalseRabbitAdminspring-doc.cn

刻度线
刻度线

receiveTimeout
(接收超时)
spring-doc.cn

等待每条消息的最长时间。 如果 ,则效果非常小 — 容器旋转并请求另一条消息。 它对事务性 with 的影响最大,因为它可能导致在超时到期之前不确认已使用的消息。 如果为 true,则如果在批处理完成之前发生此超时,则将交付部分批处理。acknowledgeMode=NONEChannelbatchSize > 1consumerBatchEnabledspring-doc.cn

刻度线

recoveryBackOff
(recovery-back-off)
spring-doc.cn

指定在使用者因非致命原因无法启动时尝试启动使用者之间的间隔。 默认为每 5 秒无限次重试。 与 互斥。BackOffFixedBackOffrecoveryIntervalspring-doc.cn

刻度线
刻度线

recoveryInterval
(恢复间隔)
spring-doc.cn

确定如果使用者由于非致命原因而无法启动,则尝试启动使用者之间的时间(以毫秒为单位)。 默认值:5000。 与 互斥。recoveryBackOffspring-doc.cn

刻度线
刻度线

retryDeclarationInterval
(缺失队列- retry-interval)
spring-doc.cn

如果配置的队列的子集在使用者初始化期间可用,则使用者将开始从这些队列中使用。 使用者尝试使用此间隔被动声明缺少的队列。 当此间隔过后,将再次使用 'declarationRetries' 和 'failedDeclarationRetryInterval'。 如果仍然缺少队列,使用者会再次等待此间隔,然后再尝试一次。 此过程将无限期地持续,直到所有队列都可用。 默认值:60000(1 分钟)。spring-doc.cn

刻度线

shutdownTimeout
(不适用)
spring-doc.cn

当容器关闭时(例如, 如果其封闭已关闭),则它会等待处理不超过此限制的 In-Flight 消息。 默认为 5 秒。ApplicationContextspring-doc.cn

刻度线
刻度线

startConsumerMinInterval
(最小开始间隔)
spring-doc.cn

按需启动每个新使用者之前必须经过的时间(以毫秒为单位)。 请参阅 侦听器并发。 默认值:10000(10 秒)。spring-doc.cn

刻度线

statefulRetryFatal
与 NullMessageId (不适用)
spring-doc.cn

使用有状态重试通知时,如果收到带有 missing 属性的消息,则将其视为 fatal (它已停止)。 将此项设置为 to 丢弃(或路由到死信队列)此类消息。messageIdfalsespring-doc.cn

刻度线
刻度线

stopConsumerMinInterval
(最小停止间隔)
spring-doc.cn

在检测到空闲使用者时,自最后一个使用者停止以来,在停止使用者之前必须经过的时间(以毫秒为单位)。 请参阅 侦听器并发。 默认值:60000(1 分钟)。spring-doc.cn

刻度线

streamConverter
(不适用)
spring-doc.cn

A 将本机 Stream 消息转换为 Spring AMQP 消息。StreamMessageConverterspring-doc.cn

刻度线

taskExecutor
(任务执行者)
spring-doc.cn

对 Spring (或标准 JDK 1.5+ )的引用,用于执行侦听器调用程序。 默认值为 a ,使用内部管理的线程。TaskExecutorExecutorSimpleAsyncTaskExecutorspring-doc.cn

刻度线
刻度线

taskScheduler
(任务计划程序)
spring-doc.cn

使用 DMLC 时,调度程序用于在 'monitorInterval' 运行监视任务。spring-doc.cn

刻度线

transactionManager
(事务管理器)
spring-doc.cn

用于侦听器操作的外部事务管理器。 也是补充 — 如果 the 是 transacted,则其事务与外部事务同步。channelTransactedChannelspring-doc.cn

刻度线
刻度线

4.1.18. 侦听器并发

SimpleMessageListenerContainer (简单消息监听器容器)

默认情况下,侦听器容器启动一个从队列接收消息的使用者。spring-doc.cn

在检查上一节中的表时,您可以看到许多控制并发的属性和特性。 最简单的是 ,它创建并发处理消息的 (固定) 数量的使用者。concurrentConsumersspring-doc.cn

在版本 1.3.0 之前,这是唯一可用的设置,必须停止并重新启动容器才能更改设置。spring-doc.cn

从 1.3.0 版本开始,您现在可以动态调整属性。 如果在容器运行时更改了此设置,则会根据需要添加或删除使用者,以适应新设置。concurrentConsumersspring-doc.cn

此外,还添加了一个名为 的新属性,容器会根据工作负载动态调整并发。 这与四个附加属性结合使用: 、 、 和 。 使用默认设置时,增加使用者的算法工作如下:maxConcurrentConsumersconsecutiveActiveTriggerstartConsumerMinIntervalconsecutiveIdleTriggerstopConsumerMinIntervalspring-doc.cn

如果尚未达到 ,并且现有使用者连续 10 个周期处于活动状态,并且自最后一个使用者启动以来已经过了至少 10 秒,则会启动新的使用者。 如果使用者在 * 毫秒内收到至少一条消息,则认为该使用者处于活动状态。maxConcurrentConsumersbatchSizereceiveTimeoutspring-doc.cn

使用默认设置时,减少使用者的算法工作如下:spring-doc.cn

如果 Running 的使用者数超过 AND,并且使用者检测到连续 10 次超时 (空闲),并且最后一个使用者至少在 60 秒前停止,则使用者将停止。 超时取决于 和 属性。 如果使用者在 * 毫秒内未收到任何消息,则认为该使用者处于空闲状态。 因此,使用默认超时 (1 秒) 和 4 时,在 40 秒的空闲时间后考虑停止使用者 (4 次超时对应于 1 次空闲检测)。concurrentConsumersreceiveTimeoutbatchSizebatchSizereceiveTimeoutbatchSizespring-doc.cn

实际上,只有当整个容器空闲一段时间时,才能停止消费者。 这是因为 broker 在所有活动使用者之间共享其工作。

每个使用者都使用一个通道,而不管配置的队列数量如何。spring-doc.cn

从版本 2.0 开始,可以使用属性设置 and 属性 — 例如,.concurrentConsumersmaxConcurrentConsumersconcurrency2-4spring-doc.cn

DirectMessageListenerContainer

使用此容器,并发性基于配置的队列和 。 每个队列的每个使用者都使用一个单独的通道,并发性由 rabbit 客户端库控制。 默认情况下,在撰写本文时,它使用线程池。consumersPerQueueDEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2spring-doc.cn

您可以配置 a 以提供所需的最大并发性。taskExecutorspring-doc.cn

4.1.19. 独占消费者

从版本 1.3 开始,您可以使用单个独占使用者配置侦听器容器。 这可以防止其他容器从队列中使用,直到当前使用者被取消。 此类容器的并发性必须为 。1spring-doc.cn

使用独占使用者时,其他容器会尝试根据属性从队列中消费,并在尝试失败时记录一条消息。recoveryIntervalWARNspring-doc.cn

4.1.20. 侦听器容器队列

版本 1.3 引入了许多改进,用于处理侦听器容器中的多个队列。spring-doc.cn

容器最初可以配置为侦听零个队列。 可以在运行时添加和删除队列。 在处理了任何预取的消息后,将回收(取消和重新创建)所有使用者。 为每个队列创建/取消单个使用者,而不会影响其他队列上的使用者。 请参阅 Javadoc 中的 、 和 方法。SimpleMessageListenerContainerDirectMessageListenerContaineraddQueuesaddQueueNamesremoveQueuesremoveQueueNamesspring-doc.cn

如果并非所有队列都可用,则容器会尝试每 60 秒被动声明(并使用)一次缺失的队列。spring-doc.cn

此外,如果使用方收到来自代理的取消(例如,如果队列被删除),则使用方会尝试恢复,并且恢复的使用方将继续处理来自任何其他已配置队列的消息。 以前,一个队列上的 cancel 会取消整个使用者,最终,容器会因缺少队列而停止。spring-doc.cn

如果您希望永久删除队列,则应在删除到队列之前或之后更新容器,以避免将来尝试从中消费。spring-doc.cn

4.1.21. 弹性:从错误和 Broker 故障中恢复

Spring AMQP 提供的一些关键(也是最流行的)高级功能与在发生协议错误或代理故障时的恢复和自动重新连接有关。 我们已经在本指南中看到了所有相关组件,但将它们全部汇集在这里并单独介绍功能和恢复场景应该会有所帮助。spring-doc.cn

主要的重新连接功能由自身启用。 使用自动声明功能通常也是有益的。 此外,如果您关心保证送达,您可能还需要在 和 中使用标志,并在 .CachingConnectionFactoryRabbitAdminchannelTransactedRabbitTemplateSimpleMessageListenerContainerAcknowledgeMode.AUTOSimpleMessageListenerContainerspring-doc.cn

自动声明交换、队列和绑定

该组件可以在启动时声明 exchanges、queues 和 bindings。 它通过 . 因此,如果 broker 在启动时不存在,则无关紧要。 第一次使用 a 时(例如 通过发送消息)将触发侦听器并应用 Admin 功能。 在侦听器中执行 auto 声明的另一个好处是,如果由于任何原因(例如, Broker Death、Network Glitch 等),则在重新建立连接时会再次应用它们。RabbitAdminConnectionListenerConnectionspring-doc.cn

以这种方式声明的队列必须具有固定名称 — 显式声明或由框架为实例生成。 匿名队列是非持久队列、独占队列和自动删除队列。AnonymousQueue
仅当 cache mode 为 (default) 时,才会执行自动声明。 存在此限制是因为独占队列和自动删除队列绑定到连接。CachingConnectionFactoryCHANNEL

从版本 2.2.2 开始,将检测 bean 类型的 bean 并在实际处理声明之前应用该函数。 这很有用,例如,在框架中具有一等支持之前设置新参数 (property)。RabbitAdminDeclarableCustomizerspring-doc.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;
    };
}

在不提供对 Bean 定义的直接访问的项目中,它也很有用。Declarablespring-doc.cn

同步操作失败和重试选项

如果您在使用(例如)时以同步序列失去了与代理的连接,则 Spring AMQP 会抛出一个(通常,但并非总是)。 我们不会试图隐藏存在问题的事实,因此您必须能够捕获并响应异常。 如果您怀疑连接丢失(这不是您的错),最简单的方法是再次尝试该操作。 你可以手动执行此操作,也可以考虑使用 Spring Retry 来处理重试(命令式或声明式)。RabbitTemplateAmqpExceptionAmqpIOExceptionspring-doc.cn

Spring Retry 提供了几个 AOP 拦截器,并且具有很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法等)。 Spring AMQP 还提供了一些方便的工厂 bean,用于以方便的形式为 AMQP 用例创建 Spring 重试拦截器,并具有可用于实现自定义恢复逻辑的强类型回调接口。 有关详细信息,请参阅 和 的 Javadoc 和 properties。 如果没有事务,或者事务是在重试回调中启动的,则无状态重试是合适的。 请注意,无状态重试比有状态重试更易于配置和分析,但如果必须回滚或肯定要回滚正在进行的事务,则通常不合适。 在事务中间丢弃的连接应具有与回滚相同的效果。 因此,对于事务在堆栈上层启动的重新连接,有状态重试通常是最佳选择。 有状态重试需要一种机制来唯一标识消息。 最简单的方法是让发送方在 message 属性中放置一个唯一值。 提供的消息转换器提供了一个选项来执行此操作:您可以设置为 。 否则,你可以将 implementation 注入到拦截器中。 密钥生成器必须为每条消息返回一个唯一的密钥。 在版本 2.0 之前的版本中,提供了 a。 它允许没有属性的消息只重试一次(忽略重试设置)。 不再提供此建议,因为与版本 1.2 一起,其功能内置于拦截器和消息侦听器容器中。StatefulRetryOperationsInterceptorStatelessRetryOperationsInterceptorMessageIdcreateMessageIdstrueMessageKeyGeneratorMissingMessageIdAdvicemessageIdspring-retryspring-doc.cn

为了向后兼容,默认情况下(重试一次后),消息 ID 为 null 的消息被视为对使用者(使用者已停止)致命。 要复制 提供的功能,可以在侦听器容器上将属性设置为。 使用该设置,使用者将继续运行,消息将被拒绝(重试一次后)。 它被丢弃或路由到死信队列(如果已配置)。MissingMessageIdAdvicestatefulRetryFatalWithNullMessageIdfalse

从版本 1.3 开始,提供了一个构建器 API 来帮助使用 Java(在类中)组装这些拦截器。 以下示例显示了如何执行此操作:@Configurationspring-doc.cn

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}

只能以这种方式配置重试功能的子集。 更高级的功能需要将 a 配置为 Spring bean。 有关可用策略及其配置的完整信息,请参阅 Spring 重试 JavadocRetryTemplatespring-doc.cn

使用 Batch 侦听器重试

建议不要使用批处理侦听器配置重试,除非该批处理是由创建者在单个记录中创建的。 请参阅Batched Messages 以了解有关使用者和创建者创建的批处理的信息。 对于使用者创建的批处理,框架不知道批处理中的哪条消息导致了失败,因此在重试用尽后无法恢复。 对于创建者创建的批处理,由于实际上只有一条消息失败,因此可以恢复整个消息。 应用程序可能希望通知自定义恢复程序在批处理中发生故障的位置,可能通过设置引发的异常的 index 属性。spring-doc.cn

批处理侦听器的重试 recoverer 必须实现 。MessageBatchRecovererspring-doc.cn

消息侦听器和异步情况

如果 a 由于业务异常而失败,则异常由消息侦听器容器处理,然后该容器返回侦听另一条消息。 如果失败是由断开的连接引起的(不是业务异常),则必须取消并重新启动为侦听器收集消息的使用者。 它会无缝地处理此问题,并留下一个日志来说明侦听器正在重新启动。 事实上,它无休止地循环,试图重新启动消费者。 只有当消费者确实行为非常糟糕时,它才会放弃。 一个副作用是,如果 broker 在容器启动时关闭,它会一直尝试,直到可以建立连接。MessageListenerSimpleMessageListenerContainerspring-doc.cn

与协议错误和断开连接相反,业务异常处理可能需要更多的思考和一些自定义配置,尤其是在使用事务或容器 ack 时。 在 2.8.x 之前,RabbitMQ 没有死信行为的定义。 因此,默认情况下,由于业务异常而被拒绝或回滚的消息可以无限地重新传递。 要限制客户重新交付的数量,一个选择是 a 在侦听器的建议链中。 拦截器可以具有实现自定义死信操作的恢复回调 — 任何适合您的特定环境的方法。StatefulRetryOperationsInterceptorspring-doc.cn

另一种替代方法是将容器的属性设置为 。 这会导致所有失败的消息都被丢弃。 当使用 RabbitMQ 2.8.x 或更高版本时,这也有助于将消息传送到死信交换。defaultRequeueRejectedfalsespring-doc.cn

或者,您可以抛出 . 这样做可以防止消息重新排队,而不管属性的设置如何。AmqpRejectAndDontRequeueExceptiondefaultRequeueRejectedspring-doc.cn

从版本 2.1 开始,引入了 an 来执行完全相反的逻辑:无论属性的设置如何,消息都将被重新排队。ImmediateRequeueAmqpExceptiondefaultRequeueRejectedspring-doc.cn

通常,会结合使用这两种技术。 您可以在通知链中使用 a,其中 a 会引发 . 当所有重试都已用尽时,将调用 。 这正是这样做的。 默认值使用错误的消息并发出一条消息。StatefulRetryOperationsInterceptorMessageRecovererAmqpRejectAndDontRequeueExceptionMessageRecoverRejectAndDontRequeueRecovererMessageRecovererWARNspring-doc.cn

从版本 1.3 开始,提供了一个 new,允许在重试用尽后发布失败的消息。RepublishMessageRecovererspring-doc.cn

当 recoverer 使用最终异常时,该消息将被确认,并且不会由 broker 发送到死信交换(如果已配置)。spring-doc.cn

When 在消费者端使用时,收到的消息在 message 属性中具有。 在本例中为 。 这意味着 broker 上的交付模式。 从版本 2.0 开始,您可以配置消息中的 for to set 以重新发布(如果它是 )。 默认情况下,它使用默认值 - 。RepublishMessageRecovererdeliveryModereceivedDeliveryModedeliveryModenullNON_PERSISTENTRepublishMessageRecovererdeliveryModenullMessagePropertiesMessageDeliveryMode.PERSISTENT

以下示例演示如何将 a 设置为 recoverer:RepublishMessageRecovererspring-doc.cn

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

该 将在消息标头中发布带有其他信息的消息,例如异常消息、堆栈跟踪、原始交换和路由密钥。 可以通过创建 subclass 并覆盖 来添加其他标头。 (或任何其他属性)也可以在 中更改,如下例所示:RepublishMessageRecovereradditionalHeaders()deliveryModeadditionalHeaders()spring-doc.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 字节 ('headroom'),则它将被截断。 这可以通过设置 recoverer 的属性来调整,如果你需要更多或更少的空间来容纳其他 headers。 从版本 2.1.13、2.2.3 开始,异常消息包含在此计算中,并且将使用以下算法最大化堆栈跟踪量:frameMaxHeadroomspring-doc.cn

  • 如果单独的堆栈跟踪会超过限制,则异常消息标头将被截断为 97 字节加,并且堆栈跟踪也会被截断。…​spring-doc.cn

  • 如果堆栈跟踪很小,则消息将被截断(加号)以适应可用字节(但堆栈跟踪本身中的消息被截断为 97 字节加号)。…​…​spring-doc.cn

每当发生任何类型的截断时,都会记录原始异常以保留完整信息。 评估在增强标头后执行,以便可以在表达式中使用异常类型等信息。spring-doc.cn

从版本 2.4.8 开始,错误交换和路由密钥可以作为 SPEL 表达式提供,其中 是评估的根对象。Messagespring-doc.cn

从版本 2.3.3 开始,提供了一个新的子类;这支持两种样式的发布者确认,并将在返回之前等待确认(如果未确认或返回消息,则引发异常)。RepublishMessageRecovererWithConfirmsspring-doc.cn

如果确认类型为 ,则子类还将检测是否返回消息并抛出 ;如果发布被否定确认,它将抛出 .CORRELATEDAmqpMessageReturnedExceptionAmqpNackReceivedExceptionspring-doc.cn

如果 confirm type 为 ,则子类将在通道上调用该方法。SIMPLEwaitForConfirmsOrDiespring-doc.cn

请参阅 Publisher Confirms and Returns 以了解有关确认和返回的更多信息。spring-doc.cn

从版本 2.1 开始,添加了一个来抛出一个,它通知侦听器容器重新排队当前失败的消息。ImmediateRequeueMessageRecovererImmediateRequeueAmqpExceptionspring-doc.cn

Spring 重试的异常分类

Spring Retry 在确定哪些异常可以调用重试方面具有很大的灵活性。 默认配置将对所有异常重试。 鉴于用户异常被包装在 中,我们需要确保分类检查异常原因。 默认分类器仅查看顶级异常。ListenerExecutionFailedExceptionspring-doc.cn

从 Spring Retry 1.0.3 开始,它有一个名为 (default: ) 的属性。 当 时,它会遍历异常原因,直到找到匹配项或没有原因。BinaryExceptionClassifiertraverseCausesfalsetruespring-doc.cn

要使用此分类器进行重试,您可以使用带有构造函数的 created,该构造函数采用最大尝试次数、实例数和布尔值 () 并将此策略注入到 .SimpleRetryPolicyMapExceptiontraverseCausesRetryTemplatespring-doc.cn

4.1.22. 多个 Broker(或集群)支持

版本 2.3 在单个应用程序与多个代理或代理集群之间通信时增加了更多便利。 从使用者的角度来看,主要好处是基础设施可以自动将自动声明的队列与适当的代理相关联。spring-doc.cn

最好用一个例子来说明这一点:spring-doc.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 组基础设施(连接工厂、管理员、容器工厂)。 如前所述,可以定义要使用的容器工厂;在这种情况下,如果 Broker 不存在,他们还使用 which 导致在 broker 上声明队列。 通过使用约定命名 bean,基础结构能够确定哪个管理员应该声明队列。 这也将与 whereby also also also declare the exchange and binding 一起工作。 它不适用于 ,因为这期望队列已经存在。@RabbitListenerqueuesToDeclareRabbitAdmin<container-factory-name>-adminbindings = @QueueBinding(…​)queuesspring-doc.cn

在生产者端,提供了一个方便的类,以使 (参见 路由连接工厂) 的使用更简单。ConnectionFactoryContextWrapperRoutingConnectionFactoryspring-doc.cn

正如您在上面看到的,已经添加了一个带有 routing keys 和 的 bean。 还有一个使用该工厂的 that。 以下是使用该模板和包装器路由到其中一个代理集群的示例。SimpleRoutingConnectionFactoryonetwothreeRabbitTemplatespring-doc.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 提供了广泛的日志记录,尤其是在级别。DEBUGspring-doc.cn

如果您希望监控应用程序和代理之间的 AMQP 协议,您可以使用 WireShark 等工具,该工具具有用于解码协议的插件。 或者,RabbitMQ Java 客户端附带了一个非常有用的类,称为 . 默认情况下,当作为 运行时,它会侦听端口 5673 并连接到 localhost 上的端口 5672。 您可以运行它并更改连接工厂配置以连接到 localhost 上的端口 5673。 它在控制台上显示解码的协议。 有关更多信息,请参阅 Javadoc。TracermainTracerspring-doc.cn

4.2. 使用 RabbitMQ Stream 插件

版本 2.4 引入了对 RabbitMQ Stream Plugin 的 Java 客户端的初始支持。spring-doc.cn

将依赖项添加到您的项目中:spring-rabbit-streamspring-doc.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'

您可以像往常一样预置队列,使用 Bean 使用 该方法指定队列类型。 例如:RabbitAdminQueueBuilder.stream()spring-doc.cn

@Bean
Queue stream() {
    return QueueBuilder.durable("stream.queue1")
            .stream()
            .build();
}

但是,这仅在您还使用非流组件(例如 or )时才有效,因为在打开 AMQP 连接时,会触发 admin 声明定义的 bean。 如果您的应用程序仅使用流组件,或者您希望使用高级流配置功能,则应改为配置:SimpleMessageListenerContainerDirectMessageListenerContainerStreamAdminspring-doc.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 文档,以了解有关 .StreamCreatorspring-doc.cn

4.2.1. 发送消息

它提供了 (AMQP) 功能的子集。RabbitStreamTemplateRabbitTemplatespring-doc.cn

例 5.RabbitStream操作
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;

}

该实现具有以下构造函数和属性:RabbitStreamTemplatespring-doc.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) {
}

在方法中用于将对象转换为 Spring AMQP 。MessageConverterconvertAndSendMessagespring-doc.cn

这用于从 Spring AMQP 转换为 native stream 。StreamMessageConverterMessageMessagespring-doc.cn

您也可以直接发送本机流 s;使用该方法提供对 消息生成器的访问。MessagemessageBuilder()Producerspring-doc.cn

这提供了一种在构建 producer 之前对其进行自定义的机制。ProducerCustomizerspring-doc.cn

请参阅 Java 客户端文档,了解如何自定义 和 。EnvironmentProducerspring-doc.cn

从版本 3.0 开始,方法返回类型不是 .CompletableFutureListenableFuture

4.2.2. 接收消息

异步消息接收由 (以及使用 时) 提供。StreamListenerContainerStreamRabbitListenerContainerFactory@RabbitListenerspring-doc.cn

侦听器容器需要一个流名称以及一个流名称。Environmentspring-doc.cn

您可以使用经典接收 Spring AMQP ,也可以使用新接口接收本机流:MessageMessageListenerMessagespring-doc.cn

public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}

有关支持的属性的信息,请参阅 Message Listener Container Configurationspring-doc.cn

与模板类似,容器具有 property。ConsumerCustomizerspring-doc.cn

请参阅 Java 客户端文档,了解如何自定义 和 。EnvironmentConsumerspring-doc.cn

使用 时,请配置 ;此时,大多数属性 (等) 将被忽略。仅支持 、 和 。 此外,只能包含一个流名称。@RabbitListenerStreamRabbitListenerContainerFactory@RabbitListenerconcurrencyidqueuesautoStartupcontainerFactoryqueuesspring-doc.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 将属性添加到 (及其工厂) 中。 还提供了一个新的工厂 Bean 来创建无状态重试拦截器,该拦截器在使用原始流消息时具有可选的使用。adviceChainStreamListenerContainerStreamMessageRecovererspring-doc.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: truespring-doc.cn

供应

为方便起见,可以通过定义 type 为 的单个 bean 来供应 super 流。SuperStreamspring-doc.cn

@Bean
SuperStream superStream() {
    return new SuperStream("my.super.stream", 3);
}

检测到此 bean 并将声明 exchange () 和 3 个队列(分区) - 其中 是 、 、 、 绑定的路由键等于 。RabbitAdminmy.super.streammy.super-stream-nn012nspring-doc.cn

如果您还希望通过 AMQP 向 Exchange 发布信息,则可以提供自定义路由密钥:spring-doc.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.cn

使用 SuperStream 进行制作

您必须将 a 添加到 :superStreamRoutingFunctionRabbitStreamTemplatespring-doc.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;
}

您还可以使用 .RabbitTemplatespring-doc.cn

使用具有单个活跃使用者的 Super Streams

在侦听器容器上调用该方法,在超级流上启用单个活跃的 consumer。superStreamspring-doc.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 时,实际并发数由 ;要实现完全并发,请将环境设置为 1。 请参阅配置环境EnvironmentmaxConsumersByConnection

4.2.5. 千分尺观察

从版本 3.0.5 开始,现在支持对 和 流侦听器容器使用 Micrometer 进行观察。 该容器现在还支持 Micrometer 计时器(未启用观察时)。RabbitStreamTemplatespring-doc.cn

在每个组件上设置以启用观察;这将禁用 Micrometer Timers,因为计时器现在将随每个观测一起管理。 使用带注释的侦听器时,请在容器工厂上设置。observationEnabledobservationEnabledspring-doc.cn

有关更多信息,请参阅 Micrometer Tracingspring-doc.cn

要向计时器/跟踪添加标签,请分别配置自定义或模板或侦听器容器。RabbitStreamTemplateObservationConventionRabbitStreamListenerObservationConventionspring-doc.cn

默认实施会添加 template observations 的标签和 containers 的标签。namelistener.idspring-doc.cn

您可以子类化 or or 提供全新的实现。DefaultRabbitStreamTemplateObservationConventionDefaultStreamRabbitListenerObservationConventionspring-doc.cn

有关更多详细信息,请参阅千分尺观测文档spring-doc.cn

4.3. 记录子系统 AMQP Appender

该框架为一些流行的日志记录子系统提供了 logging appender:spring-doc.cn

appender 是使用 logging 子系统的正常机制配置的,可用属性在以下部分中指定。spring-doc.cn

4.3.1. 通用属性

所有 appenders 都提供以下属性:spring-doc.cn

表 4.常见的 Appender 属性
财产 违约 描述
 exchangeName
 logs

要将日志事件发布到的 Exchange 的名称。spring-doc.cn

 exchangeType
 topic

要将日志事件发布到的 exchange 的类型 — 仅当 appender 声明 exchange 时才需要。 看。declareExchangespring-doc.cn

 routingKeyPattern
 %c.%p

用于生成路由密钥的 Logging subsystem 模式格式。spring-doc.cn

 applicationId

应用程序 ID — 如果模式包含 ,则添加到路由密钥中。%X{applicationId}spring-doc.cn

 senderPoolSize
 2

用于发布日志事件的线程数。spring-doc.cn

 maxSenderRetries
 30

如果 Broker 不可用或出现其他错误,则重试发送消息的次数。 重试延迟如下:,其中 是重试编号。N ^ log(N)Nspring-doc.cn

 addresses

以逗号分隔的代理地址列表,格式如下:- overrides 和 .host:port[,host:port]*hostportspring-doc.cn

 host
 localhost

要连接到的 RabbitMQ 主机。spring-doc.cn

 port
 5672

要连接的 RabbitMQ 端口。spring-doc.cn

 virtualHost
 /

要连接到的 RabbitMQ 虚拟主机。spring-doc.cn

 username
 guest

RabbitMQ 用户。spring-doc.cn

 password
 guest

此用户的 RabbitMQ 密码。spring-doc.cn

 useSsl
 false

是否使用 SSL 进行 RabbitMQ 连接。 参见 RabbitConnectionFactoryBean 和 配置 SSLspring-doc.cn

 verifyHostname
 true

为 TLS 连接启用服务器主机名验证。 参见 RabbitConnectionFactoryBean 和 配置 SSLspring-doc.cn

 sslAlgorithm
 null

要使用的 SSL 算法。spring-doc.cn

 sslPropertiesLocation
 null

SSL 属性文件的位置。spring-doc.cn

 keyStore
 null

密钥库的位置。spring-doc.cn

 keyStorePassphrase
 null

密钥库的密码。spring-doc.cn

 keyStoreType
 JKS

密钥库类型。spring-doc.cn

 trustStore
 null

信任库的位置。spring-doc.cn

 trustStorePassphrase
 null

信任库的密码。spring-doc.cn

 trustStoreType
 JKS

信任库类型。spring-doc.cn

 saslConfig
 null (RabbitMQ client default applies)

的 - 请参阅 javadoc 以获取有效值。saslConfigRabbitUtils.stringToSaslConfigspring-doc.cn

 contentType
 text/plain

content-type日志消息的属性。spring-doc.cn

 contentEncoding

content-encoding日志消息的属性。spring-doc.cn

 declareExchange
 false

是否在此 appender 启动时声明配置的 exchange。 另请参阅 和 。durableautoDeletespring-doc.cn

 durable
 true

当 is 时,durable 标志设置为此值。declareExchangetruespring-doc.cn

 autoDelete
 false

当 is 时,自动删除标志设置为此值。declareExchangetruespring-doc.cn

 charset
 null

转换为 时要使用的字符集。 默认值:null(使用系统默认字符集)。 如果当前平台不支持该字符集,我们将回退到使用系统字符集。Stringbyte[]spring-doc.cn

 deliveryMode
 PERSISTENT

PERSISTENT或者确定 RabbitMQ 是否应该保留消息。NON_PERSISTENTspring-doc.cn

 generateId
 false

用于确定属性是否设置为唯一值。messageIdspring-doc.cn

 clientConnectionProperties
 null

用于 RabbitMQ 连接的自定义客户端属性的逗号分隔对列表。key:valuespring-doc.cn

 addMdcAsHeaders
 true

在引入此属性之前,MDC 属性始终添加到 RabbitMQ 消息头中。 这可能会导致大型 MDC 出现问题,因为 RabbitMQ 的所有标头的缓冲区大小有限,并且此缓冲区非常小。 引入此属性是为了避免在大型 MDC 的情况下出现问题。 默认情况下,此值设置为 for backward compatibility。 这将关闭将序列化 MDC 转换为标头。 请注意,默认情况下,会将 MDC 添加到消息中。truefalseJsonLayoutspring-doc.cn

4.3.2. Log4j 2 附加程序

以下示例显示如何配置 Log4j 2 appender:spring-doc.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 appender 将消息发布到调用线程上的 RabbitMQ。 这是因为默认情况下,Log4j 2 不会创建线程安全事件。 如果代理已关闭,则使用 重试,重试之间没有延迟。 如果要恢复以前在单独的线程 () 上发布消息的行为,可以将该属性设置为 。 但是,您还需要将 Log4j 2 配置为使用 而不是 . 一种方法是将 system 属性 . 如果将异步发布与 一起使用,则事件很可能会因串扰而损坏。maxSenderRetriessenderPoolSizeasynctrueDefaultLogEventFactoryReusableLogEventFactory-Dlog4j2.enable.threadlocals=falseReusableLogEventFactoryspring-doc.cn

4.3.3. Logback Appender

以下示例显示如何配置 logback appender:spring-doc.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 提供了一个选项,默认情况下。 提取调用者数据可能相当昂贵,因为 log event 必须创建一个 throwable 并检查它以确定调用位置。 因此,默认情况下,在将事件添加到事件队列时,不会提取与事件关联的调用方数据。 您可以通过将属性设置为 来配置 appender 以包含调用者数据。AmqpAppenderincludeCallerDatafalseincludeCallerDatatruespring-doc.cn

从版本 2.0.0 开始,Logback 支持带有 option 的 Logback 编码器。 和 选项是互斥的。AmqpAppenderencoderencoderlayoutspring-doc.cn

4.3.4. 自定义消息

默认情况下,AMQP 附加程序填充以下消息属性:spring-doc.cn

此外,它们还使用以下值填充标头:spring-doc.cn

每个 appender 都可以被子类化,从而允许您在发布之前修改消息。 以下示例显示如何自定义日志消息:spring-doc.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 可以使用 using 进行扩展,也可以扩展AmqpAppender@PluginBuilderFactoryAmqpAppender.Builderspring-doc.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.cn

简单字符串属性

每个 appender 都支持将 Client 端属性添加到 RabbitMQ 连接。spring-doc.cn

以下示例显示了如何为 logback 添加自定义客户端属性:spring-doc.cn

<appender name="AMQP" ...>
    ...
    <clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
    ...
</appender>
例 7.对数 4j2
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        ...
        clientConnectionProperties="thing1:thing2,cat:hat"
        ...
    </RabbitMQ>
</Appenders>

属性是以逗号分隔的对列表。 键和值不能包含逗号或冒号。key:valuespring-doc.cn

查看连接时,这些属性将显示在 RabbitMQ Admin UI 上。spring-doc.cn

高级 Logback 技术

您可以将 Logback appender 子类化。 这样,您就可以在建立连接之前修改客户端连接属性。 以下示例显示了如何执行此操作:spring-doc.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.cn

对于 String 属性(如前面的示例中所示的属性),可以使用前面的技术。 子类允许添加更丰富的属性(例如添加 or numeric 属性)。Mapspring-doc.cn

4.3.6. 提供自定义队列实现

使用 a 将日志记录事件异步发布到 RabbitMQ。 默认情况下,使用 a。 但是,您可以提供任何类型的自定义实现。AmqpAppendersBlockingQueueLinkedBlockingQueueBlockingQueuespring-doc.cn

以下示例显示了如何对 Logback 执行此操作:spring-doc.cn

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}

Log4j 2 附加程序支持使用 BlockingQueueFactory,如下例所示:spring-doc.cn

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>

4.4. 示例应用程序

Spring AMQP Samples 项目包括两个示例应用程序。 第一个是一个简单的 “Hello World” 示例,它演示了同步和异步消息接收。 它为了解基本组件提供了一个很好的起点。 第二个示例基于股票交易用例,用于演示实际应用程序中常见的交互类型。 在本章中,我们提供了每个示例的快速演练,以便您可以专注于最重要的组件。 这些示例都是基于 Maven 的,因此您应该能够将它们直接导入到任何 Maven 感知的 IDE(比如 SpringSource Tool Suite)中。spring-doc.cn

4.4.1. “Hello World” 示例

“Hello World” 示例演示了同步和异步消息接收。 您可以将样例导入到 IDE 中,然后按照下面的讨论进行操作。spring-rabbit-helloworldspring-doc.cn

同步示例

在目录中,导航到包。 打开该类,注意它包含类级别的注释,并注意方法级别的一些注释。 这是 Spring 基于 Java 的配置的一个例子。 您可以在此处阅读更多相关信息。src/main/javaorg.springframework.amqp.helloworldHelloWorldConfiguration@Configuration@Beanspring-doc.cn

下面的清单显示了如何创建 connection factory:spring-doc.cn

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

该配置还包含一个实例,默认情况下,该实例查找 exchange、queue 或 binding 类型的任何 bean,然后在代理上声明它们。 实际上,生成的 bean 是一个示例,因为它是 的实例。RabbitAdminhelloWorldQueueHelloWorldConfigurationQueuespring-doc.cn

下面的清单显示了 bean 定义:helloWorldQueuespring-doc.cn

@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}

回顾一下 bean 配置,你可以看到它的属性(用于接收消息)和属性(用于发送消息)的名称为 set。rabbitTemplatehelloWorldQueuequeueroutingKeyspring-doc.cn

现在我们已经探索了配置,我们可以查看实际使用这些组件的代码。 首先,从同一个包中打开该类。 它包含一个创建 Spring 的方法。Producermain()ApplicationContextspring-doc.cn

下面的清单显示了该方法:mainspring-doc.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");
}

在前面的示例中,检索 bean 并用于发送 . 由于客户端代码应尽可能依赖接口,因此类型为 而不是 . 即使 中创建的 bean 是 的实例,依赖该接口也意味着此代码更具可移植性(您可以独立于代码更改配置)。 由于调用了该方法,因此模板将委托给其实例。 在这种情况下,它使用默认的 ,但可以向 bean 提供不同的实现,如 中所定义。AmqpTemplateMessageAmqpTemplateRabbitTemplateHelloWorldConfigurationRabbitTemplateconvertAndSend()MessageConverterSimpleMessageConverterrabbitTemplateHelloWorldConfigurationspring-doc.cn

现在打开该类。 它实际上共享相同的配置基类,这意味着它共享 bean。 这就是为什么我们为该模板配置了 a (用于发送) 和 a (用于接收)。 正如我们在 AmqpTemplate 中描述的,你可以将 'routingKey' 参数传递给 send 方法,将 'queue' 参数传递给 receive 方法。 代码基本上是 Producer 的镜像,调用 而不是 .ConsumerrabbitTemplateroutingKeyqueueConsumerreceiveAndConvert()convertAndSend()spring-doc.cn

下面的清单显示了 的主要方法:Consumerspring-doc.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());
}

如果您运行 ,然后运行 ,您应该会在控制台输出中看到。ProducerConsumerReceived: Hello Worldspring-doc.cn

异步示例

同步示例演练了同步 Hello World 示例。 本节介绍一个稍微高级一些但功能更强大的选项。 通过一些修改,Hello World 示例可以提供异步接收(也称为消息驱动的 POJO)的示例。 事实上,有一个子包恰恰提供了:.org.springframework.amqp.samples.helloworld.asyncspring-doc.cn

同样,我们从发送方开始。 打开该类,请注意它会创建一个 and 一个 bean。 这一次,由于配置专用于消息发送端,我们甚至不需要任何队列定义,并且只设置了 'routingKey' 属性。 回想一下,消息是发送到 Exchange,而不是直接发送到队列。 AMQP 默认交换是没有名称的直接交换。 所有队列都绑定到该 default 交换,并将其名称作为路由键。 这就是为什么我们只需要在此处提供路由密钥。ProducerConfigurationconnectionFactoryrabbitTemplateRabbitTemplatespring-doc.cn

下面的清单显示了定义:rabbitTemplatespring-doc.cn

public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}

由于此示例演示了异步消息接收,因此生产方设计为连续发送消息(如果它是像同步版本一样的每次执行消息模型,那么它实际上是消息驱动的使用者就不会那么明显)。 负责持续发送消息的组件定义为 . 它配置为每 3 秒运行一次。ProducerConfigurationspring-doc.cn

下面的清单显示了该组件:spring-doc.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 任务调度支持,可以在此处了解更多信息。 简单的例子是,中的 bean 向调度程序注册任务。postProcessorProducerConfigurationspring-doc.cn

现在我们可以转向接收方。 为了强调消息驱动的 POJO 行为,我们从对消息做出反应的组件开始。 该类被调用,如下面的清单所示:HelloWorldHandlerspring-doc.cn

public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}

该类是 POJO。 它不扩展任何基类,不实现任何接口,甚至不包含任何导入。 它被 Spring AMQP “适应”到接口。 然后,您可以在 . 对于此示例,容器是在类中创建的。 你可以在那里看到 POJO 包装在适配器中。MessageListenerMessageListenerAdapterSimpleMessageListenerContainerConsumerConfigurationspring-doc.cn

下面的清单显示了如何定义:listenerContainerspring-doc.cn

@Bean
public SimpleMessageListenerContainer listenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueName(this.helloWorldQueueName);
    container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
    return container;
}

它是一个 Spring 生命周期组件,默认情况下,它会自动启动。 如果您查看该类,则可以看到它的方法只包含一个用于创建 . Producer 的方法也是一个单行引导程序,因为其方法被注释的组件也会自动启动。 您可以按任何顺序启动 and,您应该会看到每 3 秒发送和接收一次的消息。SimpleMessageListenerContainerConsumermain()ApplicationContextmain()@ScheduledProducerConsumerspring-doc.cn

4.4.2. 股票交易

Stock Trading 示例演示了比 Hello World 示例更高级的消息传送方案。 但是,配置非常相似,只是稍微复杂一些。 由于我们详细介绍了 Hello World 配置,因此,我们在这里重点介绍此示例的不同之处。 有一个服务器将市场数据(股票报价)推送到主题交易所。 然后,客户端可以通过将队列与路由模式(例如)绑定来订阅市场数据馈送。 此演示的另一个主要功能是由客户端发起并由服务器处理的请求-回复“股票交易”交互。 这涉及客户端在订单请求消息本身内发送的私有队列。app.stock.quotes.nasdaq.*replyTospring-doc.cn

服务器的核心配置位于 package 内的 class 中。 它扩展了 . 这是定义服务器和客户端通用资源的地方,包括市场数据主题交换(其名称为“app.stock.marketdata”)和服务器为股票交易公开的队列(其名称为“app.stock.request”)。 在该通用配置文件中,您还可以看到 a 在 .RabbitServerConfigurationorg.springframework.amqp.rabbit.stocks.config.serverAbstractStockAppRabbitConfigurationJackson2JsonMessageConverterRabbitTemplatespring-doc.cn

特定于服务器的配置由两部分组成。 首先,它在 上配置市场数据交换,以便它不需要在每次调用时提供该交换名称来发送 . 它在 base configuration 类中定义的抽象回调方法中执行此操作。 下面的清单显示了该方法:RabbitTemplateMessagespring-doc.cn

public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}

其次,声明股票请求队列。 在这种情况下,它不需要任何显式绑定,因为它绑定到默认的 no-name 交换,并将自己的名称作为路由键。 如前所述,AMQP 规范定义了该行为。 下面的清单显示了 bean 的定义:stockRequestQueuespring-doc.cn

@Bean
public Queue stockRequestQueue() {
    return new Queue(STOCK_REQUEST_QUEUE_NAME);
}

现在,您已经看到了服务器的 AMQP 资源的配置,请导航到目录下的包。 在那里,您可以看到提供方法的实际类。 它会根据配置文件创建一个 base. 在那里,您可以看到发布虚拟市场数据的计划任务。 该配置依赖于 Spring 的名称空间支持。 引导配置文件还导入了一些其他文件。 最有趣的是 ,它就在 下。 在那里,您可以看到负责处理股票交易请求的 bean。 最后,看看 中定义的 bean(也在 'src/main/resources' 中)。 该 bean 是该类的一个实例,并且是消息驱动的 POJO 的一个很好的示例,它也可以发送回复消息。 请注意,它本身并不与框架或任何 AMQP 概念耦合。 它接受 a 并返回 . 下面的清单显示了该方法的定义:org.springframework.amqp.rabbit.stockssrc/test/javaServermain()ApplicationContextserver-bootstrap.xmltaskserver-messaging.xmlsrc/main/resourcesmessageListenerContainerserverHandlerserver-handlers.xmlServerHandlerTradeRequestTradeResponsehandleMessagespring-doc.cn

public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}

现在我们已经看到了服务器最重要的配置和代码,我们可以转向客户端。 最好的起点可能是 包中的 。 请注意,它声明了两个队列,但没有提供显式名称。 下面的清单显示了两个队列的 bean 定义:RabbitClientConfigurationorg.springframework.amqp.rabbit.stocks.config.clientspring-doc.cn

@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}

这些是私有队列,唯一名称是自动生成的。 客户端使用第一个生成的队列绑定到服务器已公开的市场数据交换。 回想一下,在 AMQP 中,使用者与队列交互,而生产者与交换交互。 队列与交易所的 “绑定” 是告诉 broker 将消息从给定交易所投递 (或路由) 到队列。 由于市场数据交换是主题交换,因此可以使用路由模式来表示绑定。 它对对象执行此操作,该对象是使用 Fluent API 生成的。 以下清单显示了 :RabbitClientConfigurationBindingBindingBuilderBindingspring-doc.cn

@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;

@Bean
public Binding marketDataBinding() {
    return BindingBuilder.bind(
        marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}

请注意,实际值已在属性文件(下)中外部化,并且我们使用 Spring 的 Comments 来注入该值。 这通常是一个好主意。 否则,该值将在类中硬编码,并且无需重新编译即可不可修改。 在这种情况下,在更改用于绑定的路由模式的同时运行多个版本的客户端要容易得多。 我们现在可以试试。client.propertiessrc/main/resources@Valuespring-doc.cn

首先运行 ,然后 。 您应该会看到股票的虚拟报价,因为与 client.properties 中的 'stocks.quote.pattern' 键关联的当前值是 'app.stock.quotes.nasdaq.'. 现在,在保持现有 ServerClient 运行的同时,将该属性值更改为 'app.stock.quotes.nyse'。并启动第二个实例。 您应该看到,第一个客户仍然接收纳斯达克报价,而第二个客户接收纽约证券交易所报价。 相反,您可以更改模式以获取所有股票甚至单个股票代码。org.springframework.amqp.rabbit.stocks.Serverorg.springframework.amqp.rabbit.stocks.ClientNASDAQClientspring-doc.cn

我们探索的最后一个功能是从客户端的角度进行请求 - 回复交互。 回想一下,我们已经看到了 接受对象并返回对象的 。 侧面对应的代码在包里。 它委托给 为了发送消息。 下面的清单显示了该方法:ServerHandlerTradeRequestTradeResponseClientRabbitStockServiceGatewayorg.springframework.amqp.rabbit.stocks.gatewayRabbitTemplatesendspring-doc.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;
        }
    });
}

请注意,在发送消息之前,它会设置地址。 它提供由 Bean 定义生成的队列(如前所示)。 下面的清单显示了类本身的定义:replyTotraderJoeQueue@BeanStockServiceGatewayspring-doc.cn

@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}

如果您不再运行服务器和客户端,请立即启动它们。 尝试发送格式为 '100 TCKR' 的请求。 在模拟请求的 “处理” 的短暂人为延迟之后,您应该会看到客户端上出现一条确认消息。spring-doc.cn

4.4.3. 从非 Spring 应用程序接收 JSON

Spring 应用程序在发送 JSON 时,将 Header 设置为完全限定的类名,以帮助接收应用程序将 JSON 转换回 Java 对象。TypeIdspring-doc.cn

该示例探讨了从非 Spring 应用程序转换 JSON 的几种技术。spring-rabbit-jsonspring-doc.cn

4.5. 测试支持

为异步应用程序编写集成必然比测试更简单的应用程序更复杂。 当 annotation 等抽象出现时,情况会变得更加复杂。 问题是如何验证在发送消息后,侦听器是否按预期收到了消息。@RabbitListenerspring-doc.cn

框架本身有许多单元测试和集成测试。 一些使用模拟,而另一些则使用实时 RabbitMQ 代理的集成测试。 您可以查阅这些测试,了解测试方案的一些想法。spring-doc.cn

Spring AMQP 版本 1.6 引入了 jar,它为测试其中一些更复杂的场景提供了支持。 预计该项目将随着时间的推移而扩展,但我们需要社区反馈,以便为帮助测试所需的功能提出建议。 请使用 JIRAGitHub Issues 提供此类反馈。spring-rabbit-testspring-doc.cn

4.5.1. @SpringRabbitTest

使用此注释将基础结构 bean 添加到 Spring test 中。 使用时这不是必需的,例如,因为 Spring Boot 的自动配置将添加 bean。ApplicationContext@SpringBootTestspring-doc.cn

已注册的 Bean 包括:spring-doc.cn

  • CachingConnectionFactory (autoConnectionFactory).如果存在,则使用其 connection factory。@RabbitEnabledspring-doc.cn

  • RabbitTemplate (autoRabbitTemplate)spring-doc.cn

  • RabbitAdmin (autoRabbitAdmin)spring-doc.cn

  • RabbitListenerContainerFactory (autoContainerFactory)spring-doc.cn

此外,还添加了与 (to support ) 关联的 bean。@EnableRabbit@RabbitListenerspring-doc.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.cn

4.5.2. Mockito 实现Answer<?>

目前有两种 implementation 可以帮助进行测试。Answer<?>spring-doc.cn

第一个 , , 提供一个 返回 latch 并倒计时。 以下示例演示如何使用:LatchCountDownAndCallRealMethodAnswerAnswer<Void>nullLatchCountDownAndCallRealMethodAnswerspring-doc.cn

LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();

第二个选项提供了一种机制,可以选择性地调用真实方法,并提供一个机会 以根据 和 结果(如果有)返回自定义结果。LambdaAnswer<T>InvocationOnMockspring-doc.cn

考虑以下 POJO:spring-doc.cn

public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}

以下类测试 POJO:Thingspring-doc.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.cn

当与@RabbitListenerTestRabbitListenerTestHarness结合使用时,用于为侦听器获取正确构建的答案。harness.getLambdaAnswerFor("listenerId", true, …​)spring-doc.cn

4.5.3. 和@RabbitListenerTestRabbitListenerTestHarness

为其中一个类添加 注解会导致框架将 standard 替换为一个名为 (它还允许通过 ) 进行检测。@Configuration@RabbitListenerTestRabbitListenerAnnotationBeanPostProcessorRabbitListenerTestHarness@RabbitListener@EnableRabbitspring-doc.cn

这会以两种方式增强侦听器。 首先,它将侦听器包装在 中,从而启用正常的存根和验证操作。 它还可以向侦听器添加 an,从而允许访问参数、结果和引发的任何异常。 您可以通过 上的属性控制启用其中的哪些(或两者)。 后者用于访问有关调用的较低级别数据。 它还支持阻塞测试线程,直到异步侦听器被调用。RabbitListenerTestHarnessMockito SpyMockitoAdvice@RabbitListenerTestspring-doc.cn

final @RabbitListener方法不能被窥探或建议。 此外,只能侦测或建议具有属性的侦听器。id

请看看一些例子。spring-doc.cn

以下示例使用 spy:spring-doc.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 将 Harness 注入到测试用例中,以便我们可以访问 spy。
2 获取对 spy 的引用,以便我们可以验证它是否按预期调用。 由于这是一个发送和接收操作,因此无需暂停测试线程,因为它已经 暂停等待回复。RabbitTemplate
3 在这种情况下,我们只使用 send 操作,因此我们需要一个 latch 来等待对侦听器的异步调用 在容器线程上。 我们使用 Answer<?> 实现之一来帮助解决这个问题。 重要说明:由于侦听器的侦测方式,请务必使用 来获取 spy 的正确配置答案。harness.getLatchAnswerFor()
4 配置 spy 以调用 .Answer

以下示例使用 capture 建议:spring-doc.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 将 Harness 注入到测试用例中,以便我们可以访问 spy。
2 用于检索调用数据 - 在本例中,因为它是请求/回复 场景,无需等待任何时间,因为测试线程在等待中已暂停 以获得结果。harness.getNextInvocationDataFor()RabbitTemplate
3 然后,我们可以验证参数和结果是否符合预期。
4 这一次我们需要一些时间来等待数据,因为它是容器线程上的异步操作,我们需要 以暂停测试线程。
5 当侦听器引发异常时,它在调用数据的属性中可用。throwable
当将自定义 s 与 harness 一起使用时,为了正常运行,此类答案应子类化并从 harness () 获取实际的侦听器(不是间谍)并调用。 有关示例,请参阅提供的 Mockito Answer<?> Implementations 源代码。Answer<?>ForwardsInvocationgetDelegate("myListener")super.answer(invocation)

4.5.4. 使用TestRabbitTemplate

提供 是为了执行一些基本的集成测试,而无需代理。 当您将其添加为测试用例中的时,它会发现上下文中的所有侦听器容器,无论是声明为 OR 还是使用注释。 它目前仅支持按队列名称进行路由。 该模板从容器中提取消息侦听器,并直接在测试线程上调用它。 返回回复的侦听器支持请求-回复消息传递 ( methods)。TestRabbitTemplate@Bean@Bean<bean/>@RabbitListenersendAndReceivespring-doc.cn

以下测试用例使用模板:spring-doc.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 及更高版本提供了一个名为 的附加 jar。 此 jar 包含几个实用程序实例,用于运行 JUnit4 测试。 请参阅 JUnit5 条件 以了解 JUnit5 测试。spring-rabbit-junit@Rulespring-doc.cn

BrokerRunning

BrokerRunning提供了一种机制,允许测试在代理未运行时成功(默认情况下为 on , )。localhostspring-doc.cn

它还具有用于初始化和清空队列以及删除队列和交换的实用方法。spring-doc.cn

以下示例显示了其用法:spring-doc.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
}

有几种静态方法,例如 ,用于验证 broker 是否启用了管理插件。isRunning…​isBrokerAndManagementRunning()spring-doc.cn

配置规则

有时,如果没有代理,例如夜间 CI 构建,您希望测试失败。 要在运行时禁用规则,请将名为 的环境变量设置为 .RABBITMQ_SERVER_REQUIREDtruespring-doc.cn

您可以使用 setter 或环境变量覆盖代理属性,例如 hostname:spring-doc.cn

下面的示例演示如何使用 setter 覆盖属性:spring-doc.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.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 和管理 REST API)。localhost:5672localhost:15672/api/spring-doc.cn

更改主机名会影响 和 REST API 连接(除非显式设置了管理员 uri)。amqpmanagementspring-doc.cn

BrokerRunning还提供了一个名为 Method 的方法,用于传入包含这些变量的 Map。 它们会覆盖系统环境变量。 如果您希望对多个测试套件中的测试使用不同的配置,这可能很有用。 重要说明:在调用创建规则实例的任何静态方法之前,必须调用该方法。 变量值将应用于此调用后创建的所有实例。 调用 以重置规则以使用默认值(包括任何实际的环境变量)。staticsetEnvironmentVariableOverridesisRunning()clearEnvironmentVariableOverrides()spring-doc.cn

在测试用例中,您可以在创建连接工厂时使用 ; 返回规则的 RabbitMQ 。 以下示例显示了如何执行此操作:brokerRunninggetConnectionFactory()ConnectionFactoryspring-doc.cn

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
LongRunningIntegrationTest

LongRunningIntegrationTest是禁用长时间运行的测试的规则。 您可能希望在开发人员系统上使用它,但请确保在夜间 CI 构建等上禁用该规则。spring-doc.cn

以下示例显示了其用法:spring-doc.cn

@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

要在运行时禁用规则,请将名为 的环境变量设置为 .RUN_LONG_INTEGRATION_TESTStruespring-doc.cn

4.5.6. JUnit5 条件

版本 2.0.2 引入了对 JUnit5 的支持。spring-doc.cn

使用注释@RabbitAvailable

此类级注释类似于 JUnit4 @Rules中讨论的注释。 它由 处理。BrokerRunning@RuleRabbitAvailableConditionspring-doc.cn

该批注具有三个属性:spring-doc.cn

  • queues:在每次测试之前声明(和清除)并在所有测试完成时删除的队列数组。spring-doc.cn

  • management:如果您的测试还需要在代理上安装管理插件,请将其设置为。truespring-doc.cn

  • purgeAfterEach: (自版本 2.2 起) when (default) 时,将在测试之间清除。truequeuesspring-doc.cn

它用于检查 broker 是否可用,如果没有,则跳过测试。 如 配置规则中所述,如果没有代理,则名为 if 的环境变量会导致测试快速失败。 您可以使用环境变量配置条件,如 配置规则中所述。RABBITMQ_SERVER_REQUIREDtruespring-doc.cn

此外,还支持参数化测试构造函数和方法的参数解析。 支持两种参数类型:RabbitAvailableConditionspring-doc.cn

  • BrokerRunningSupport:实例(在 2.2 之前,这是一个 JUnit 4 实例)BrokerRunningspring-doc.cn

  • ConnectionFactory:实例的 RabbitMQ 连接工厂BrokerRunningSupportspring-doc.cn

以下示例显示了两者:spring-doc.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.cn

实际的用户测试可能如下所示:spring-doc.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.cn

从版本 2.2 开始,返回一个对象;以前,返回 JUnit 4 实例。 新类具有与 . 相同的 API 。getBrokerRunning()BrokerRunningSupportBrokerRunnningBrokerRunning

以下测试来自框架,演示了用法:spring-doc.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@Ruletruespring-doc.cn

@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}

默认情况下,变量为 ,但您可以在注释的属性中指定变量名称。RUN_LONG_INTEGRATION_TESTSvaluespring-doc.cn