4. 参考资料

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

4.1. 使用 Spring AMQP

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

4.1.1. AMQP 抽象

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

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

Message

0-9-1 AMQP 规范没有定义Message类或接口。 相反,当执行basicPublish(),内容作为字节数组参数传递,其他属性作为单独的参数传入。 Spring AMQP 定义了一个Message类作为更通用的 AMQP 域模型表示的一部分。 目的Messageclass 将 body 和 properties 封装在单个实例中,以便 API 反过来可以更简单。 以下示例显示了Message类定义:spring-doc.cadn.net.cn

public class Message {

    private final MessageProperties messageProperties;

    private final byte[] body;

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

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

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

MessagePropertiesinterface 定义了几个常见的属性,比如 'messageId'、'timestamp'、'contentType' 等等。 您还可以通过调用setHeader(String key, Object value)方法。spring-doc.cadn.net.cn

从版本开始1.5.7,1.6.11,1.7.42.0.0,如果消息正文是序列化的Serializablejava 对象,则在执行toString()作(例如在日志消息中)。 这是为了防止不安全的反序列化。 默认情况下,只有java.utiljava.lang类被反序列化。 要恢复到之前的行为,您可以通过调用Message.addAllowedListPatterns(…​). 一个简单的支持通配符,例如com.something., *.MyClass. 无法反序列化的主体由byte[<size>]在日志消息中。
交换

Exchangeinterface 表示 AMQP Exchange,这是 Message Producer 发送到的目标。 代理的虚拟主机中的每个 Exchange 都有一个唯一的名称以及一些其他属性。 以下示例显示了Exchange接口:spring-doc.cadn.net.cn

public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}

如您所见,Exchange还有一个 'type' 由 中定义的常量表示ExchangeTypes. 基本类型包括:direct,topic,fanoutheaders. 在 core 包中,您可以找到Exchange接口。 这些Exchange类型。 例如,Directexchange 允许队列由固定的路由键(通常是队列的名称)绑定。 一个TopicExchange 支持具有路由模式的绑定,这些模式可能包括分别表示“Exactly One”和“Zero-or-More”的“*”和“#”通配符。 这FanoutExchange 发布到绑定到它的所有队列,而不考虑任何路由键。 有关这些类型和其他 Exchange 类型的更多信息,请参阅其他资源spring-doc.cadn.net.cn

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

Queueclass 表示消息使用者从中接收消息的组件。 像各种Exchange类,我们的实现旨在成为这个核心 AMQP 类型的抽象表示。 下面的清单显示了Queue类:spring-doc.cadn.net.cn

public class Queue  {

    private final String name;

    private volatile boolean durable;

    private volatile boolean exclusive;

    private volatile boolean autoDelete;

    private volatile Map<String, Object> arguments;

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

    // Getters and Setters omitted for brevity

}

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

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

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

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

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

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

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

您可以将队列绑定到FanoutExchange没有路由密钥,如下例所示:spring-doc.cadn.net.cn

new Binding(someQueue, someFanoutExchange);

我们还提供BindingBuilder促进“Fluent API”样式,如下例所示:spring-doc.cadn.net.cn

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

就其本身而言,这个Bindingclass 仅保存有关 connection 的数据。 换句话说,它不是一个 “active” 组件。 但是,正如您稍后将在 配置 Broker 中看到的那样,AmqpAdmin类可以使用Binding实例来实际触发 broker 上的绑定作。 此外,正如您在同一部分中所看到的,您可以定义Binding实例使用 Spring 的@Beanannotations 中的@Configuration类。 还有一个方便的基类,它进一步简化了生成与 AMQP 相关的 bean 定义的方法,并识别队列、交换和绑定,以便在应用程序启动时在 AMQP 代理上声明它们。spring-doc.cadn.net.cn

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

4.1.2. 连接和资源管理

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

用于管理与 RabbitMQ 代理的连接的中心组件是ConnectionFactory接口。 责任ConnectionFactoryimplementation 是提供org.springframework.amqp.rabbit.connection.Connection,它是com.rabbitmq.client.Connection.spring-doc.cadn.net.cn

选择连接工厂

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

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

对于大多数使用案例,CachingConnectionFactory应该使用。 这ThreadChannelConnectionFactory如果您想确保严格的消息排序而无需使用 Scoped Operations,则可以使用。 这PooledChannelConnectionFactory类似于CachingConnectionFactory因为它使用单个连接和通道池。 它的实现更简单,但它不支持相关的发布者确认。spring-doc.cadn.net.cn

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

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

PooledChannelConnectionFactory

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

阿帕奇commons-pool2jar 必须位于 class path 上才能使用此工厂。spring-doc.cadn.net.cn

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

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

CachingConnectionFactory

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

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

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

从版本 1.5.5 开始,一个名为connectionLimit。 设置此属性后,它将限制允许的连接总数。 设置后,如果达到限制,则channelCheckoutTimeLimit用于等待连接变为空闲状态。 如果超过该时间,则AmqpTimeoutException被抛出。spring-doc.cadn.net.cn

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

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

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

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

从版本 1.4.2 开始,CachingConnectionFactory具有一个名为channelCheckoutTimeout. 当此属性大于零时,channelCacheSize成为可在连接上创建的通道数的限制。 如果达到限制,则调用线程会阻塞,直到通道可用或达到此超时,在这种情况下,会触发AmqpTimeoutException被抛出。spring-doc.cadn.net.cn

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

以下示例显示了如何创建新的connection:spring-doc.cadn.net.cn

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

Connection connection = connectionFactory.createConnection();

使用 XML 时,配置可能类似于以下示例:spring-doc.cadn.net.cn

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

一个ConnectionFactory可以使用 Rabbit 命名空间快速方便地创建,如下所示:spring-doc.cadn.net.cn

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

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

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

此外,使用命名空间,您可以添加 'channel-cache-size' 属性,如下所示:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

请参阅 连接到集群 以了解有关address-shuffle-mode.spring-doc.cadn.net.cn

以下示例中有一个自定义线程工厂,该工厂在线程名称前面加上rabbitmq-:spring-doc.cadn.net.cn

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

<bean id="tf" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
    <constructor-arg value="rabbitmq-" />
</bean>
AddressResolver 地址解析器

从版本 2.1.15 开始,您现在可以使用AddressResolver解析连接地址。 这将覆盖addresseshost/port性能。spring-doc.cadn.net.cn

命名连接

从版本 1.7 开始,ConnectionNameStrategy用于注入到AbstractionConnectionFactory. 生成的名称用于目标 RabbitMQ 连接的应用程序特定标识。 如果 RabbitMQ 服务器支持连接名称,则连接名称将显示在管理 UI 中。 此值不必是唯一的,并且不能用作连接标识符,例如,在 HTTP API 请求中。 此值应该是人类可读的,并且是ClientPropertiesconnection_name钥匙。 您可以使用简单的 Lambda,如下所示:spring-doc.cadn.net.cn

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

ConnectionFactoryargument 可以通过一些逻辑来区分目标连接名称。 默认情况下,beanNameAbstractConnectionFactory、表示对象的十六进制字符串和内部计数器用于生成connection_name. 这<rabbit:connection-factory>namespace 组件还随connection-name-strategy属性。spring-doc.cadn.net.cn

的实现SimplePropertyValueConnectionNameStrategy将连接名称设置为 Application 属性。 您可以将其声明为@Bean并将其注入到 Connection Factory 中,如下例所示:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

从版本 1.7.7 开始,AmqpResourceNotAvailableException,该SimpleConnection.createChannel()无法创建Channel(例如,由于channelMax达到 limit 并且缓存中没有可用的通道)。 您可以在RetryPolicy在一些回退后恢复作。spring-doc.cadn.net.cn

配置底层客户端连接工厂

CachingConnectionFactory使用 Rabbit 客户端的实例ConnectionFactory. 许多配置属性通过 (host,port,userName,password,requestedHeartBeatconnectionTimeout例如),在CachingConnectionFactory. 要设置其他属性 (clientProperties),您可以定义 Rabbit 工厂的实例,并使用CachingConnectionFactory. 使用命名空间时(如前所述),您需要在connection-factory属性。 为方便起见,提供了一个工厂 Bean 来帮助在 Spring 应用程序上下文中配置连接工厂,如下一节所述。spring-doc.cadn.net.cn

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

从版本 1.4 开始,一个方便的RabbitConnectionFactoryBean是为了通过使用依赖关系注入在底层客户端连接工厂上方便地配置 SSL 属性。 其他 setter 委托给底层工厂。 以前,您必须以编程方式配置 SSL 选项。 以下示例显示如何配置RabbitConnectionFactoryBean:spring-doc.cadn.net.cn

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

@Bean
CachingConnectionFactory connectionFactory(ConnectionFactory rabbitConnectionFactory) {
    CachingConnectionFactory ccf = new CachingConnectionFactory(rabbitConnectionFactory);
    ccf.setHost("...");
    // ...
    return ccf;
}
引导 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 文档。 省略keyStoretrustStore配置以通过 SSL 进行连接,而无需证书验证。 下一个示例显示如何提供密钥和信任存储配置。spring-doc.cadn.net.cn

sslPropertiesLocationproperty 是一个 SpringResource指向包含以下键的属性文件:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

从版本 2.3 开始,INORDER添加了 shuffle 模式,这意味着在创建连接后,第一个地址将移动到末尾。 您可能希望将此模式与 RabbitMQ 分片插件一起使用,其中CacheMode.CONNECTION以及合适的并发(如果您希望从所有节点上的所有分片中使用)。spring-doc.cadn.net.cn

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

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

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

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

    @Autowired
    private RabbitTemplate rabbitTemplate;

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

}

使用后取消绑定资源很重要。 有关更多信息,请参阅 JavaDocAbstractRoutingConnectionFactory.spring-doc.cadn.net.cn

从版本 1.4 开始,RabbitTemplate支持 SPELsendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpression属性,这些属性在每个 AMQP 协议交互作 (send,sendAndReceive,receivereceiveAndReply),解析为lookupKeyAbstractRoutingConnectionFactory. 您可以使用 Bean 引用,例如@vHostResolver.getVHost(#root)在表达式中。 为send作时,需要发送的消息是根 Evaluation 对象。 为receive作中,该queueName是根评估对象。spring-doc.cadn.net.cn

路由算法如下:如果 selector 表达式为nullor 的计算结果为null或提供的ConnectionFactory不是AbstractRoutingConnectionFactory,一切都像以前一样工作,依赖于提供的ConnectionFactory实现。 如果评估结果不是null,但没有目标ConnectionFactory为了那个lookupKeyAbstractRoutingConnectionFactory配置了lenientFallback = true. 在AbstractRoutingConnectionFactory,它会回退到其routing实现基于determineCurrentLookupKey(). 但是,如果lenientFallback = falseIllegalStateException被抛出。spring-doc.cadn.net.cn

命名空间支持还提供send-connection-factory-selector-expressionreceive-connection-factory-selector-expressionattributes 上的<rabbit:template>元件。spring-doc.cadn.net.cn

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

从版本 1.6.9 开始,您可以使用setLookupKeyQualifier在 Listener 容器上。 例如,这样做可以侦听具有相同名称但在不同虚拟主机中的队列(每个虚拟主机都有一个连接工厂)。spring-doc.cadn.net.cn

例如,使用 lookup key 限定符thing1以及一个监听 queue 的容器thing2,您可以注册目标连接工厂的查找键可以是thing1[thing2].spring-doc.cadn.net.cn

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

从版本 2.4.4 开始,可以禁用此验证。 如果您遇到 confirms 和 returns 之间的值需要不相等的情况,则可以使用AbstractRoutingConnectionFactory#setConsistentConfirmsReturns以关闭验证。 请注意,添加到AbstractRoutingConnectionFactory将确定confirmsreturns.spring-doc.cadn.net.cn

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

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

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

    PooledChannelConnectionFactory pooledChannelConnectionFactory = new PooledChannelConnectionFactory(cf);

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

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

    final RabbitTemplate rabbitTemplate = new RabbitTemplate(routingConnectionFactory);

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

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

Queue Affinity 和LocalizedQueueConnectionFactory

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

LocalizedQueueConnectionFactory是一个RoutingConnectionFactorySimpleMessageListenerContainer使用队列名称作为查找键,如上面的 Routing Connection Factory 中所述。spring-doc.cadn.net.cn

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

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

@Autowired
private ConfigurationProperties props;

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

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

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

从版本 3.0 开始,RabbitMQhttp-client不再用于访问 Rest API。 相反,默认情况下,WebClient如果spring-webflux位于 class 路径上;否则RestTemplate被使用。

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

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

您还可以通过实现LocalizedQueueConnectionFactory.NodeLocator并覆盖其createClient, ``restCall和可选的close方法。spring-doc.cadn.net.cn

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

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

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

});

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

发布者确认并返回

通过设置CachingConnectionFactory财产publisherConfirmTypeConfirmType.CORRELATEDpublisherReturnsproperty 设置为 'true'。spring-doc.cadn.net.cn

设置这些选项后,Channel工厂创建的实例包装在PublisherCallbackChannel,用于促进回调。 当获得这样的通道时,客户端可以注册一个PublisherCallbackChannel.Listener使用Channel. 这PublisherCallbackChannelimplementation 包含用于将 Confirm 或 return 路由到相应侦听器的 logic 。 这些功能将在以下各节中进一步说明。spring-doc.cadn.net.cn

另请参阅 相关发布者确认和返回simplePublisherConfirmsScoped Operations 中。spring-doc.cadn.net.cn

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

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

@FunctionalInterface
public interface ConnectionListener {

    void onCreate(Connection connection);

    default void onClose(Connection connection) {
    }

    default void onShutDown(ShutdownSignalException signal) {
    }

}

从版本 2.0 开始,org.springframework.amqp.rabbit.connection.ConnectionObject 可以随com.rabbitmq.client.BlockedListener要通知连接已阻止和未阻止事件的实例。 以下示例显示了 ChannelListener 接口定义:spring-doc.cadn.net.cn

@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}

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

记录通道关闭事件

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

CachingConnectionFactory使用默认策略来记录 Channel Closure,如下所示:spring-doc.cadn.net.cn

要修改此行为,您可以注入自定义ConditionalExceptionLoggerCachingConnectionFactory在其closeExceptionLogger财产。spring-doc.cadn.net.cn

运行时缓存属性

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

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

ConnectionNameStrategy.spring-doc.cadn.net.cn

channelCacheSize

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

localPort

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

idleChannelsTx

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

idleChannelsNotTx

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

idleChannelsTxHighWater

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

idleChannelsNotTxHighWater

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

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

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

ConnectionNameStrategy.spring-doc.cadn.net.cn

openConnections

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

channelCacheSize

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

connectionCacheSize

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

idleConnections

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

idleConnectionsHighWater

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

idleChannelsTx:<localPort>

此连接当前处于空闲 (缓存) 状态的事务通道数。 您可以使用localPort部分,以便与 RabbitMQ Admin UI 上的连接和通道相关联。spring-doc.cadn.net.cn

idleChannelsNotTx:<localPort>

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

idleChannelsTxHighWater:<localPort>

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

idleChannelsNotTxHighWater:<localPort>

非事务性通道的最大数量已同时空闲(缓存)。 您可以使用localPort部分,以便与 RabbitMQ Admin UI 上的连接和通道相关联。spring-doc.cadn.net.cn

cacheMode属性 (CHANNELCONNECTION) 也包括在内。spring-doc.cadn.net.cn

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

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

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

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

CachingConnectionFactory现在允许您访问底层 Connection Factory 以允许,例如, 设置自定义客户端属性。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

4.1.4.AmqpTemplate

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

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

添加重试功能

从版本 1.3 开始,您现在可以配置RabbitTemplate要使用RetryTemplate以帮助处理代理连接问题。 有关完整信息,请参见spring-retry项目。 下面只是一个使用指数回退策略和默认SimpleRetryPolicy,这会在将异常引发给调用方之前进行 3 次尝试。spring-doc.cadn.net.cn

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

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

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

以下示例使用@ConfigurationJava 中的注释:spring-doc.cadn.net.cn

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

从版本 1.4 开始,除了retryTemplate属性、recoveryCallback选项在RabbitTemplate. 它用作RetryTemplate.execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback).spring-doc.cadn.net.cn

RecoveryCallback在某种程度上受到限制,因为重试上下文仅包含lastThrowable田。 对于更复杂的使用案例,您应该使用外部RetryTemplate以便您可以将其他信息传达给RecoveryCallback通过上下文的属性。 以下示例显示了如何执行此作:
retryTemplate.execute(
    new RetryCallback<Object, Exception>() {

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

    }, new RecoveryCallback<Object>() {

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

在这种情况下,您不会注入RetryTemplateRabbitTemplate.spring-doc.cadn.net.cn

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

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

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

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

this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

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

});

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

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

相关发布者确认并返回

RabbitTemplate实现AmqpTemplate支持 Publisher Confirms 和 Returns。spring-doc.cadn.net.cn

对于返回的消息,模板的mandatoryproperty 必须设置为truemandatory-expression必须评估为true对于特定消息。 此功能需要一个CachingConnectionFactory它有它的publisherReturns属性设置为true(请参阅 发布者确认并返回)。 返回值通过注册一个RabbitTemplate.ReturnsCallback通过调用setReturnsCallback(ReturnsCallback callback). 回调必须实现以下方法:spring-doc.cadn.net.cn

void returnedMessage(ReturnedMessage returned);

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

只有一个ReturnsCallback由每个RabbitTemplate. 另请参阅 回复超时spring-doc.cadn.net.cn

对于发布者确认(也称为发布者确认),模板需要一个CachingConnectionFactory它有它的publisherConfirm属性设置为ConfirmType.CORRELATED. 确认通过注册RabbitTemplate.ConfirmCallback通过调用setConfirmCallback(ConfirmCallback callback). 回调必须实现此方法:spring-doc.cadn.net.cn

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

CorrelationData是客户端在发送原始消息时提供的对象。 这ack对于ack和 false 表示nack. 为nack实例中,原因可能包含nack,如果它在nack生成。 例如,当向不存在的 exchange 发送消息时。 在这种情况下,代理将关闭通道。 关闭的原因包含在cause. 这cause已在版本 1.4 中添加。spring-doc.cadn.net.cn

只有一个ConfirmCallbackRabbitTemplate.spring-doc.cadn.net.cn

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

CorrelationDataobject 具有CompletableFuture,而不是使用ConfirmCallback在模板上。 以下示例显示如何配置CorrelationData实例:spring-doc.cadn.net.cn

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

由于它是一个CompletableFuture<Confirm>,您可以执行以下任一作get()准备好或使用时的结果whenComplete()进行异步回调。 这Confirmobject 是具有 2 个属性的简单 bean:ackreason(对于nack实例)。 未为 broker-generated 填充原因nack实例。 它为nack实例(例如,在ack实例非常出色)。spring-doc.cadn.net.cn

此外,当同时启用 confirms 和 returns 时,CorrelationData return属性中填充返回的消息(如果无法将其路由到任何队列)。 可以保证在使用ack.CorrelationData.getReturn()返回ReturnMessage具有属性:spring-doc.cadn.net.cn

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

作用域作

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

从版本 2.0 开始,一个名为invoke提供OperationsCallback. 在回调范围内和提供的RabbitOperations参数使用相同的专用Channel,该 URL 将在最后关闭(不会返回到缓存中)。 如果通道是PublisherCallbackChannel,则在收到所有确认后,它会返回到缓存中(请参阅 相关发布者确认和返回)。spring-doc.cadn.net.cn

@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}

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

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

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

如果您愿意RabbitAdminOperationsCallback,则必须使用相同的RabbitTemplate用于invoke操作。spring-doc.cadn.net.cn

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

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

此外,RabbitTemplatepublisherSequenceNumber属性MessageProperties. 如果您想检查(或记录或以其他方式使用)特定的确认,您可以使用重载的invoke方法,如下例所示:spring-doc.cadn.net.cn

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

以下示例日志acknack实例:spring-doc.cadn.net.cn

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

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

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

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

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

@SpringBootApplication
public class Application {

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

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

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

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

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

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


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

}

@Component
class Service {

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

	private final RabbitTemplate template;

	private final TaskExecutor exec;

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

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

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

}

即使发布是在两个不同的线程上执行的,它们也将使用相同的通道,因为缓存的上限是单个通道。spring-doc.cadn.net.cn

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

@SpringBootApplication
public class Application {

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

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

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

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

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

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


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

}

@Component
class Service {

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

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	private final ThreadChannelConnectionFactory connFactory;

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

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

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

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

}
一旦prepareSwitchContext调用时,如果当前线程再执行任何作,则会在新 Channel 上执行。 当不再需要线程绑定通道时,关闭它很重要。
消息传递集成

从版本 1.4 开始,RabbitMessagingTemplate(建立在RabbitTemplate)提供了与 Spring Framework 消息传递抽象的集成——即org.springframework.messaging.Message. 这样,您就可以使用spring-messaging Message<?>抽象化。 其他 Spring 项目(例如 Spring 集成和 Spring 的 STOMP 支持)也使用这种抽象。 涉及两个消息转换器:一个用于在 spring-messaging 之间进行转换Message<?>和 Spring AMQP 的Messageabstraction 和一个用于在 Spring AMQP 的Messageabstraction 和底层 RabbitMQ 客户端库所需的格式。 默认情况下,消息有效负载由提供的RabbitTemplate实例的消息转换器。 或者,您可以注入自定义MessagingMessageConverter使用其他一些 payload converter,如下例所示:spring-doc.cadn.net.cn

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

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

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

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

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

第一个示例是 Literal 表达式。 第二个获取username属性。spring-doc.cadn.net.cn

使用单独的连接

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

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

4.1.5. 发送消息

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

void send(Message message) throws AmqpException;

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

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

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

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

您可以设置exchange属性(如果您计划在大部分或所有时间使用该 Template 实例发送到同一 Exchange)。 在这种情况下,您可以使用前面清单中的第二种方法。 以下示例在功能上等同于前面的示例:spring-doc.cadn.net.cn

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

如果exchangeroutingKeyproperties 的 API API 中,您可以使用仅接受Message. 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

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

或者,您可以创建一个模板,该模板可用于主要或专门发布到单个 Queue。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

从版本 1.3 开始,消息构建器 API 由MessageBuilderMessagePropertiesBuilder. 这些方法提供了一种方便的“Fluent”方法来创建消息或消息属性。 以下示例显示了 Fluent API 的运行情况:spring-doc.cadn.net.cn

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

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

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

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

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

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

public static MessageBuilder fromMessage(Message message) (4)

public static MessageBuilder fromClonedMessage(Message message) (5)
1 生成器创建的消息具有一个正文,该正文是对参数的直接引用。
2 生成器创建的消息具有一个 body,该 body 是一个新数组,其中包含参数中的 bytes 副本。
3 生成器创建的消息具有一个 body,该 body 是一个新数组,其中包含参数中的字节范围。 看Arrays.copyOfRange()了解更多详情。
4 生成器创建的消息具有一个 body,该 body 是对参数 body 的直接引用。 参数的属性将复制到新的MessageProperties对象。
5 生成器创建的消息具有一个 body,该 body 是一个包含参数 body 副本的新数组。 参数的属性将复制到新的MessageProperties对象。

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

public static MessagePropertiesBuilder newInstance() (1)

public static MessagePropertiesBuilder fromProperties(MessageProperties properties) (2)

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

使用RabbitTemplate实现AmqpTemplate,每个send()methods 具有一个重载版本,该版本需要一个额外的CorrelationData对象。 启用 publisher 确认后,此对象将在 中描述的回调中返回AmqpTemplate. 这允许发件人关联确认 (acknack) 替换为已发送的消息。spring-doc.cadn.net.cn

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

Message postProcessMessage(Message message, Correlation correlation);

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

同样从版本 1.6.7 开始,一个名为CorrelationDataPostProcessor。 毕竟 this 被调用MessagePostProcessor实例(在send()方法以及setBeforePublishPostProcessors()). 实现可以更新或替换send()方法(如果有)。 这Message和原始CorrelationData(如果有)作为参数提供。 以下示例演示如何使用postProcess方法:spring-doc.cadn.net.cn

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

当模板的mandatoryproperty 为true,则返回的消息由 中描述的回调提供AmqpTemplate.spring-doc.cadn.net.cn

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

发布者返回也可以由RabbitTemplate在 Send 和 Receive作中。 有关更多信息,请参阅 Reply Timeout (回复超时)。spring-doc.cadn.net.cn

配料

版本 1.4.2 引入了BatchingRabbitTemplate. 这是RabbitTemplate替换为send方法,该方法根据BatchingStrategy. 只有当批处理完成时,才会将消息发送到 RabbitMQ。 下面的清单显示了BatchingStrategy接口定义:spring-doc.cadn.net.cn

public interface BatchingStrategy {

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

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

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

一个SimpleBatchingStrategy。 它支持将消息发送到单个 exchange 或 routing key。 它具有以下属性:spring-doc.cadn.net.cn

SimpleBatchingStrategy通过在每条嵌入消息前面使用四字节二进制长度来格式化批处理。 这是通过设置springBatchFormatmessage 属性设置为lengthHeader4.spring-doc.cadn.net.cn

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

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

4.1.6. 接收消息

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

轮询消费者

AmqpTemplate本身可用于轮询Message接待。 默认情况下,如果没有可用的消息,null将立即返回。 没有阻塞。 从版本 1.5 开始,您可以设置receiveTimeout,以毫秒为单位,并且 receive 方法会阻塞长达该时间,等待消息。 小于零的值表示无限期阻止(或至少在与 broker 的连接丢失之前)。 版本 1.6 引入了receive允许在每次调用时传入超时的方法。spring-doc.cadn.net.cn

由于接收作会创建一个新的QueueingConsumer对于每条消息,此技术并不真正适用于高容量环境。 考虑使用异步使用者或receiveTimeout为零。

从版本 2.4.8 开始,当使用非零超时时,你可以指定传递给basicConsume将 Consumer 与 Channel 关联的方法。 例如:template.addConsumerArg("x-priority", 10).spring-doc.cadn.net.cn

有四个简单的receive方法可用。 与Exchange在发送端,有一个方法要求已设置默认队列属性 直接放在模板本身上,并且有一个在运行时接受 queue 参数的方法。 版本 1.6 引入了接受timeoutMillis覆盖receiveTimeout基于每个请求。 下面的清单显示了这四种方法的定义:spring-doc.cadn.net.cn

Message receive() throws AmqpException;

Message receive(String queueName) throws AmqpException;

Message receive(long timeoutMillis) throws AmqpException;

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

与发送消息一样,AmqpTemplate有一些方便的方法来接收 POJO,而不是Message实例,而实现提供了一种自定义MessageConverter用于创建Object返回: 下面的清单显示了这些方法:spring-doc.cadn.net.cn

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;

Object receiveAndConvert(long timeoutMillis) throws AmqpException;

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

从版本 2.0 开始,这些方法有一些变体,它们需要额外的ParameterizedTypeReference参数来转换复杂类型。 模板必须配置有SmartMessageConverter. 看MessageRabbitTemplate了解更多信息。spring-doc.cadn.net.cn

sendAndReceive方法,从版本 1.3 开始,AmqpTemplate具有多种便利性receiveAndReply同步接收、处理和回复消息的方法。 下面的清单显示了这些方法定义:spring-doc.cadn.net.cn

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

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

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

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

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

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

AmqpTemplateimplementation 负责receivereply阶段。 在大多数情况下,您应该只提供ReceiveAndReplyCallback为收到的消息执行一些业务逻辑,并根据需要构建 Reply 对象或 Message。 注意,一个ReceiveAndReplyCallback可能会返回null. 在这种情况下,不会发送任何回复,并且receiveAndReply其工作原理类似于receive方法。 这允许将同一队列用于混合消息,其中一些消息可能不需要回复。spring-doc.cadn.net.cn

仅当提供的回调不是ReceiveAndReplyMessageCallback,它提供原始消息交换协定。spring-doc.cadn.net.cn

ReplyToAddressCallback对于需要自定义逻辑来确定replyToaddress 并回复ReceiveAndReplyCallback. 默认情况下,replyTo请求消息中的信息用于路由回复。spring-doc.cadn.net.cn

下面的清单显示了基于 POJO 的接收和回复的示例:spring-doc.cadn.net.cn

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

                public Invoice handle(Order order) {
                        return processOrder(order);
                }
        });
if (received) {
        log.info("We received an order!");
}
异步使用者
Spring AMQP 还通过使用@RabbitListener注解,并提供一个开放的基础设施来以编程方式注册端点。 这是迄今为止设置 asynchronous consumer 的最便捷方法。 有关更多详细信息,请参阅 Annotation-driven Listener Endpoints

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

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

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

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

消息侦听器

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

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

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

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

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

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

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

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

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

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

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

从 2.0 版本开始,一个方便的FunctionalInterface已提供。 下面的清单显示了FunctionalInterface:spring-doc.cadn.net.cn

@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}

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

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

从版本 2.2 开始,buildListenerArguments(Object)已弃用,并且是新的buildListenerArguments(Object, Channel, Message)一个已经被引入。 新方法帮助 listener 获取ChannelMessage参数执行更多作,例如调用channel.basicReject(long, boolean)在 Manual acknowledge 模式下。 下面的清单显示了最基本的示例:spring-doc.cadn.net.cn

public class ExtendedListenerAdapter extends MessageListenerAdapter {

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

}

现在,您可以配置ExtendedListenerAdapterMessageListenerAdapter如果您需要接收 “频道” 和 “消息”。 listener 的参数应设置为buildListenerArguments(Object, Channel, Message)返回,如下面的 listener 示例所示:spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

前面的两个示例都创建了一个DirectMessageListenerContainer(请注意typeattribute — 它默认为simple).spring-doc.cadn.net.cn

或者,您可能更喜欢使用 Java 配置,它看起来类似于前面的代码片段:spring-doc.cadn.net.cn

@Configuration
public class ExampleAmqpConfiguration {

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

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

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

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

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

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

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

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

auto-delete队列

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

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

您还可以将条件声明(请参阅条件声明)与auto-startup="false"admin 将队列声明推迟到容器启动。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

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

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

在这种情况下,queue 和 exchange 由containerAdmin,它有auto-startup="false",以便在上下文初始化期间不声明元素。 此外,容器由于同样的原因未启动。 当容器稍后启动时,它会使用其对containerAdmin以声明元素。spring-doc.cadn.net.cn

批量消息

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

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

设置 container 属性consumerBatchEnabled以启用此功能。deBatchingEnabled还必须为 true,以便容器负责处理这两种类型的批处理。 实现BatchMessageListenerChannelAwareBatchMessageListener什么时候consumerBatchEnabled是真的。 从版本 2.2.7 开始,SimpleMessageListenerContainerDirectMessageListenerContainer可以将 Producer 创建的 Batch 拆分为List<Message>. 有关将此功能与 @RabbitListener 一起使用的信息,请参阅使用 Batching@RabbitListener.spring-doc.cadn.net.cn

消费者事件

每当侦听器出现 (消费者)经历某种失败。 活动ListenerContainerConsumerFailedEvent具有以下属性:spring-doc.cadn.net.cn

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

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

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

  • throwable:这Throwable那被抓住了。spring-doc.cadn.net.cn

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

系统范围的事件(例如连接失败)由所有使用者在以下情况下发布concurrentConsumers大于 1。

如果使用者失败是因为 (默认情况下,如果其队列被独占使用)以及发布事件,则WARN发出日志。 要更改此日志记录行为,请提供自定义ConditionalExceptionLoggerSimpleMessageListenerContainer实例的exclusiveConsumerExceptionLogger财产。 另请参阅记录通道关闭事件spring-doc.cadn.net.cn

致命错误始终记录在ERROR水平。 这是不可修改的。spring-doc.cadn.net.cn

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

消费者标签

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

public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}

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

注释驱动的侦听器端点

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

@Component
public class MyService {

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

}

前面示例的思路是,只要在名为myQueueprocessOrdermethod (在本例中,使用消息的有效负载)。spring-doc.cadn.net.cn

带注释的终端节点基础设施在后台为每个带注释的方法创建一个消息侦听器容器,方法是使用RabbitListenerContainerFactory.spring-doc.cadn.net.cn

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

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

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

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

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

}

在第一个示例中,队列myQueue自动声明 (durable) 与 Exchange 一起(如果需要), 并使用路由密钥绑定到 Exchange。 在第二个示例中,声明并绑定了一个匿名(独占、自动删除)队列;队列名称由框架使用Base64UrlNamingStrategy. 您不能使用此技术声明代理命名的队列;它们需要声明为 bean 定义;请参阅 容器和 Broker-Named 队列。 倍数QueueBinding条目,让侦听器侦听多个队列。 在第三个示例中,名称为 retrieved from 属性的队列my.queue如有必要,使用队列名称作为路由键来声明默认绑定到默认 Exchange。spring-doc.cadn.net.cn

从 2.0 版本开始,@Exchangeannotation 支持任何 Exchange 类型,包括 CUSTOM。 有关更多信息,请参阅 AMQP 概念spring-doc.cadn.net.cn

您可以使用 normal@Beandefinitions 来定义。spring-doc.cadn.net.cn

通知ignoreDeclarationExceptions在第一个例子中的 EXCHANGE 上。 例如,这允许绑定到可能具有不同设置的现有 Exchange(例如internal). 默认情况下,现有 Exchange 的属性必须匹配。spring-doc.cadn.net.cn

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

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

您还可以在@QueueBinding队列、交易所、 和 bindings,如下例所示:spring-doc.cadn.net.cn

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

请注意,x-message-ttl参数设置为 10 秒。 由于参数类型不是String,我们必须指定它的类型 —— 在这个例子中,Integer. 与所有此类声明一样,如果队列已存在,则参数必须与队列中的参数匹配。 对于标头交换,我们设置绑定参数以匹配具有thing1header 设置为somevalue和 这thing2header 必须与任何值一起存在。 这x-matchargument 表示必须同时满足这两个条件。spring-doc.cadn.net.cn

参数名称、值和类型可以是属性占位符 (${…​}) 或 SPEL 表达式 (#{…​}). 这name必须解析为String. 的表达式type必须解析为Class或类的完全限定名称。 这value必须解析为可由DefaultConversionService添加到类型(例如x-message-ttl在前面的示例中)。spring-doc.cadn.net.cn

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

元注释

有时,您可能希望对多个侦听器使用相同的配置。 要减少样板配置,您可以使用 meta-annotations 创建自己的侦听器注释。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

public class MetaListener {

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

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

}

在前面的示例中,由@MyAnonFanoutListenerannotation 绑定一个匿名的自动删除 queue 到 fanout 交换,metaFanout. 从版本 2.2.3 开始,@AliasFor支持允许覆盖元注释注释上的属性。 此外,用户注释现在可以@Repeatable,允许为一个方法创建多个容器。spring-doc.cadn.net.cn

@Component
static class MetaAnnotationTestBean {

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

}


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

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

}

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

    MyListener[] value();

}
启用 Listener Endpoint Annotations

要启用对@RabbitListenerannotations 中,您可以添加@EnableRabbit到你的@Configuration类。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
@EnableRabbit
public class AppConfig {

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

从 2.0 版本开始,一个DirectMessageListenerContainerFactory也可用。 它创建DirectMessageListenerContainer实例。spring-doc.cadn.net.cn

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

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

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

默认情况下,基础架构会查找名为rabbitListenerContainerFactory作为工厂用于创建消息侦听器容器的源。 在这种情况下,忽略 RabbitMQ 基础设施设置,processOrder方法的调用方式,核心轮询大小为 3 个线程,最大池大小为 10 个线程。spring-doc.cadn.net.cn

你可以自定义侦听器容器工厂以用于每个注解,或者你可以通过实现RabbitListenerConfigurer接口。 仅当注册了至少一个端点而没有特定的容器工厂时,才需要默认值。 请参阅 Javadoc 以获取完整的详细信息和示例。spring-doc.cadn.net.cn

容器工厂提供了用于添加MessagePostProcessor在接收消息之后 (调用侦听器之前) 和发送回复之前应用的实例。spring-doc.cadn.net.cn

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

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

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

如果您更喜欢 XML 配置,可以使用<rabbit:annotation-driven>元素。 任何带有 Comments 的 bean@RabbitListener被检测到。spring-doc.cadn.net.cn

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

<rabbit:annotation-driven/>

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

DirectMessageListenerContainer实例中,您可以使用类似于以下内容的 XML:spring-doc.cadn.net.cn

<rabbit:annotation-driven/>

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

从版本 2.0 开始,@RabbitListenerannotation 具有concurrency财产。 它支持 SpEL 表达式 (#{…​}) 和属性占位符 (${…​}). 其含义和允许的值取决于容器类型,如下所示:spring-doc.cadn.net.cn

  • 对于DirectMessageListenerContainer,该值必须是单个整数值,该值将consumersPerQueue属性。spring-doc.cadn.net.cn

  • 对于SimpleRabbitListenerContainer,该值可以是单个整数值,该值将concurrentConsumers属性,或者它可以具有m-n哪里mconcurrentConsumersproperty 和nmaxConcurrentConsumers财产。spring-doc.cadn.net.cn

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

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

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

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

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

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

默认的MessageConverter第一步是 Spring AMQPSimpleMessageConverter处理转换为Stringjava.io.Serializable对象。 所有其他byte[]. 在下面的讨论中,我们将其称为 “消息转换器”。spring-doc.cadn.net.cn

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

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

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

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

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

从版本 2.3 开始,您可以通过在messageConverter财产。spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    ...

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

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

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

    ...

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

从版本 2.3.7 开始,您可以添加自己的HandlerMethodArgumentResolver并解析自定义方法参数。 您只需实施RabbitListenerConfigurer和使用方法setCustomMethodArgumentResolvers()来自类RabbitListenerEndpointRegistrar.spring-doc.cadn.net.cn

@Configuration
class CustomRabbitConfig implements RabbitListenerConfigurer {

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

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

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

				}
			);
    }

}
编程终端节点注册

RabbitListenerEndpoint提供 Rabbit 终端节点的模型,并负责为该模型配置容器。 基础设施允许您以编程方式配置终端节点,以及RabbitListener注解。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

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

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

应该注意的是,您也可以跳过使用@RabbitListener一起,并通过RabbitListenerConfigurer.spring-doc.cadn.net.cn

带注释的端点方法签名

到目前为止,我们一直在注入一个简单的String但是它实际上可以有一个非常灵活的方法签名。 以下示例重写它以注入Order使用自定义标头:spring-doc.cadn.net.cn

@Component
public class MyService {

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

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

不是受支持类型之一的非带注释的元素(即Message,MessageProperties,Message<?>Channel) 与负载匹配。 您可以通过使用@Payload. 您还可以通过添加额外的@Valid.spring-doc.cadn.net.cn

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

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

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

例如,如果我们想确保我们的Order有效,我们可以用@Valid并配置必要的验证器,如下所示:spring-doc.cadn.net.cn

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

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

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setValidator(myValidator());
        return factory;
    }
}
@RabbitListener @Payload验证

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

@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
当将 Spring Boot 与验证Starters一起使用时,LocalValidatorFactoryBean是自动配置的:
@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    @Autowired
    private LocalValidatorFactoryBean validator;
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(this.validator);
    }
}
public static class ValidatedClass {
  @Max(10)
  private int bar;
  public int getBar() {
    return this.bar;
  }
  public void setBar(int bar) {
    this.bar = bar;
  }
}
@RabbitListener(id="validated", queues = "queue1", errorHandler = "validationErrorHandler",
      containerFactory = "jsonListenerContainerFactory")
public void validatedListener(@Payload @Valid ValidatedClass val) {
    ...
}
@Bean
public RabbitListenerErrorHandler validationErrorHandler() {
    return (m, e) -> {
        ...
    };
}
侦听多个队列

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

@Component
public class MyService {

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

}

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

@Component
public class MyService {

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

}

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

回复管理

现有的支持MessageListenerAdapteralready 允许您的方法具有非 void 返回类型。 在这种情况下,调用的结果将封装在发送到ReplyToAddress标头,或发送到侦听程序上配置的默认地址。 您可以使用@SendTo消息抽象的注释。spring-doc.cadn.net.cn

假设我们的processOrder方法现在应该返回一个OrderStatus,我们可以这样写来自动发送回复:spring-doc.cadn.net.cn

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

如果需要以独立于传输的方式设置其他 Headers,则可以返回Message相反,如下所示:spring-doc.cadn.net.cn

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

或者,您可以使用MessagePostProcessorbeforeSendReplyMessagePostProcessorsContainer Factory 属性来添加更多标头。 从版本 2.2.3 开始,被调用的 bean/方法在回复消息中可用,它可以在消息后处理器中使用,将信息传回给调用者:spring-doc.cadn.net.cn

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

从版本 2.2.5 开始,您可以配置ReplyPostProcessor在发送回复消息之前对其进行修改;它在correlationId标头已设置为匹配请求。spring-doc.cadn.net.cn

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

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

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

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

idparameter 是侦听器 ID。spring-doc.cadn.net.cn

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

@SendTovalue 被假定为回复exchangeroutingKey对,它跟在exchange/routingKey模式 其中,可以省略其中一个部分。 取值如下:spring-doc.cadn.net.cn

  • thing1/thing2:这replyToexchange 和routingKey.thing1/:这replyToexchange 和默认值 (空)routingKey.thing2/thing2:这replyTo routingKey和默认 (空) 交换。 或空的:/replyTodefault exchange 和默认的routingKey.spring-doc.cadn.net.cn

此外,您还可以使用@SendTo没有value属性。 这种情况等于空的sendTo模式。@SendTo仅在入站邮件没有replyToAddress财产。spring-doc.cadn.net.cn

从版本 1.5 开始,@SendTovalue 可以是 bean 初始化 SPEL 表达式,如以下示例所示:spring-doc.cadn.net.cn

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

表达式的计算结果必须为String,可以是简单的队列名称(发送到默认 exchange)或使用 表单exchange/routingKey如前面的示例所述。spring-doc.cadn.net.cn

#{…​}expression 在初始化期间计算一次。

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

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

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

SPEL 表达式的运行时性质用!{…​}分隔符。 评估上下文#rootobject 具有三个属性:spring-doc.cadn.net.cn

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

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

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

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

如果您使用的是复杂的消息转换器,例如ContentTypeDelegatingMessageConverter中,您可以通过设置replyContentType属性。 这允许转换器为回复选择合适的 delegate converter。spring-doc.cadn.net.cn

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

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

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

这些属性 (replyContentTypeconverterWinsContentType) 在返回类型为 Spring AMQP 时不适用Message或 Spring MessagingMessage<?>. 在第一种情况下,不涉及转换;只需将contentTypemessage 属性。 在第二种情况下,使用消息标头控制行为:spring-doc.cadn.net.cn

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

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

多方法侦听器

从版本 1.5.0 开始,您可以指定@RabbitListener注解。 与新的@RabbitHandler注解,这允许单个侦听器根据 传入消息的有效负载类型。 最好用一个例子来描述这一点:spring-doc.cadn.net.cn

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

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

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

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

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

}

在这种情况下,个人@RabbitHandler如果转换后的有效负载是Thing2一个CatHat. 您应该了解,系统必须能够根据负载类型识别唯一方法。 检查类型是否可分配给没有注释或带有@Payload注解。 请注意,相同的方法签名适用,如方法级别@RabbitListener (前面描述的)。spring-doc.cadn.net.cn

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

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

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

代理@RabbitListener和泛型

如果您的服务是要代理的(例如,在@Transactional),您应该记住一些注意事项 该接口具有泛型参数。 请考虑以下示例:spring-doc.cadn.net.cn

interface TxService<P> {

   String handle(P payload, String header);

}

static class TxServiceImpl implements TxService<Foo> {

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

}

使用通用接口和特定实现,您被迫切换到 CGLIB 目标类代理,因为接口的实际实现handlemethod 是一种桥接方法。 在事务管理的情况下,CGLIB 的使用是通过 注释选项:@EnableTransactionManagement(proxyTargetClass = true). 在这种情况下,必须在实现中的目标方法上声明所有 Comments,如下例所示:spring-doc.cadn.net.cn

static class TxServiceImpl implements TxService<Foo> {

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

}
处理异常

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

从版本 2.0 开始,@RabbitListenerannotation 具有两个新属性:errorHandlerreturnExceptions.spring-doc.cadn.net.cn

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

您可以使用errorHandler要提供RabbitListenerErrorHandler实现。 该功能接口有一种方法,如下所示:spring-doc.cadn.net.cn

@FunctionalInterface
public interface RabbitListenerErrorHandler {

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

}

如您所见,您可以访问从容器接收的原始消息 spring-messagingMessage<?>对象,以及侦听器抛出的异常(包装在ListenerExecutionFailedException). 错误处理程序可以返回一些结果(作为回复发送)或引发原始异常或新异常(引发到容器或返回给发送者,具体取决于returnExceptions设置)。spring-doc.cadn.net.cn

returnExceptions属性,当true,将导致异常返回给发件人。 异常包装在RemoteInvocationResult对象。 在发送方,有一个可用的RemoteInvocationAwareMessageConverterAdapter,如果将其配置为RabbitTemplate重新引发服务器端异常,包装在AmqpRemoteException. 服务器异常的堆栈跟踪是通过合并服务器和客户端堆栈跟踪来合成的。spring-doc.cadn.net.cn

此机制通常仅适用于默认的SimpleMessageConverter,它使用 Java 序列化。 异常通常不是“Jackson 友好”的,并且不能序列化为 JSON。 如果您使用 JSON,请考虑使用errorHandler返回一些其他对 Jackson 友好的Errorobject 的 intent 值。
在版本 2.1 中,此接口从 packageo.s.amqp.rabbit.listenero.s.amqp.rabbit.listener.api.

从版本 2.1.7 开始,Channel在消息报头中可用;这允许您在使用AcknowledgeMode.MANUAL:spring-doc.cadn.net.cn

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

从版本 2.2.18 开始,如果抛出消息转换异常,将调用错误处理程序,并使用nullmessage论点。 这允许应用程序向调用者发送一些结果,指示收到了格式错误的消息。 以前,此类错误由容器引发和处理。spring-doc.cadn.net.cn

容器管理

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

您还可以使用其idgetListenerContainer(String id)— 代表 例registry.getListenerContainer("multi")对于由上述代码段创建的容器。spring-doc.cadn.net.cn

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

从版本 1.5 开始,您现在可以为group拖动到RabbitListener端点。 这提供了一种机制来获取对容器子集的引用。 添加group属性导致Collection<MessageListenerContainer>以组名称注册到上下文中。spring-doc.cadn.net.cn

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

使用 Batching 进行@RabbitListener

当收到一批消息时,取消批处理通常由容器执行,并且一次使用一条消息调用侦听器。 从版本 2.2 开始,你可以配置监听器容器工厂和监听器在一次调用中接收整个批处理,只需将工厂的batchListener属性,并将方法有效负载参数设为ListCollection:spring-doc.cadn.net.cn

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

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

// or

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

设置batchListener属性设置为 true 会自动关闭deBatchingEnabledcontainer 属性(除非consumerBatchEnabledtrue- 见下文)。实际上,取消批处理从容器移动到侦听器适配器,并且适配器会创建传递给侦听器的列表。spring-doc.cadn.net.cn

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

同样从 2.2 版本开始。当一次接收一条批处理消息时,最后一条消息包含一个布尔标头,该标头设置为true. 可以通过添加@Header(AmqpHeaders.LAST_IN_BATCH)boolean last' 参数添加到侦听器方法中。 标头从MessageProperties.isLastInBatch(). 另外AmqpHeaders.BATCH_SIZE填充每个消息片段中批次的大小。spring-doc.cadn.net.cn

此外,还有一个新属性consumerBatchEnabled已添加到SimpleMessageListenerContainer. 如果为 true,容器将创建一批消息,最多batchSize;如果满足以下条件,则交付部分批次receiveTimeout已过,没有新消息到达。 如果收到生产者创建的批处理,则会对其进行 Debatch 并将其添加到使用者端批处理中;因此,实际传送的消息数可能会超过batchSize,表示从 Broker 接收的消息数。deBatchingEnabled必须为 true,当consumerBatchEnabled是真的;Container Factory 将强制执行此要求。spring-doc.cadn.net.cn

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

使用consumerBatchEnabled@RabbitListener:spring-doc.cadn.net.cn

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

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

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

  • 第二个使用org.springframework.messaging.Message<?>替换为转换后的有效负载和映射的标头/属性。spring-doc.cadn.net.cn

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

您还可以添加Channel参数,通常在使用MANUALACK 模式。 这对于第三个示例不是很有用,因为您无权访问delivery_tag财产。spring-doc.cadn.net.cn

Spring Boot 为consumerBatchEnabledbatchSize,但不适用于batchListener. 从版本 3.0 开始,将consumerBatchEnabledtrue在 Container Factory 上还设置batchListenertrue. 什么时候consumerBatchEnabledtrue,则侦听器必须是 Batch 侦听器。spring-doc.cadn.net.cn

从版本 3.0 开始,侦听器方法可以消耗Collection<?>List<?>.spring-doc.cadn.net.cn

使用容器工厂

引入侦听器容器工厂是为了支持@RabbitListener并使用RabbitListenerEndpointRegistry,如 编程端点注册.spring-doc.cadn.net.cn

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

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

以下示例演示如何使用SimpleRabbitListenerEndpoint要创建侦听器容器,请执行以下作:spring-doc.cadn.net.cn

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

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

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

在任何一种情况下,侦听器也可以是ChannelAwareMessageListener,因为它现在是MessageListener.spring-doc.cadn.net.cn

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

以这种方式创建的容器是正常的@Bean实例,并且未在RabbitListenerEndpointRegistry.
异步@RabbitListener返回类型

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

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

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

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

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

线程和异步使用者

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

来自TaskExecutorSimpleMessageListenerContainer用于调用MessageListener当新邮件由RabbitMQ Client. 如果未配置,则SimpleAsyncTaskExecutor被使用。 如果您使用池化执行程序,则需要确保池大小足以处理配置的并发。 使用DirectMessageListenerContainerMessageListener直接在RabbitMQ Client线。 在这种情况下,taskExecutor用于监视使用者的任务。spring-doc.cadn.net.cn

当使用默认的SimpleAsyncTaskExecutor中,对于调用侦听器的线程,侦听器容器beanName用于threadNamePrefix. 这对于日志分析非常有用。 我们通常建议始终在 logging appender 配置中包含线程名称。 当TaskExecutor具体通过taskExecutor属性,则按原样使用,无需修改。 建议您使用类似的技术来命名由自定义TaskExecutorbean 定义,以帮助在日志消息中识别线程。

ExecutorCachingConnectionFactory传递到RabbitMQ Client创建连接时,其线程用于将新消息传送到侦听器容器。 如果未配置,则客户端使用内部线程池执行器,其池大小(在编写时)为Runtime.getRuntime().availableProcessors() * 2对于每个连接。spring-doc.cadn.net.cn

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

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

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

选择容器

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

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

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

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

  • maxConcurrentConsumers和使用者扩展间隔或触发器 — DMLC 中没有自动扩展。 但是,它确实允许您以编程方式更改consumersPerQueueproperty 和消费者进行相应的调整。spring-doc.cadn.net.cn

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

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

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

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

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

检测空闲的异步使用者

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

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

要配置此功能,请将idleEventInterval在容器上。 以下示例显示了如何在 XML 和 Java 中执行此作(对于SimpleMessageListenerContainer以及SimpleRabbitListenerContainerFactory):spring-doc.cadn.net.cn

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

在上述每种情况下,当容器处于空闲状态时,每分钟发布一次事件。spring-doc.cadn.net.cn

事件消耗

您可以通过实现ApplicationListener— 要么是普通听众,要么缩小到只有 接收此特定事件。 您还可以使用@EventListener,引入于 Spring Framework 4.2 中。spring-doc.cadn.net.cn

以下示例将@RabbitListener@EventListener转换为单个类。 您需要了解应用程序侦听器获取所有容器的事件,因此您可能需要 如果要根据哪个容器处于空闲状态执行特定作,请检查侦听器 ID。 您还可以使用@EventListener condition为此目的。spring-doc.cadn.net.cn

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

以下示例演示如何使用@RabbitListener@EventListener附注:spring-doc.cadn.net.cn

public class Listener {

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

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

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

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

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

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

您可以使用micrometerTagscontainer 属性。spring-doc.cadn.net.cn

千分尺观察

从 3.0 版本开始,现在支持使用 Micrometer 进行观察,因为RabbitTemplate和侦听器容器。spring-doc.cadn.net.cn

设置observationEnabled在每个组件上以便于观察;这将禁用 Micrometer Timers,因为计时器现在将随每个观测一起管理。 使用带注解的侦听器时,将observationEnabled在集装箱工厂。spring-doc.cadn.net.cn

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

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

默认实现会添加name标签,以及listener.id标记。spring-doc.cadn.net.cn

你可以子类化DefaultRabbitTemplateObservationConventionDefaultRabbitListenerObservationConvention或提供全新的实现。spring-doc.cadn.net.cn

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

4.1.7. 容器和 Broker-Named 队列

虽然最好使用AnonymousQueue实例作为自动删除队列,从版本 2.1 开始,您可以将名为 Queues 的代理与侦听器容器一起使用。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

请注意空的String对于名称。 当RabbitAdmin声明队列,它会更新Queue.actualName属性替换为 broker 返回的名称。 您必须使用setQueues()当您配置容器以使其正常工作时,以便容器可以在运行时访问声明的名称。 仅仅设置名称是不够的。spring-doc.cadn.net.cn

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

4.1.8. 消息转换器

AmqpTemplate还定义了几种发送和接收委托给MessageConverter. 这MessageConverter为每个方向提供一种方法:一种用于转换为Message另一个用于Message. 请注意,当转换为Message中,除了对象之外,您还可以提供属性。 这object参数通常对应于 Message 正文。 下面的清单显示了MessageConverter接口定义:spring-doc.cadn.net.cn

public interface MessageConverter {

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

    Object fromMessage(Message message) throws MessageConversionException;

}

相关的Message-sending 方法。AmqpTemplate比我们之前讨论的方法更简单,因为它们不需要Message实例。 相反,MessageConverter负责“创建”每个Message通过将提供的对象转换为 Byte 数组Messagebody 的 ID 中,然后添加任何提供的MessageProperties. 下面的清单显示了各种方法的定义:spring-doc.cadn.net.cn

void convertAndSend(Object message) throws AmqpException;

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

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

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

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

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

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

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;
MessageListenerAdapter异步 Consumer 中提到的MessageConverter.
SimpleMessageConverter

默认实现MessageConverter策略称为SimpleMessageConverter. 这是RabbitTemplate如果未显式配置替代项。 它处理基于文本的内容、序列化的 Java 对象和字节数组。spring-doc.cadn.net.cn

Message

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

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

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

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

转换为Message

转换为Message从任意 Java 对象中,SimpleMessageConverter同样处理字节数组、字符串和可序列化实例。 它将每个 Cookie 转换为 bytes (在 byte 数组的情况下,没有要转换的内容),并相应地设置 content-type 属性。 如果Object要转换的 URL 与其中一种类型不匹配,则Messagebody 为 null。spring-doc.cadn.net.cn

SerializerMessageConverter

此转换器类似于SimpleMessageConverter除了它可以与其他 Spring Framework 一起配置SerializerDeserializer的 implementationsapplication/x-java-serialized-object转换。spring-doc.cadn.net.cn

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

Jackson2JsonMessage转换器

本节介绍如何使用Jackson2JsonMessageConverterMessage. 它包含以下部分:spring-doc.cadn.net.cn

转换为Message

如上一节所述,通常不建议依赖 Java 序列化。 JSON 是一种相当常见的替代方案,它在不同语言和平台之间更加灵活和可移植 (JavaScript 对象表示法)。 转换器可以在任何RabbitTemplate实例来覆盖其对SimpleMessageConverter违约。 这Jackson2JsonMessageConverter使用com.fasterxml.jackson2.x 库。 以下示例将Jackson2JsonMessageConverter:spring-doc.cadn.net.cn

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

如上所示,Jackson2JsonMessageConverter使用DefaultClassMapper默认情况下。 类型信息被添加到 (和检索)MessageProperties. 如果入站消息不包含MessageProperties,但您知道预期的类型,则 可以使用defaultType属性,如下例所示:spring-doc.cadn.net.cn

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

此外,您还可以根据TypeId页眉。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

现在,如果发送系统将标头设置为thing1,则转换器会创建一个Thing1object 等。 有关从非 Spring 应用程序转换消息的完整讨论,请参阅从非 Spring 应用程序接收 JSON 示例应用程序。spring-doc.cadn.net.cn

从版本 2.4.3 开始,转换器不会添加contentEncodingmessage 属性(如果supportedMediaType具有charset参数;这也用于编码。 一种新方法setSupportedMediaType已添加:spring-doc.cadn.net.cn

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

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

从版本 2.4.3 开始,如果没有contentEncodingmessage 属性,则转换器将尝试检测charset参数中的contentTypemessage 属性并使用它。 如果两者都不存在,则如果supportedMediaType具有charset参数,它将用于解码,并最终回退到defaultCharset财产。 一种新方法setSupportedMediaType已添加:spring-doc.cadn.net.cn

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

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

此外,从版本 1.6 开始,当您使用@RabbitListener注解(在方法上),推断的类型信息将添加到MessageProperties. 这允许转换器转换为目标方法的参数类型。 这仅适用于一个没有注释的参数或单个参数具有@Payload注解。 类型的参数Message在分析过程中被忽略。spring-doc.cadn.net.cn

默认情况下,推断的类型信息将覆盖入站TypeId和创建的相关标头 通过发送系统。 这允许接收系统自动转换为不同的域对象。 仅当参数类型是具体的(不是抽象或接口)或来自java.util包。 在所有其他情况下,TypeId并使用 related headers。 在某些情况下,您可能希望覆盖默认行为并始终使用TypeId信息。 例如,假设您有一个@RabbitListener这需要Thing1参数,但消息包含Thing2那 是Thing1(这是具体的)。 推断的类型将不正确。 要处理这种情况,请将TypePrecedence属性Jackson2JsonMessageConverterTYPE_ID相反 的默认值INFERRED. (该属性实际上位于转换器的DefaultJackson2JavaTypeMapper,但转换器上提供了 setter 为方便起见。 如果你注入了一个自定义类型 mapper,你应该在 mapper 上设置 property 。
Message、传入的MessageProperties.getContentType()必须符合 JSON 标准 (contentType.contains("json")用于检查)。 从版本 2.2 开始,application/json如果没有contentType属性,或者它具有默认值application/octet-stream. 要恢复到之前的行为(返回未转换的byte[]),将转换器的assumeSupportedContentTypeproperty 设置为false. 如果内容类型不受支持,则WARN日志消息Could not convert incoming message with content-type […​],并且message.getBody()按原样返回 — 作为byte[]. 因此,为了满足Jackson2JsonMessageConverter要求,生产者必须添加contentTypemessage 属性 — 例如,作为application/jsontext/x-json或使用Jackson2JsonMessageConverter,它会自动设置标题。 下面的清单显示了许多 converter 调用:
@RabbitListener
public void thing1(Thing1 thing1) {...}

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

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

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

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

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

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

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

只有当@RabbitListener注解在方法级别声明。 具有类级别@RabbitListener,则转换后的类型用于选择哪个@RabbitHandler方法调用。 因此,基础设施提供了targetObjectmessage 属性,您可以在自定义 converter 来确定类型。
从版本 1.6.11 开始,Jackson2JsonMessageConverter因此,DefaultJackson2JavaTypeMapper (DefaultClassMapper) 提供trustedPackages选项来克服序列化小工具漏洞。 默认情况下,为了向后兼容,Jackson2JsonMessageConverter信任所有软件包 — 即,它用于 option。*

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

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

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

要启用此功能,请将setNullAsOptionalEmptytrue;什么时候false(默认),则转换器将回退到原始消息正文 (byte[]).spring-doc.cadn.net.cn

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

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

从版本 2.2.8 开始,默认情况下保留之前的行为。如果你有这样的自定义ObjectMapper并且您希望忽略 type headers,并始终使用推断的类型进行转换,请将alwaysConvertToInferredTypetrue. 这是为了实现向后兼容性并避免在转换失败时尝试转换的开销(使用标准的ObjectMapper).spring-doc.cadn.net.cn

使用 Spring Data Projection 接口

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

interface SomeSample {

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

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

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

要启用此功能,请将useProjectionForInterfacestrue在消息转换器上。 您还必须添加spring-data:spring-data-commonscom.jayway.jsonpath:json-path添加到 class 路径。spring-doc.cadn.net.cn

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

MessageRabbitTemplate

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

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

另一个选项是MarshallingMessageConverter. 它将 Spring OXM 库的MarshallerUnmarshaller策略接口。 您可以在此处阅读有关该库的更多信息。 在配置方面,最常见的是只提供 constructor 参数,因为大多数Marshaller还实施Unmarshaller. 以下示例显示如何配置MarshallingMessageConverter:spring-doc.cadn.net.cn

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

此类是在版本 2.1 中引入的,可用于将消息与 XML 相互转换。spring-doc.cadn.net.cn

Jackson2XmlMessageConverterJackson2JsonMessageConverter具有相同的基类:AbstractJackson2MessageConverter.spring-doc.cadn.net.cn

AbstractJackson2MessageConverterclass 来替换已删除的 class:AbstractJsonMessageConverter.

Jackson2XmlMessageConverter使用com.fasterxml.jackson2.x 库。spring-doc.cadn.net.cn

您可以按照与Jackson2JsonMessageConverter,但它支持 XML 而不是 JSON。 以下示例将Jackson2JsonMessageConverter:spring-doc.cadn.net.cn

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

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

从版本 2.2 开始,application/xml如果没有contentType属性,或者它具有默认值application/octet-stream. 要恢复到之前的行为(返回未转换的byte[]),将转换器的assumeSupportedContentTypeproperty 设置为false.
ContentTypeDelegatingMessageConverter

这个类是在版本 1.4.2 中引入的,它允许将MessageConverter基于MessageProperties. 默认情况下,它会委托给SimpleMessageConverter如果没有contentTypeproperty 或存在与任何已配置的转换器都不匹配的值。 以下示例将ContentTypeDelegatingMessageConverter:spring-doc.cadn.net.cn

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

本节介绍如何反序列化 Java 对象。spring-doc.cadn.net.cn

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

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

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

您可以设置模式列表,例如thing1.,thing1.thing2.Cat.MySafeClass.spring-doc.cadn.net.cn

按顺序检查模式,直到找到匹配项。 如果没有匹配项,则SecurityException被抛出。spring-doc.cadn.net.cn

您可以使用allowedListPatterns属性。 或者,如果您信任所有消息发起方,则可以设置环境变量SPRING_AMQP_DESERIALIZATION_TRUST_ALL或 system 属性spring.amqp.deserialization.trust.alltrue.spring-doc.cadn.net.cn

消息属性转换器

MessagePropertiesConverterstrategy 接口用于在 Rabbit Client 之间进行转换BasicProperties和 Spring AMQPMessageProperties. 默认实现 (DefaultMessagePropertiesConverter) 通常足以满足大多数目的,但如果需要,您可以实现自己的 默认属性转换器将BasicProperties类型的元素LongStringString大小不大于1024字节。 较大LongString实例不会被转换(请参阅下一段)。 可以使用 constructor 参数覆盖此限制。spring-doc.cadn.net.cn

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

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

大型进货LongString标头现在也可以在输出时正确 “转换” (默认情况下)。spring-doc.cadn.net.cn

提供了一个新的构造函数,允许您将转换器配置为像以前一样工作。 下面的清单显示了该方法的 Javadoc 注释和声明:spring-doc.cadn.net.cn

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

同样从版本 1.6 开始,一个名为correlationIdString已添加到MessageProperties. 以前,在与BasicProperties由 RabbitMQ 客户端使用,则不需要byte[] <→ String执行 conversion 是因为MessageProperties.correlationId是一个byte[]BasicProperties使用String. (最终,RabbitMQ 客户端使用 UTF-8 将String到字节以放入协议消息中)。spring-doc.cadn.net.cn

为了提供最大的向后兼容性,一个名为correlationIdPolicy已添加到DefaultMessagePropertiesConverter. 这需要DefaultMessagePropertiesConverter.CorrelationIdPolicyenum 参数。 默认情况下,它设置为BYTES,这将复制之前的行为。spring-doc.cadn.net.cn

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

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

同样从版本 1.6 开始,入站deliveryModeproperty 不再映射到MessageProperties.deliveryMode. 它被映射到MessageProperties.receivedDeliveryMode相反。 此外,入站userIdproperty 不再映射到MessageProperties.userId. 它被映射到MessageProperties.receivedUserId相反。 这些更改是为了避免这些属性的意外传播,如果相同MessagePropertiesobject 用于出站消息。spring-doc.cadn.net.cn

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

4.1.9. 修改消息 - 压缩等

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

消息转换器 中可以看到,一个这样的扩展点位于AmqpTemplate convertAndReceive作中,您可以在其中提供MessagePostProcessor. 例如,在 POJO 转换后,MessagePostProcessor允许您在Message.spring-doc.cadn.net.cn

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

这些扩展点用于压缩等功能,为此,还用于多个MessagePostProcessor提供了 implementations。GZipPostProcessor,ZipPostProcessorDeflaterPostProcessor在发送之前压缩消息,以及GUnzipPostProcessor,UnzipPostProcessorInflaterPostProcessor解压缩收到的消息。spring-doc.cadn.net.cn

从版本 2.1.5 开始,GZipPostProcessor可以使用copyProperties = true选项创建原始消息属性的副本。 默认情况下,出于性能原因,这些属性会重复使用,并使用压缩内容编码和可选的MessageProperties.SPRING_AUTO_DECOMPRESS页眉。 如果保留对原始出站消息的引用,则其属性也将更改。 因此,如果您的应用程序使用这些消息后处理器保留了出站消息的副本,请考虑将copyProperties选项打开。
从版本 2.2.12 开始,您可以配置压缩后处理器在内容编码元素之间使用的分隔符。 在 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将 Compressor 上的 property 设置为:. 当您的使用者升级到 2.2.11 或更高版本时,您可以恢复为默认值 ', '。

同样,SimpleMessageListenerContainer还有一个setAfterReceivePostProcessors()方法,让在容器收到消息后执行解压缩。spring-doc.cadn.net.cn

从版本 2.1.4 开始,addBeforePublishPostProcessors()addAfterReceivePostProcessors()已添加到RabbitTemplate允许将新的后处理器分别附加到 Before Publish 和 After Receive 后处理器的列表中。 此外,还提供了一些方法来删除后处理器。 同样地AbstractMessageListenerContainer还有addAfterReceivePostProcessors()removeAfterReceivePostProcessor()方法。 请参阅 JavadocRabbitTemplateAbstractMessageListenerContainer了解更多详情。spring-doc.cadn.net.cn

4.1.10. 请求/回复消息

AmqpTemplate还提供多种sendAndReceive方法接受前面描述的单向发送作 (exchange,routingKeyMessage). 这些方法对于请求-回复场景非常有用,因为它们处理必要的reply-to属性,并且可以在内部为此目的创建的独占队列上侦听回复消息。spring-doc.cadn.net.cn

类似的请求-回复方法也可用,其中MessageConverter将同时应用于请求和回复。 这些方法被命名为convertSendAndReceive. 请参阅Javadoc 的AmqpTemplate了解更多详情。spring-doc.cadn.net.cn

从版本 1.5.0 开始,每个sendAndReceivemethod 变体有一个重载版本,该版本采用CorrelationData. 与正确配置的连接工厂一起,这允许接收作发送端的发布者确认。 请参阅相关的发布者确认和返回以及Javadoc 的RabbitOperations了解更多信息。spring-doc.cadn.net.cn

从版本 2.0 开始,这些方法有变体 (convertSendAndReceiveAsType),该ParameterizedTypeReference参数来转换复杂的返回类型。 模板必须配置有SmartMessageConverter. 看MessageRabbitTemplate了解更多信息。spring-doc.cadn.net.cn

从版本 2.1 开始,您可以配置RabbitTemplate使用noLocalReplyConsumer选项来控制noLocal标志。 这是false默认情况下。spring-doc.cadn.net.cn

回复超时

默认情况下,发送和接收方法会在 5 秒后超时并返回 null。 您可以通过设置replyTimeout财产。 从版本 1.5 开始,如果将mandatoryproperty 设置为true(或mandatory-expression计算结果为true对于特定消息),如果消息无法传送到队列,则AmqpMessageReturnedException被抛出。 此异常具有returnedMessage,replyCodereplyText属性以及exchangeroutingKey用于发送。spring-doc.cadn.net.cn

此功能使用发布者返回。 您可以通过设置publisherReturnstrueCachingConnectionFactory(请参阅 发布者确认并返回)。 此外,您不得注册自己的ReturnCallback使用RabbitTemplate.

从版本 2.1.2 开始,replyTimedOut方法,让子类被告知超时,以便它们可以清理任何保留状态。spring-doc.cadn.net.cn

从版本 2.0.11 和 2.1.3 开始,当您使用默认的DirectReplyToMessageListenerContainer中,您可以通过设置模板的replyErrorHandler财产。 对于任何失败的投放,如延迟回复和在没有关联标头的情况下收到的消息,都会调用此错误处理程序。 传入的异常是一个ListenerExecutionFailedException,它有一个failedMessage财产。spring-doc.cadn.net.cn

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

命名队列仍支持回复侦听器(除了amq.rabbitmq.reply-to)、允许控制回复并发等。spring-doc.cadn.net.cn

从版本 1.6 开始,如果您希望为每个 reply,将useTemporaryReplyQueuesproperty 设置为true. 如果您将replyAddress.spring-doc.cadn.net.cn

您可以通过子类化来更改指示是否使用直接回复的条件RabbitTemplate并覆盖useDirectReplyTo()以检查不同的标准。 该方法仅在发送第一个请求时调用一次。spring-doc.cadn.net.cn

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

AsyncRabbitTemplate没有这样的选项。 它总是使用DirectReplyToContainerfor reply。spring-doc.cadn.net.cn

从版本 2.3.7 开始,模板具有一个新属性useChannelForCorrelation. 当这是true,则服务器不必将相关 ID 从请求消息标头复制到回复消息。 相反,用于发送请求的通道用于将回复与请求相关联。spring-doc.cadn.net.cn

Message Correlation With A Reply Queue

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

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

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

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

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

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

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

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

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

在 1.5.0 版本之前,reply-address属性不可用。 回复始终使用默认 exchange 进行路由,并且reply-queuename 作为路由密钥。 这仍然是默认值,但您现在可以指定新的reply-address属性。 这reply-address可以包含格式为<exchange>/<routingKey>并且回复将路由到指定的 Exchange,并路由到与路由密钥绑定的队列。 这reply-address的优先级高于reply-queue. 当只有reply-address正在使用中,则<reply-listener>必须配置为单独的<listener-container>元件。 这reply-addressreply-queue(或queues属性<listener-container>) 必须在逻辑上引用同一队列。

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

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

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

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

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

当您自己连接回复侦听器和模板时,请务必确保模板的replyAddress和容器的queues(或queueNames) 属性引用同一队列。 模板将回复地址插入到出站邮件中replyTo财产。

下面的清单显示了如何手动连接 bean 的示例:spring-doc.cadn.net.cn

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

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

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

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

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

一个完整的RabbitTemplate此测试用例显示了与固定回复队列相连的 “远程” 侦听器容器,该容器处理请求并返回回复。spring-doc.cadn.net.cn

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

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

有关配置死信的更多信息,请参阅 RabbitMQ 死信文档。 您还可以查看FixedReplyQueueDeadLetterTeststest case 为例。spring-doc.cadn.net.cn

异步 Rabbit 模板

版本 1.6 引入了AsyncRabbitTemplate. 这具有类似的sendAndReceive(以及convertSendAndReceive) 方法更改为AmqpTemplate. 但是,它们不是阻塞,而是返回一个CompletableFuture.spring-doc.cadn.net.cn

sendAndReceive方法返回一个RabbitMessageFuture. 这convertSendAndReceive方法返回一个RabbitConverterFuture.spring-doc.cadn.net.cn

您可以稍后通过调用get()on the future,或者您可以注册一个与 result 异步调用的回调。 下面的清单显示了这两种方法:spring-doc.cadn.net.cn

@Autowired
private AsyncRabbitTemplate template;

...

public void doSomeWorkAndGetResultLater() {

    ...

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

    // do some more work

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

    ...

}

public void doSomeWorkAndGetResultAsync() {

    ...

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

    ...

}

如果mandatory设置了该 API 的 API API 的 API API 中,并且无法传递消息,则 future 会抛出一个ExecutionException原因为AmqpMessageReturnedException,它封装了返回的消息和有关返回的信息。spring-doc.cadn.net.cn

如果enableConfirms设置,则 future 具有一个名为confirm,它本身就是一个CompletableFuture<Boolean>true表示发布成功。 如果确认 future 为falseRabbitFuture具有另一个名为nackCause,其中包含失败的原因(如果可用)。spring-doc.cadn.net.cn

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

您可以设置receiveTimeout属性来超时回复(默认为30000- 30 秒)。 如果发生超时,则 future 将以AmqpReplyTimeoutException.spring-doc.cadn.net.cn

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

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

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

public AsyncRabbitTemplate(RabbitTemplate template)

请参阅 RabbitMQ 直接回复 以将直接回复与同步RabbitTemplate.spring-doc.cadn.net.cn

版本 2.0 引入了这些方法的变体 (convertSendAndReceiveAsType),该ParameterizedTypeReference参数来转换复杂的返回类型。 您必须配置底层RabbitTemplate替换为SmartMessageConverter. 看MessageRabbitTemplate了解更多信息。spring-doc.cadn.net.cn

从版本 3.0 开始,AsyncRabbitTemplate方法现在返回CompletableFutures 而不是ListenableFutures.
使用 AMQP 的 Spring 远程处理

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

sendAndReceiveRabbitTemplate(客户端)和@RabbitListener相反。spring-doc.cadn.net.cn

4.1.11. 配置 Broker

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

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

public interface AmqpAdmin {

    // Exchange Operations

    void declareExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();

    String declareQueue(Queue queue);

    void deleteQueue(String queueName);

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

    void purgeQueue(String queueName, boolean noWait);

    // Binding Operations

    void declareBinding(Binding binding);

    void removeBinding(Binding binding);

    Properties getQueueProperties(String queueName);

}

getQueueProperties()method 返回有关队列的一些有限信息(消息计数和使用者计数)。 返回的属性的键在RabbitTemplate (QUEUE_NAME,QUEUE_MESSAGE_COUNTQUEUE_CONSUMER_COUNT). RabbitMQ REST APIQueueInfo对象。spring-doc.cadn.net.cn

无参数declareQueue()method 定义代理上的队列,其名称是自动生成的。 此自动生成的队列的其他属性包括exclusive=true,autoDelete=truedurable=false.spring-doc.cadn.net.cn

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

这与AnonymousQueue其中,框架会生成一个唯一的 (UUID) 名称和集durablefalseexclusive,autoDeletetrue. 一个<rabbit:queue/>替换为空 (或缺失)name属性始终创建一个AnonymousQueue.spring-doc.cadn.net.cn

AnonymousQueue了解原因AnonymousQueue优先于代理生成的队列名称,以及 如何控制名称的格式。 从版本 2.1 开始,匿名队列使用 argument 声明Queue.X_QUEUE_LEADER_LOCATOR设置为client-local默认情况下。 这可确保在应用程序连接到的节点上声明队列。 声明式队列必须具有固定的名称,因为它们可能会在上下文中的其他位置引用,例如在 listener 中所示的 Listener 示例:spring-doc.cadn.net.cn

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

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

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

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

CachingConnectionFactorycache 模式为CHANNEL(默认值)、RabbitAdminimplementation 会自动延迟声明 queues、exchanges 和 bindings 在同一ApplicationContext. 这些组件会在Connection将向 broker 打开。 有一些命名空间功能使这非常方便 — 例如, 在 Stocks 示例应用程序中,我们有以下内容:spring-doc.cadn.net.cn

<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

<fanout-exchange name="broadcast.responses"
                 xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="tradeQueue"/>
    </bindings>
</fanout-exchange>

<topic-exchange name="app.stock.marketdata"
                xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
    </bindings>
</topic-exchange>

在前面的示例中,我们使用匿名队列(实际上,在内部,只是名称由框架生成,而不是由代理生成的队列)并通过 ID 引用它们。 我们还可以用显式名称声明队列,这些名称也用作上下文中它们的 bean 定义的标识符。 以下示例使用显式名称配置队列:spring-doc.cadn.net.cn

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

队列可以使用其他参数进行配置,例如x-message-ttl. 当您使用命名空间支持时,它们以Map的 argument-name/argument-value 对,这些对使用<rabbit:queue-arguments>元素。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<rabbit:queue name="withArguments">
    <rabbit:queue-arguments>
        <entry key="x-dead-letter-exchange" value="myDLX"/>
        <entry key="x-dead-letter-routing-key" value="dlqRK"/>
    </rabbit:queue-arguments>
</rabbit:queue>

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

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

在提供混合类型的参数时,必须为每个 entry 元素提供类型。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

对于 Spring Framework 3.2 及更高版本,可以更简洁地声明这一点,如下所示:spring-doc.cadn.net.cn

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

当您使用 Java 配置时,Queue.X_QUEUE_LEADER_LOCATOR参数作为第一类属性支持通过setLeaderLocator()方法上的Queue类。 从版本 2.1 开始,匿名队列的声明将此属性设置为client-local默认情况下。 这可确保在应用程序连接到的节点上声明队列。spring-doc.cadn.net.cn

RabbitMQ 代理不允许声明具有不匹配参数的队列。 例如,如果queuealready exists (已存在),但没有time to live参数,并尝试使用 (例如)key="x-message-ttl" value="100",则会引发异常。

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

可以通过设置ignore-declaration-exceptions属性设置为trueRabbitAdmin实例。 此选项指示RabbitAdmin记录异常并继续声明其他元素。 在配置RabbitAdmin使用 Java,此属性称为ignoreDeclarationExceptions. 这是适用于所有元素的全局设置。 Queues、exchanges 和 bindings 具有类似的属性,该属性仅适用于这些元素。spring-doc.cadn.net.cn

在版本 1.6 之前,此属性仅在IOException发生在通道上,例如当前属性和所需属性不匹配时。 现在,此属性对任何异常都生效,包括TimeoutException和其他。spring-doc.cadn.net.cn

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

标头交换

从版本 1.3 开始,您可以配置HeadersExchange以匹配多个标头。 您还可以指定是否必须匹配任何或所有标头。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<rabbit:headers-exchange name="headers-test">
    <rabbit:bindings>
        <rabbit:binding queue="bucket">
            <rabbit:binding-arguments>
                <entry key="foo" value="bar"/>
                <entry key="baz" value="qux"/>
                <entry key="x-match" value="all"/>
            </rabbit:binding-arguments>
        </rabbit:binding>
    </rabbit:bindings>
</rabbit:headers-exchange>

从版本 1.6 开始,您可以配置Exchanges替换为internalflag(默认为false) 和这样的Exchange在 Broker 上通过RabbitAdmin(如果应用程序上下文中存在一个)。 如果internalflag 为true对于 Exchange,RabbitMQ 不允许客户端使用 Exchange。 这对于死信交换或 exchange-to-exchange 绑定非常有用,在这种情况下,您不希望使用 exchange 直接由出版商提供。spring-doc.cadn.net.cn

要了解如何使用 Java 配置 AMQP 基础结构,请查看 Stock 示例应用程序 其中有@ConfigurationAbstractStockRabbitConfiguration,而RabbitClientConfigurationRabbitServerConfiguration子。 下面的清单显示了AbstractStockRabbitConfiguration:spring-doc.cadn.net.cn

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

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

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

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

    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}

在 Stock 应用程序中,使用以下命令配置服务器@Configuration类:spring-doc.cadn.net.cn

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

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

这是整个继承链的终点@Configuration类。 最终结果是TopicExchangeQueue在应用程序启动时向 Broker 声明。 没有TopicExchange添加到服务器配置中的队列中,就像在 Client 端应用程序中完成的那样。 但是,股票请求队列会自动绑定到 AMQP 默认交易所。 此行为由规范定义。spring-doc.cadn.net.cn

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

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;

    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}

客户端通过declareQueue()方法上的AmqpAdmin. 它使用在属性文件中外部化的路由模式将该队列绑定到市场数据交换。spring-doc.cadn.net.cn

用于队列和交换的 Builder API

版本 1.6 引入了一个方便的 Fluent API,用于配置QueueExchange对象。 以下示例演示如何使用它:spring-doc.cadn.net.cn

@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}

@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}

从版本 2.0 开始,ExchangeBuilder现在默认创建 Durable Exchange,以便与单个AbstractExchange类。 要与构建器进行非持久交换,请使用.durable(false)调用.build(). 这durable()method 中。spring-doc.cadn.net.cn

2.2 版本引入了 Fluent API 来添加“众所周知的”exchange 和 queue 参数......spring-doc.cadn.net.cn

@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}
声明 Exchanges、Queues 和 Bindings 的集合

您可以包装Declarable对象 (Queue,ExchangeBinding) 在Declarables对象。 这RabbitAdmin检测此类 bean(以及 discreteDeclarablebeans),并在建立连接时(最初和连接失败后)在代理上声明包含的对象。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
public static class Config {

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

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

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

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}
在 2.1 之前的版本中,您可以声明多个Declarable实例Collection<Declarable>. 在某些情况下,这可能会导致不良的副作用,因为管理员必须迭代所有Collection<?>豆。

版本 2.2 添加了getDeclarablesByTypemethod 设置为Declarables;这可以用作一种方便,例如,在声明侦听器容器 bean 时。spring-doc.cadn.net.cn

public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}
条件声明

默认情况下,所有 queues、exchange 和 bindings 都由所有RabbitAdmin实例(假设它们具有auto-startup="true") 中。spring-doc.cadn.net.cn

从版本 2.1.9 开始,RabbitAdmin具有新属性explicitDeclarationsOnly(即false默认情况下);当此项设置为true,管理员将只声明显式配置为由该管理员声明的 bean。spring-doc.cadn.net.cn

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

表示这些元素的类实现Declarable,它有两种方法:shouldDeclare()getDeclaringAdmins(). 这RabbitAdmin使用这些方法来确定特定实例是否实际应处理其Connection.spring-doc.cadn.net.cn

这些属性可用作命名空间中的属性,如以下示例所示:spring-doc.cadn.net.cn

<rabbit:admin id="admin1" connection-factory="CF1" />

<rabbit:admin id="admin2" connection-factory="CF2" />

<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />

<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />

<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />

<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />

<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />

<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
    <rabbit:bindings>
        <rabbit:binding key="foo" queue="bar"/>
    </rabbit:bindings>
</rabbit:direct-exchange>
默认情况下,auto-declareattribute 为true并且,如果declared-by未提供(或为空),则所有RabbitAdmin实例声明对象(只要管理员的auto-startupattribute 为true、默认值和管理员的explicit-declarations-only属性为 false)。

同样,您可以使用基于 Java 的@Configuration以达到相同的效果。 在下面的示例中,组件由admin1但不是admin2:spring-doc.cadn.net.cn

@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}

@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}

@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}

@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}

@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}
关于idname属性

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

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

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

从版本 2.0 开始,如果你用id 以及name属性,则该名称不再声明为 Bean 名称别名。 如果您希望声明一个队列并与之交换name,您必须提供id.spring-doc.cadn.net.cn

如果元素只有name属性。 该 bean 仍可由name— 例如,在绑定声明中。 但是,如果名称包含 SPEL,则仍然无法引用它 — 您必须提供id供参考。spring-doc.cadn.net.cn

AnonymousQueue

通常,当您需要名称唯一的独占自动删除队列时,我们建议您使用AnonymousQueue而不是代理定义的队列名称(使用""Queuename 使 broker 生成队列 name) 的 S Sspring-doc.cadn.net.cn

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

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

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

您可以控制AnonymousQueue实例。spring-doc.cadn.net.cn

默认情况下,队列名称以spring.gen-后跟 base64 的UUID— 例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g.spring-doc.cadn.net.cn

您可以提供AnonymousQueue.NamingStrategyimplementation 的 Implementation。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Bean
public Queue anon1() {
    return new AnonymousQueue();
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}

@Bean
public Queue anon3() {
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}

第一个 Bean 生成一个队列名称,前缀为spring.gen-后跟 base64 的UUID— 代表 例:spring.gen-MRBv9sqISkuCiPfOYfpo4g. 第二个 Bean 生成一个队列名称,前缀为something-后跟 base64 的UUID. 第三个 bean 仅使用 UUID(无 base64 转换)生成名称 — 例如,f20c818a-006b-4416-bf91-643590fedb0e.spring-doc.cadn.net.cn

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

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

您可以在使用 XML 配置时指定命名策略。 这naming-strategy属性存在于<rabbit:queue>元素 对于实现AnonymousQueue.NamingStrategy. 以下示例说明如何以各种方式指定命名策略:spring-doc.cadn.net.cn

<rabbit:queue id="uuidAnon" />

<rabbit:queue id="springAnon" naming-strategy="uuidNamer" />

<rabbit:queue id="customAnon" naming-strategy="customNamer" />

<bean id="uuidNamer" class="org.springframework.amqp.core.AnonymousQueue.UUIDNamingStrategy" />

<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
    <constructor-arg value="custom.gen-" />
</bean>

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

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

从版本 2.1 开始,匿名队列使用 argument 声明Queue.X_QUEUE_LEADER_LOCATOR设置为client-local默认情况下。 这可确保在应用程序连接到的节点上声明队列。 您可以通过调用queue.setLeaderLocator(null)在构造实例之后。spring-doc.cadn.net.cn

恢复自动删除声明

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

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

在以下情况下,将不会执行单个声明的恢复deleteQueue(…​),deleteExchange(…​)removeBinding(…​)被调用。 删除队列和交换时,将从可恢复实体中删除关联的绑定。spring-doc.cadn.net.cn

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

4.1.12. 代理事件监听器

Event Exchange 插件启用时,如果添加BrokerEventListener将选定的代理事件发布为BrokerEvent实例,可以与普通的 Spring 一起使用ApplicationListener@EventListener方法。 事件由代理发布到主题交换amq.rabbitmq.event为每个事件类型使用不同的路由键。 侦听器使用事件键,这些键用于将AnonymousQueue发送到 Exchange,以便侦听器仅接收选定的事件。 由于它是主题交换,因此可以使用通配符(以及显式请求特定事件),如下例所示:spring-doc.cadn.net.cn

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

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

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

4.1.13. 延迟消息交换

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

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

要使用RabbitAdmin要将 Exchange 声明为 delayed,您可以设置delayed属性添加到true. 这RabbitAdmin使用 Exchange 类型 (Direct,Fanout等)以设置x-delayed-typeargument 和 使用 type 声明 Exchangex-delayed-message.spring-doc.cadn.net.cn

delayed属性(默认值:false) 在使用 XML 配置 Exchange Bean 时也可用。 以下示例演示如何使用它:spring-doc.cadn.net.cn

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

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

MessageProperties properties = new MessageProperties();
properties.setDelay(15000);
template.send(exchange, routingKey,
        MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());
rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() {

    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setDelay(15000);
        return message;
    }

});

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

4.1.14. RabbitMQ REST API

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

跃点依赖项 (com.rabbitmq:http-client) 现在也是optional.spring-doc.cadn.net.cn

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

4.1.15. 异常处理

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

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

但是,有一类错误是侦听器无法控制行为的。 当遇到无法转换的消息(例如,无效的content_encodingheader),则在消息到达用户代码之前会引发一些异常。 跟defaultRequeueRejected设置为true(默认)(或抛出ImmediateRequeueAmqpException),此类消息将被一遍又一遍地重新传递。 在 1.3.2 版本之前,用户需要编写一个自定义的ErrorHandler,如 异常处理中所述,以避免这种情况。spring-doc.cadn.net.cn

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

  • o.s.amqp…​MessageConversionException:在使用MessageConverter.spring-doc.cadn.net.cn

  • o.s.messaging…​MessageConversionException:如果在映射到@RabbitListener方法。spring-doc.cadn.net.cn

  • o.s.messaging…​MethodArgumentNotValidException:如果验证(例如@Valid) 在侦听器中使用,验证失败。spring-doc.cadn.net.cn

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

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

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

您可以使用FatalExceptionStrategy以便用户可以为条件消息拒绝提供自己的规则 — 例如,将BinaryExceptionClassifier来自 Spring Retry(消息侦听器和异步情况)。 此外,ListenerExecutionFailedException现在有一个failedMessage可在决策中使用的属性。 如果FatalExceptionStrategy.isFatal()method 返回true,错误处理程序会抛出AmqpRejectAndDontRequeueException. 默认的FatalExceptionStrategy在确定异常为致命异常时记录警告消息。spring-doc.cadn.net.cn

从 1.6.3 版本开始,将 user exceptions 添加到 fatal list 的一种便捷方法是 subclassConditionalRejectingErrorHandler.DefaultExceptionStrategy并覆盖isUserCauseFatal(Throwable cause)method 返回true对于致命异常。spring-doc.cadn.net.cn

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

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

4.1.16. 事务

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

有两种方法可以向框架发出所需的事务语义信号。 在这两个RabbitTemplateSimpleMessageListenerContainer,则有一个标志channelTransactedwhich、iftrue,告诉框架使用事务通道,并通过提交或回滚(取决于结果)结束所有作(发送或接收),并发出回滚信号的异常。 另一个信号是使用 Spring 的PlatformTransactionManagerimplementations 作为正在进行的作的上下文。 如果在框架发送或接收消息时已经有一个事务正在进行中,并且channelTransactedflag 为true,则消息收发事务的提交或回滚将推迟到当前事务结束。 如果channelTransactedflag 为false,没有事务语义适用于消息传递作(它是自动确认的)。spring-doc.cadn.net.cn

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

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

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

在前面的示例中,Stringpayload 在标记为@Transactional. 如果数据库处理失败并出现异常,则传入消息将返回给代理,并且不会发送传出消息。 这适用于使用RabbitTemplate在事务方法链中(例如,除非Channel直接作以提前提交事务)。spring-doc.cadn.net.cn

对于具有SimpleMessageListenerContainer,如果需要外部事务,则必须由容器在设置侦听器时请求该事务。 为了表示需要外部事务,用户提供了PlatformTransactionManager添加到容器。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
public class ExampleExternalTransactionAmqpConfiguration {

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

}

在前面的示例中,事务管理器被添加为从另一个 bean 定义注入的依赖项(未显示),并且channelTransactedflag 也被设置为true. 其效果是,如果侦听器失败并出现异常,则事务将回滚,并且消息也会返回给代理。 值得注意的是,如果事务提交失败(例如,由于 数据库约束错误或连接问题),则 AMQP 事务也会回滚,并将消息返回给代理。 这有时被称为“尽力而为的 1 阶段提交”,是一种非常强大的可靠消息传递模式。 如果channelTransactedflag 设置为false(默认值)在前面的示例中,仍将为侦听器提供外部事务,但所有消息传递作都将自动确认,因此效果是即使在业务作回滚时也提交消息传递作。spring-doc.cadn.net.cn

条件回滚

在 1.6.6 版本之前,将回滚规则添加到容器的transactionAttribute当使用外部事务管理器(例如 JDBC)时,没有任何效果。 异常始终回滚事务。spring-doc.cadn.net.cn

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.1.17. 消息侦听器容器配置

配置一个SimpleMessageListenerContainer(SMLC) 和DirectMessageListenerContainer(DMLC) 与交易和服务质量相关,其中一些相互交互。 适用于 SMLC、DMLC 或StreamListenerContainer(StLC)(请参阅使用 RabbitMQ 流插件)由相应列中的复选标记指示。 请参阅选择容器,了解可帮助您确定哪个容器适合您的应用程序的信息。spring-doc.cadn.net.cn

下表显示了使用命名空间配置<rabbit:listener-container/>. 这typeattribute 可以是simple(默认)或direct要指定SMLCDMLC分别。 某些属性不由命名空间公开。 这些由N/A对于属性。spring-doc.cadn.net.cn

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

ackTimeout
(不适用)spring-doc.cadn.net.cn

什么时候messagesPerAck设置了 TIMEOUT 时,此 timeout 将用作发送 ACK 的替代方法。 当新消息到达时,将未确认的消息计数与messagesPerAck,并将自上次 ACK 以来的时间与此值进行比较。 如果任一条件为true,则消息将被确认。 当没有新消息到达并且有未确认的消息时,此超时是近似值,因为仅检查每个条件monitorInterval. 另请参阅messagesPerAckmonitorInterval在此表中。spring-doc.cadn.net.cn

刻度线

acknowledgeMode
(确认)spring-doc.cadn.net.cn

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

  • MANUAL:侦听器必须通过调用Channel.basicAck().spring-doc.cadn.net.cn

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

刻度线
刻度线

adviceChain
(建议链)spring-doc.cadn.net.cn

应用于侦听器执行的 AOP Advice 数组。 这可用于应用其他横切关注点,例如在 broker 死亡时自动重试。 请注意,在 AMQP 错误之后的简单重新连接由CachingConnectionFactory,只要 broker 还活着。spring-doc.cadn.net.cn

刻度线
刻度线

afterReceivePostProcessors
(不适用)spring-doc.cadn.net.cn

一个MessagePostProcessor在调用侦听器之前调用的实例。 后处理器可以实现PriorityOrderedOrdered. 数组使用最后调用的无序成员进行排序。 如果后处理器返回null,则消息将被丢弃(并确认,如果适用)。spring-doc.cadn.net.cn

刻度线
刻度线

alwaysRequeueWithTxManagerRollback
(不适用)spring-doc.cadn.net.cn

设置为true在配置事务管理器时,始终在 rollback 上对消息重新排队。spring-doc.cadn.net.cn

刻度线
刻度线

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

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

在版本 1.6 之前,如果上下文中有多个 admin,则容器将随机选择一个。 如果没有管理员,它将在内部创建一个管理员。 无论哪种情况,都可能导致意外结果。 从版本 1.6 开始,对于autoDeclare要起作用,必须只有一个RabbitAdmin中,或者必须使用rabbitAdmin财产。
刻度线
刻度线

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

标志,指示容器应在ApplicationContextdoes(作为SmartLifecycle回调,这发生在所有 bean 初始化之后)。 默认为true,但您可以将其设置为false如果您的代理在启动时可能不可用,请调用start()稍后当您知道 broker 已准备就绪时手动作。spring-doc.cadn.net.cn

刻度线
刻度线
刻度线

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

当与acknowledgeMode设置为AUTO,容器会尝试在发送 ack 之前处理最多此数量的消息(等待每条消息直到接收超时设置)。 这也是提交事务通道时。 如果prefetchCount小于batchSize,则会增加以匹配batchSize.spring-doc.cadn.net.cn

刻度线

batchingStrategy
(不适用)spring-doc.cadn.net.cn

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

刻度线
刻度线

channelTransacted
(通道事务处理)spring-doc.cadn.net.cn

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

刻度线
刻度线

concurrency
(不适用)spring-doc.cadn.net.cn

m-n每个侦听器的并发使用者范围(最小值、最大值)。 苟n提供,n是固定数量的使用者。 请参阅 侦听器并发spring-doc.cadn.net.cn

刻度线

concurrentConsumers
(并发)spring-doc.cadn.net.cn

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

刻度线
刻度线

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

ConnectionFactory. 使用 XML 名称空间进行配置时,默认引用的 Bean 名称为rabbitConnectionFactory.spring-doc.cadn.net.cn

刻度线
刻度线

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

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

刻度线

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

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

刻度线

consumerBatchEnabled
(已启用批处理)spring-doc.cadn.net.cn

如果MessageListener支持它,将此项设置为 true 可启用离散消息的批处理,最高可达batchSize;如果没有新消息到达,将传递部分批处理receiveTimeout. 如果为 false,则仅支持由 producer 创建的批处理;请参阅 批处理spring-doc.cadn.net.cn

刻度线

consumerCustomizer
(不适用)spring-doc.cadn.net.cn

一个ConsumerCustomizer用于修改容器创建的流使用者的 bean。spring-doc.cadn.net.cn

刻度线

consumerStartTimeout
(不适用)spring-doc.cadn.net.cn

等待使用者线程启动的时间(以毫秒为单位)。 如果超过此时间,则会写入错误日志。 发生这种情况的一个示例是,如果配置了taskExecutor没有足够的线程来支持容器concurrentConsumers.spring-doc.cadn.net.cn

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

刻度线

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

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

刻度线
刻度线

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

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

刻度线

consumeDelay
(不适用)spring-doc.cadn.net.cn

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

刻度线
刻度线

debatchingEnabled
(不适用)spring-doc.cadn.net.cn

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

刻度线
刻度线

declarationRetries
(declaration-retriries)spring-doc.cadn.net.cn

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

刻度线

defaultRequeueRejected
(重新排队-被拒绝)spring-doc.cadn.net.cn

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

刻度线
刻度线

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

ErrorHandler策略来处理在执行 MessageListener 期间可能发生的任何未捕获的异常。 违约:ConditionalRejectingErrorHandlerspring-doc.cadn.net.cn

刻度线
刻度线

exclusive
(独家)spring-doc.cadn.net.cn

确定此容器中的单个使用者是否具有对队列的独占访问权限。 当true. 如果另一个使用者具有独占访问权限,则容器会尝试根据recovery-intervalrecovery-back-off. 使用命名空间时,此属性显示在<rabbit:listener/>元素以及队列名称。 违约:false.spring-doc.cadn.net.cn

刻度线
刻度线

exclusiveConsumerExceptionLogger
(不适用)spring-doc.cadn.net.cn

当独占使用者无法访问队列时使用的异常记录器。 默认情况下,这记录在WARN水平。spring-doc.cadn.net.cn

刻度线
刻度线

failedDeclarationRetryInterval
(失败声明 -重试间隔)spring-doc.cadn.net.cn

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

刻度线
刻度线

forceCloseChannel
(不适用)spring-doc.cadn.net.cn

如果使用者在shutdownTimeout,如果这是true,通道将被关闭,从而导致任何未确认的消息重新排队。 默认为true从 2.0 开始。 您可以将其设置为false以恢复到之前的行为。spring-doc.cadn.net.cn

刻度线
刻度线

forceStop
(不适用)spring-doc.cadn.net.cn

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

刻度线
刻度线

globalQos
(全局 QoS)spring-doc.cadn.net.cn

如果为 true,则prefetchCount全局应用于通道,而不是通道上的每个使用者。 看basicQos.global了解更多信息。spring-doc.cadn.net.cn

刻度线
刻度线

(群展)spring-doc.cadn.net.cn

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

刻度线
刻度线

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

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

刻度线
刻度线

javaLangErrorHandler
(不适用)spring-doc.cadn.net.cn

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

刻度线
刻度线

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

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

刻度线

messagesPerAck
(不适用)spring-doc.cadn.net.cn

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

刻度线

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

当容器启动时,如果此属性为true(默认:false),容器会检查上下文中声明的所有队列是否与代理上已有的队列兼容。 如果不匹配的属性(例如auto-delete) 或参数 (skuch 为x-message-ttl) 存在时,容器(和应用程序上下文)无法启动并出现致命异常。spring-doc.cadn.net.cn

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

必须有一个RabbitAdmin在应用程序上下文中(或使用rabbitAdmin属性)。 否则,此属性必须为false.spring-doc.cadn.net.cn

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

missingQueuesFatal
(缺少队列致命)spring-doc.cadn.net.cn

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

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

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

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

<util:properties
        id="spring.amqp.global.properties">
    <prop key="mlc.missing.queues.fatal">
        false
    </prop>
</util:properties>

此全局属性不适用于具有显式missingQueuesFatal属性集。spring-doc.cadn.net.cn

默认重试属性(每隔 5 秒重试 3 次)可以通过设置以下属性来覆盖。spring-doc.cadn.net.cn

在 为@RabbitListener在标记了@Lazy. 这是为了避免潜在的死锁,这可能会使此类容器的启动延迟长达 60 秒。 使用惰性侦听器 bean 的应用程序应该在获取对惰性 bean 的引用之前检查队列。
刻度线
刻度线

monitorInterval
(监控间隔)spring-doc.cadn.net.cn

使用 DMLC 时,将计划在此间隔运行任务,以监控使用者的状态并恢复任何失败的状态。spring-doc.cadn.net.cn

刻度线

noLocal
(不适用)spring-doc.cadn.net.cn

设置为true以禁用从服务器向使用者传送在同一通道连接上发布的消息。spring-doc.cadn.net.cn

刻度线
刻度线

phase
(阶段)spring-doc.cadn.net.cn

什么时候autoStartuptrue,此容器应在其中启动和停止的生命周期阶段。 值越低,此容器开始得越早,停止得越晚。 默认值为Integer.MAX_VALUE,这意味着容器会尽可能晚地启动并尽快停止。spring-doc.cadn.net.cn

刻度线
刻度线

possibleAuthenticationFailureFatal
(可能的身份验证失败致命)spring-doc.cadn.net.cn

当设置为true(SMLC 的默认值),如果PossibleAuthenticationFailureException在连接期间引发,则被视为致命的。 这会导致应用程序上下文在启动期间无法初始化(如果容器配置了自动启动)。spring-doc.cadn.net.cn

2.0 版本开始。spring-doc.cadn.net.cn

DirectMessageListenerContainerspring-doc.cadn.net.cn

当设置为false(默认),则每个使用者将尝试根据monitorInterval.spring-doc.cadn.net.cn

SimpleMessageListenerContainer (简单消息监听器容器)spring-doc.cadn.net.cn

当设置为false,在进行 3 次重试后,容器将进入恢复模式,就像其他问题一样,比如代理宕机。 容器将尝试根据recoveryInterval财产。 在每次恢复尝试期间,每个使用者将再次尝试 4 次以启动。 这个过程将无限期地持续下去。spring-doc.cadn.net.cn

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

<util:properties
    id="spring.amqp.global.properties">
  <prop
    key="mlc.possible.authentication.failure.fatal">
     false
  </prop>
</util:properties>

此全局属性不会应用于具有显式missingQueuesFatal属性集。spring-doc.cadn.net.cn

默认重试属性(3 次重试,间隔 5 秒)可以使用此属性之后的属性覆盖。spring-doc.cadn.net.cn

刻度线
刻度线

prefetchCount
(预取)spring-doc.cadn.net.cn

每个使用者可以未完成的未确认消息数。 此值越高,消息的传递速度就越快,但非顺序处理的风险就越高。 如果acknowledgeModeNONE. 如有必要,将增加此值以匹配batchSizemessagePerAck. 自 2.0 起默认为 250。 您可以将其设置为 1 以恢复到之前的行为。spring-doc.cadn.net.cn

在某些情况下,prefetch 值应 低 — 例如,对于大型消息,尤其是在处理速度较慢的情况下(消息可能会累积 添加到客户端进程中的大量内存),并且如果需要严格的消息排序 (在这种情况下,prefetch 值应设置回 1)。 此外,对于低容量消息收发和多个使用者(包括单个侦听器容器实例中的并发),您可能希望减少预取,以便在使用者之间更均匀地分配消息。

另请参阅globalQos.spring-doc.cadn.net.cn

刻度线
刻度线

rabbitAdmin
(管理员)spring-doc.cadn.net.cn

当侦听器容器侦听至少一个自动删除队列,并且在启动期间发现该队列缺失时,该容器会使用RabbitAdmin来声明 queue 以及任何相关的 bindings 和 exchanges。 如果此类元素配置为使用条件声明(请参阅条件声明),则容器必须使用配置为声明这些元素的 admin。 在此处指定该管理员。 仅当使用带有条件声明的自动删除队列时,才需要它。 如果您不希望在容器启动之前声明自动删除队列,请将auto-startupfalse在 admin. 默认为RabbitAdmin声明所有非条件元素。spring-doc.cadn.net.cn

刻度线
刻度线

receiveTimeout
(接收超时)spring-doc.cadn.net.cn

等待每条消息的最长时间。 如果acknowledgeMode=NONE,这影响很小 — 容器旋转并请求另一条消息。 它对交易ChannelbatchSize > 1,因为它可能导致在超时到期之前不确认已使用的消息。 什么时候consumerBatchEnabled为 true,则如果此超时发生在批处理完成之前,则将交付部分批处理。spring-doc.cadn.net.cn

刻度线

recoveryBackOff
(恢复回退)spring-doc.cadn.net.cn

指定BackOff如果使用者由于非致命原因而无法启动,则尝试启动使用者的间隔。 默认值为FixedBackOff每 5 秒无限次重试。 互斥与recoveryInterval.spring-doc.cadn.net.cn

刻度线
刻度线

recoveryInterval
(恢复间隔)spring-doc.cadn.net.cn

确定如果使用者由于非致命原因而无法启动,则尝试启动使用者之间的时间(以毫秒为单位)。 默认值:5000。 互斥与recoveryBackOff.spring-doc.cadn.net.cn

刻度线
刻度线

retryDeclarationInterval
(缺失队列- retry-interval)spring-doc.cadn.net.cn

如果配置的队列的子集在使用者初始化期间可用,则使用者将开始从这些队列中使用。 使用者尝试使用此间隔被动声明缺少的队列。 当此间隔过后,将再次使用 'declarationRetries' 和 'failedDeclarationRetryInterval'。 如果仍然缺少队列,使用者会再次等待此间隔,然后再尝试一次。 此过程将无限期地持续,直到所有队列都可用。 默认值:60000(1 分钟)。spring-doc.cadn.net.cn

刻度线

shutdownTimeout
(不适用)spring-doc.cadn.net.cn

当容器关闭时(例如, 如果它包含ApplicationContext已关闭),它会等待处理到此限制的 in-flight 消息。 默认为 5 秒。spring-doc.cadn.net.cn

刻度线
刻度线

startConsumerMinInterval
(最小开始间隔)spring-doc.cadn.net.cn

按需启动每个新使用者之前必须经过的时间(以毫秒为单位)。 请参阅 侦听器并发。 默认值:10000(10 秒)。spring-doc.cadn.net.cn

刻度线

statefulRetryFatal
WithNullMessageId (不适用)spring-doc.cadn.net.cn

当使用有状态重试通知时,如果缺少messageIdproperty 时,它被视为 fatal (它已停止)。 将此项设置为false丢弃(或路由到死信队列)此类消息。spring-doc.cadn.net.cn

刻度线
刻度线

stopConsumerMinInterval
(最小停止间隔)spring-doc.cadn.net.cn

在检测到空闲使用者时,自最后一个使用者停止以来,在停止使用者之前必须经过的时间(以毫秒为单位)。 请参阅 侦听器并发。 默认值:60000(1 分钟)。spring-doc.cadn.net.cn

刻度线

streamConverter
(不适用)spring-doc.cadn.net.cn

一个StreamMessageConverter将本机 Stream 消息转换为 Spring AMQP 消息。spring-doc.cadn.net.cn

刻度线

taskExecutor
(任务执行程序)spring-doc.cadn.net.cn

对 Spring 的引用TaskExecutor(或标准 JDK 1.5+Executor) 来执行侦听器调用程序。 默认值为SimpleAsyncTaskExecutor,使用内部管理的线程。spring-doc.cadn.net.cn

刻度线
刻度线

taskScheduler
(任务计划程序)spring-doc.cadn.net.cn

使用 DMLC 时,调度程序用于在 'monitorInterval' 运行监视任务。spring-doc.cadn.net.cn

刻度线

transactionManager
(事务管理器)spring-doc.cadn.net.cn

用于侦听器作的外部事务管理器。 也是对channelTransacted- 如果Channel是 transacted,则其事务将与外部事务同步。spring-doc.cadn.net.cn

刻度线
刻度线

4.1.18. 侦听器并发

SimpleMessageListenerContainer (简单消息监听器容器)

默认情况下,侦听器容器启动一个从队列接收消息的使用者。spring-doc.cadn.net.cn

在检查上一节中的表时,您可以看到许多控制并发的属性和特性。 最简单的是concurrentConsumers,这将创建并发处理消息的 (固定) 数量的使用者。spring-doc.cadn.net.cn

在版本 1.3.0 之前,这是唯一可用的设置,必须停止并重新启动容器才能更改设置。spring-doc.cadn.net.cn

从 1.3.0 版本开始,您现在可以动态调整concurrentConsumers财产。 如果在容器运行时更改了此设置,则会根据需要添加或删除使用者,以适应新设置。spring-doc.cadn.net.cn

此外,一个名为maxConcurrentConsumers,容器会根据工作负载动态调整并发。 这与四个附加属性结合使用:consecutiveActiveTrigger,startConsumerMinInterval,consecutiveIdleTriggerstopConsumerMinInterval. 使用默认设置时,增加使用者的算法工作如下:spring-doc.cadn.net.cn

如果maxConcurrentConsumers未达到,并且现有使用者连续 10 个周期处于活动状态,并且自最后一个使用者启动以来已过去至少 10 秒,则会启动新的使用者。 如果使用者在batchSize * receiveTimeout毫秒。spring-doc.cadn.net.cn

使用默认设置时,减少使用者的算法工作如下:spring-doc.cadn.net.cn

如果有多个concurrentConsumers正在运行,并且使用者检测到 10 个连续超时 (空闲),并且最后一个使用者至少在 60 秒前停止,则使用者将停止。 超时取决于receiveTimeoutbatchSize性能。 如果 Consumer 在batchSize * receiveTimeout毫秒。 因此,使用默认超时(1 秒)和batchSize在 4 个实例中,在 40 秒的空闲时间后考虑停止使用者(4 次超时对应于 1 次空闲检测)。spring-doc.cadn.net.cn

实际上,只有当整个容器空闲一段时间时,才能停止消费者。 这是因为 broker 在所有活动使用者之间共享其工作。

每个使用者都使用一个通道,而不管配置的队列数量如何。spring-doc.cadn.net.cn

从版本 2.0 开始,concurrentConsumersmaxConcurrentConsumers属性可以使用concurrencyproperty — 例如2-4.spring-doc.cadn.net.cn

DirectMessageListenerContainer

对于此容器,并发性基于配置的队列和consumersPerQueue. 每个队列的每个使用者都使用一个单独的通道,并发性由 rabbit 客户端库控制。 默认情况下,在撰写本文时,它使用DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2线程。spring-doc.cadn.net.cn

您可以配置taskExecutor以提供所需的最大并发性。spring-doc.cadn.net.cn

4.1.19. 独占消费者

从版本 1.3 开始,您可以使用单个独占使用者配置侦听器容器。 这可以防止其他容器从队列中使用,直到当前使用者被取消。 此类容器的并发性必须为1.spring-doc.cadn.net.cn

当使用独占使用者时,其他容器会尝试根据recoveryInterval属性并记录一个WARN消息(如果尝试失败)。spring-doc.cadn.net.cn

4.1.20. 侦听器容器队列

版本 1.3 引入了许多改进,用于处理侦听器容器中的多个队列。spring-doc.cadn.net.cn

容器最初可以配置为侦听零个队列。 可以在运行时添加和删除队列。 这SimpleMessageListenerContainer在处理了任何预取的消息后,回收 (取消和重新创建) 所有使用者。 这DirectMessageListenerContainer为每个队列创建/取消单个使用者,而不会影响其他队列上的使用者。 请参阅 Javadoc 以获取addQueues,addQueueNames,removeQueuesremoveQueueNames方法。spring-doc.cadn.net.cn

如果并非所有队列都可用,则容器会尝试每 60 秒被动声明(并使用)一次缺失的队列。spring-doc.cadn.net.cn

此外,如果使用方收到来自代理的取消(例如,如果队列被删除),则使用方会尝试恢复,并且恢复的使用方将继续处理来自任何其他已配置队列的消息。 以前,一个队列上的 cancel 会取消整个使用者,最终,容器会因缺少队列而停止。spring-doc.cadn.net.cn

如果您希望永久删除队列,则应在删除到队列之前或之后更新容器,以避免将来尝试从中消费。spring-doc.cadn.net.cn

4.1.21. 弹性:从错误和 Broker 故障中恢复

Spring AMQP 提供的一些关键(也是最流行的)高级功能与在发生协议错误或代理故障时的恢复和自动重新连接有关。 我们已经在本指南中看到了所有相关组件,但将它们全部汇集在这里并单独介绍功能和恢复场景应该会有所帮助。spring-doc.cadn.net.cn

主要的重新连接功能由CachingConnectionFactory本身。 使用RabbitAdmin自动声明功能。 此外,如果您关心保证送达,您可能还需要使用channelTransacted标志输入RabbitTemplateSimpleMessageListenerContainerAcknowledgeMode.AUTO(如果您自己执行 ACK,则为 MANUAL)SimpleMessageListenerContainer.spring-doc.cadn.net.cn

自动声明交换、队列和绑定

RabbitAdmin组件可以在启动时声明 exchanges、queues 和 bindings。 它通过ConnectionListener. 因此,如果 broker 在启动时不存在,则无关紧要。 第一次Connection(例如, 通过发送消息)将触发侦听器并应用 Admin 功能。 在侦听器中执行 auto 声明的另一个好处是,如果由于任何原因(例如, Broker Death、Network Glitch 等),则在重新建立连接时会再次应用它们。spring-doc.cadn.net.cn

以这种方式声明的队列必须具有固定名称 — 要么显式声明,要么由框架为AnonymousQueue实例。 匿名队列是非持久队列、独占队列和自动删除队列。
仅当CachingConnectionFactorycache 模式为CHANNEL(默认值)。 存在此限制是因为独占队列和自动删除队列绑定到连接。

从版本 2.2.2 开始,RabbitAdmin将检测DeclarableCustomizer并在实际处理声明之前应用该函数。 这很有用,例如,在框架中具有一等支持之前设置新参数 (property)。spring-doc.cadn.net.cn

@Bean
public DeclarableCustomizer customizer() {
    return dec -> {
        if (dec instanceof Queue && ((Queue) dec).getName().equals("my.queue")) {
            dec.addArgument("some.new.queue.argument", true);
        }
        return dec;
    };
}

在不提供对Declarablebean 定义。spring-doc.cadn.net.cn

同步作失败和重试选项

如果在使用RabbitTemplate(例如),Spring AMQP 会抛出一个AmqpException(通常,但并非总是,AmqpIOException). 我们不会试图隐藏存在问题的事实,因此您必须能够捕获并响应异常。 如果您怀疑连接丢失(这不是您的错),最简单的方法是再次尝试该作。 你可以手动执行此作,也可以考虑使用 Spring Retry 来处理重试(命令式或声明式)。spring-doc.cadn.net.cn

Spring Retry 提供了几个 AOP 拦截器,并且具有很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法等)。 Spring AMQP 还提供了一些方便的工厂 bean,用于以方便的形式为 AMQP 用例创建 Spring 重试拦截器,并具有可用于实现自定义恢复逻辑的强类型回调接口。 请参阅 Javadoc 和StatefulRetryOperationsInterceptorStatelessRetryOperationsInterceptor了解更多详情。 如果没有事务,或者事务是在重试回调中启动的,则无状态重试是合适的。 请注意,无状态重试比有状态重试更易于配置和分析,但如果必须回滚或肯定要回滚正在进行的事务,则通常不合适。 在事务中间丢弃的连接应具有与回滚相同的效果。 因此,对于事务在堆栈上层启动的重新连接,有状态重试通常是最佳选择。 有状态重试需要一种机制来唯一标识消息。 最简单的方法是让发送者在MessageIdmessage 属性。 提供的消息转换器提供了一个选项来执行此作:您可以将createMessageIdstrue. 否则,您可以注入MessageKeyGeneratorimplementation 复制到拦截器中。 密钥生成器必须为每条消息返回一个唯一的密钥。 在版本 2.0 之前的版本中,MissingMessageIdAdvice。 它启用了没有messageId属性设置为仅重试一次(忽略重试设置)。 此建议不再提供,因为spring-retry版本 1.2 中,其功能内置于 Interceptor 和 Message Listener 容器中。spring-doc.cadn.net.cn

为了向后兼容,默认情况下(重试一次后),消息 ID 为 null 的消息被视为对使用者(使用者已停止)致命。 要复制MissingMessageIdAdvice中,您可以设置statefulRetryFatalWithNullMessageIdproperty 设置为false在 Listener 容器上。 使用该设置,使用者将继续运行,消息将被拒绝(重试一次后)。 它被丢弃或路由到死信队列(如果已配置)。

从版本 1.3 开始,提供了一个构建器 API 来帮助使用 Java 组装这些拦截器(在@Configuration类)。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}

只能以这种方式配置重试功能的子集。 更高级的功能需要配置RetryTemplate作为 Spring bean 进行。 有关可用策略及其配置的完整信息,请参阅 Spring 重试 Javadocspring-doc.cadn.net.cn

使用 Batch 侦听器重试

建议不要使用批处理侦听器配置重试,除非该批处理是由创建者在单个记录中创建的。 请参阅Batched Messages 以了解有关使用者和创建者创建的批处理的信息。 对于使用者创建的批处理,框架不知道批处理中的哪条消息导致了失败,因此在重试用尽后无法恢复。 对于创建者创建的批处理,由于实际上只有一条消息失败,因此可以恢复整个消息。 应用程序可能希望通知自定义恢复程序在批处理中发生故障的位置,可能通过设置引发的异常的 index 属性。spring-doc.cadn.net.cn

批处理侦听器的重试 recoverer 必须实现MessageBatchRecoverer.spring-doc.cadn.net.cn

消息侦听器和异步情况

如果MessageListener由于业务异常而失败,则异常由 Message Listener 容器处理,然后该容器返回侦听另一条消息。 如果失败是由断开的连接引起的(不是业务异常),则必须取消并重新启动为侦听器收集消息的使用者。 这SimpleMessageListenerContainer无缝处理此问题,并留下一个日志来说明侦听器正在重新启动。 事实上,它无休止地循环,试图重新启动消费者。 只有当消费者确实行为非常糟糕时,它才会放弃。 一个副作用是,如果 broker 在容器启动时关闭,它会一直尝试,直到可以建立连接。spring-doc.cadn.net.cn

与协议错误和断开连接相反,业务异常处理可能需要更多的思考和一些自定义配置,尤其是在使用事务或容器 ack 时。 在 2.8.x 之前,RabbitMQ 没有死信行为的定义。 因此,默认情况下,由于业务异常而被拒绝或回滚的消息可以无限地重新传递。 要限制客户端的重新投放次数,一种选择是StatefulRetryOperationsInterceptor在侦听器的通知链中。 拦截器可以具有实现自定义死信作的恢复回调 — 任何适合您的特定环境的方法。spring-doc.cadn.net.cn

另一种选择是将容器的defaultRequeueRejectedproperty 设置为false. 这会导致所有失败的消息都被丢弃。 当使用 RabbitMQ 2.8.x 或更高版本时,这也有助于将消息传送到死信交换。spring-doc.cadn.net.cn

或者,您可以抛出AmqpRejectAndDontRequeueException. 这样做可以防止消息重新排队,而不管defaultRequeueRejected财产。spring-doc.cadn.net.cn

从版本 2.1 开始,ImmediateRequeueAmqpException来执行完全相反的逻辑:消息将被重新排队,而不管defaultRequeueRejected财产。spring-doc.cadn.net.cn

通常,会结合使用这两种技术。 您可以使用StatefulRetryOperationsInterceptor在通知链中,使用MessageRecoverer这会抛出一个AmqpRejectAndDontRequeueException. 这MessageRecover在所有重试都已用尽时调用。 这RejectAndDontRequeueRecoverer确实如此。 默认的MessageRecoverer使用错误消息并发出WARN消息。spring-doc.cadn.net.cn

从版本 1.3 开始,新的RepublishMessageRecoverer,以允许在重试用尽后发布失败的消息。spring-doc.cadn.net.cn

当 recoverer 使用最终异常时,该消息将被确认,并且不会由 broker 发送到死信交换(如果已配置)。spring-doc.cadn.net.cn

什么时候RepublishMessageRecoverer在消费者端使用,收到的消息有deliveryModereceivedDeliveryModemessage 属性。 在这种情况下,deliveryModenull. 这意味着NON_PERSISTENTbroker 上的 delivery 模式。 从版本 2.0 开始,您可以配置RepublishMessageRecoverer对于deliveryMode设置为重新发布的消息(如果是null. 默认情况下,它使用MessageProperties默认值 -MessageDeliveryMode.PERSISTENT.

以下示例演示如何设置RepublishMessageRecoverer作为 recoverer:spring-doc.cadn.net.cn

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

RepublishMessageRecoverer发布消息时,消息标头中包含其他信息,例如异常消息、堆栈跟踪、原始交换和路由密钥。 可以通过创建子类并覆盖来添加其他 HeadersadditionalHeaders(). 这deliveryMode(或任何其他属性)也可以在additionalHeaders(),如下例所示:spring-doc.cadn.net.cn

RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") {

    protected Map<? extends String, ? extends Object> additionalHeaders(Message message, Throwable cause) {
        message.getMessageProperties()
            .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
        return null;
    }

};

从版本 2.0.5 开始,如果堆栈跟踪太大,则可能会截断堆栈跟踪;这是因为所有标头都必须适合单个帧。 默认情况下,如果堆栈跟踪导致可用于其他标头的字节少于 20,000 字节 ('headroom'),则它将被截断。 这可以通过设置 recoverer 的frameMaxHeadroomproperty,如果你需要更多或更少的空间来存储其他标头。 从版本 2.1.13、2.2.3 开始,异常消息包含在此计算中,并且将使用以下算法最大化堆栈跟踪量:spring-doc.cadn.net.cn

  • 如果单独的堆栈跟踪会超过限制,则异常消息标头将被截断为 97 字节加…​并且堆栈跟踪也被截断。spring-doc.cadn.net.cn

  • 如果堆栈跟踪很小,则消息将被截断(加上…​) 以适应可用字节(但堆栈跟踪本身中的消息被截断为 97 字节加…​).spring-doc.cadn.net.cn

每当发生任何类型的截断时,都会记录原始异常以保留完整信息。 评估在增强标头后执行,以便可以在表达式中使用异常类型等信息。spring-doc.cadn.net.cn

从版本 2.4.8 开始,错误交换和路由密钥可以作为 SPEL 表达式提供,其中Message作为评估的根对象。spring-doc.cadn.net.cn

从版本 2.3.3 开始,一个新的子类RepublishMessageRecovererWithConfirms提供;这支持两种样式的发布者确认,并将在返回之前等待确认(如果未确认或返回消息,则引发异常)。spring-doc.cadn.net.cn

如果确认类型为CORRELATED中,子类还将检测是否返回消息并抛出AmqpMessageReturnedException;如果发布被否定确认,它将抛出一个AmqpNackReceivedException.spring-doc.cadn.net.cn

如果确认类型为SIMPLE,子类将调用waitForConfirmsOrDie方法。spring-doc.cadn.net.cn

请参阅 Publisher Confirms and Returns 以了解有关确认和返回的更多信息。spring-doc.cadn.net.cn

从版本 2.1 开始,ImmediateRequeueMessageRecoverer以抛出ImmediateRequeueAmqpException,它通知侦听器容器对当前失败的消息重新排队。spring-doc.cadn.net.cn

Spring 重试的异常分类

Spring Retry 在确定哪些异常可以调用重试方面具有很大的灵活性。 默认配置将对所有异常重试。 鉴于用户异常被包装在ListenerExecutionFailedException,我们需要确保 classification 检查异常原因。 默认分类器仅查看顶级异常。spring-doc.cadn.net.cn

从 Spring Retry 1.0.3 开始,BinaryExceptionClassifier具有一个名为traverseCauses(默认:false). 什么时候true,它会遍历异常原因,直到找到匹配项或没有原因。spring-doc.cadn.net.cn

要使用此分类器进行重试,您可以使用SimpleRetryPolicy使用采用最大尝试次数的构造函数创建,MapException实例和布尔值 (traverseCauses) 并将此策略注入到RetryTemplate.spring-doc.cadn.net.cn

4.1.22. 多个 Broker(或集群)支持

版本 2.3 在单个应用程序与多个代理或代理集群之间通信时增加了更多便利。 从使用者的角度来看,主要好处是基础设施可以自动将自动声明的队列与适当的代理相关联。spring-doc.cadn.net.cn

最好用一个例子来说明这一点:spring-doc.cadn.net.cn

@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
public class Application {

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

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

    @Bean
    CachingConnectionFactory cf2() {
        return new CachingConnectionFactory("otherHost");
    }

    @Bean
    CachingConnectionFactory cf3() {
        return new CachingConnectionFactory("thirdHost");
    }

    @Bean
    SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1,
            CachingConnectionFactory cf2, CachingConnectionFactory cf3) {

        SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
        rcf.setDefaultTargetConnectionFactory(cf1);
        rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3));
        return rcf;
    }

    @Bean("factory1-admin")
    RabbitAdmin admin1(CachingConnectionFactory cf1) {
        return new RabbitAdmin(cf1);
    }

    @Bean("factory2-admin")
    RabbitAdmin admin2(CachingConnectionFactory cf2) {
        return new RabbitAdmin(cf2);
    }

    @Bean("factory3-admin")
    RabbitAdmin admin3(CachingConnectionFactory cf3) {
        return new RabbitAdmin(cf3);
    }

    @Bean
    public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
        return new RabbitListenerEndpointRegistry();
    }

    @Bean
    public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) {
        MultiRabbitListenerAnnotationBeanPostProcessor postProcessor
                = new MultiRabbitListenerAnnotationBeanPostProcessor();
        postProcessor.setEndpointRegistry(registry);
        postProcessor.setContainerFactoryBeanName("defaultContainerFactory");
        return postProcessor;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf1);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf2);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf3);
        return factory;
    }

    @Bean
    RabbitTemplate template(SimpleRoutingConnectionFactory rcf) {
        return new RabbitTemplate(rcf);
    }

    @Bean
    ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) {
        return new ConnectionFactoryContextWrapper(rcf);
    }

}

@Component
class Listeners {

    @RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1")
    public void listen1(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2")
    public void listen2(String in) {

    }

    @RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3")
    public void listen3(String in) {

    }

}

如你所见,我们已经声明了 3 组基础设施(连接工厂、管理员、容器工厂)。 如前所述,@RabbitListener可以定义要使用的容器工厂;在这种情况下,他们还使用queuesToDeclare这会导致在 broker 上声明队列(如果它不存在)。 通过将RabbitAdmin具有约定的 bean<container-factory-name>-admin,基础架构能够确定哪个管理员应该声明队列。 这也适用于bindings = @QueueBinding(…​)因此,也将声明交换和约束。 它不适用于queues,因为这期望队列已经存在。spring-doc.cadn.net.cn

在生产商方面,一个方便的ConnectionFactoryContextWrapper类,要使用RoutingConnectionFactory(请参阅 Routing Connection Factory) 更简单。spring-doc.cadn.net.cn

如您在上面看到的,SimpleRoutingConnectionFactory添加了路由键的 Beanone,twothree. 还有一个RabbitTemplate使用该工厂。 以下是使用该模板和包装器路由到其中一个代理集群的示例。spring-doc.cadn.net.cn

@Bean
public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) {
    return args -> {
        wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1"));
        wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2"));
        wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3"));
    };
}

4.1.23. 调试

Spring AMQP 提供了广泛的日志记录,尤其是在DEBUG水平。spring-doc.cadn.net.cn

如果您希望监控应用程序和代理之间的 AMQP 协议,您可以使用 WireShark 等工具,该工具具有用于解码协议的插件。 或者,RabbitMQ Java 客户端附带了一个非常有用的类,称为Tracer. 当作为main,默认情况下,它侦听端口 5673 并连接到 localhost 上的端口 5672。 您可以运行它并更改连接工厂配置以连接到 localhost 上的端口 5673。 它在控制台上显示解码的协议。 请参阅TracerJavadoc 了解更多信息。spring-doc.cadn.net.cn

4.2. 使用 RabbitMQ Stream 插件

版本 2.4 引入了对 RabbitMQ Stream Plugin 的 Java 客户端的初始支持。spring-doc.cadn.net.cn

添加spring-rabbit-stream依赖项添加到项目中:spring-doc.cadn.net.cn

例 3.maven
<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit-stream</artifactId>
  <version>3.0.14</version>
</dependency>
示例 4.Gradle
compile 'org.springframework.amqp:spring-rabbit-stream:3.0.14'

您可以像往常一样预置队列,使用RabbitAdminbean,使用QueueBuilder.stream()方法来指定队列类型。 例如:spring-doc.cadn.net.cn

@Bean
Queue stream() {
    return QueueBuilder.durable("stream.queue1")
            .stream()
            .build();
}

但是,这仅在您还使用非流组件(例如SimpleMessageListenerContainerDirectMessageListenerContainer),因为在打开 AMQP 连接时,会触发管理员来声明定义的 bean。 如果您的应用程序仅使用流组件,或者您希望使用高级流配置功能,则应配置StreamAdmin相反:spring-doc.cadn.net.cn

@Bean
StreamAdmin streamAdmin(Environment env) {
    return new StreamAdmin(env, sc -> {
        sc.stream("stream.queue1").maxAge(Duration.ofHours(2)).create();
        sc.stream("stream.queue2").create();
    });
}

请参阅 RabbitMQ 文档,了解有关StreamCreator.spring-doc.cadn.net.cn

4.2.1. 发送消息

RabbitStreamTemplate提供了RabbitTemplate(AMQP) 功能。spring-doc.cadn.net.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;

}

RabbitStreamTemplateimplementation 具有以下构造函数和属性:spring-doc.cadn.net.cn

例 6.RabbitStreamTemplate
public RabbitStreamTemplate(Environment environment, String streamName) {
}

public void setMessageConverter(MessageConverter messageConverter) {
}

public void setStreamConverter(StreamMessageConverter streamConverter) {
}

public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) {
}

MessageConverter用于convertAndSend将对象转换为 Spring AMQP 的方法Message.spring-doc.cadn.net.cn

StreamMessageConverter用于从 Spring AMQP 转换Message到本机流Message.spring-doc.cadn.net.cn

您还可以发送本机流Messages 直接;使用messageBuilder()方法提供对Producer的消息构建器。spring-doc.cadn.net.cn

ProducerCustomizer提供了一种在构建 Producer 之前对其进行自定义的机制。spring-doc.cadn.net.cn

请参阅 Java 客户端文档,了解如何自定义EnvironmentProducer.spring-doc.cadn.net.cn

从版本 3.0 开始,方法返回类型为CompletableFuture而不是ListenableFuture.

4.2.2. 接收消息

异步消息接收由StreamListenerContainer(以及StreamRabbitListenerContainerFactory使用@RabbitListener).spring-doc.cadn.net.cn

侦听器容器需要一个Environment以及单个流名称。spring-doc.cadn.net.cn

你可以接收 Spring AMQPMessages 使用经典MessageListener,或者您可以接收原生流Message使用新接口:spring-doc.cadn.net.cn

public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}

有关支持的属性的信息,请参阅 Message Listener Container Configurationspring-doc.cadn.net.cn

与模板类似,容器有一个ConsumerCustomizer财产。spring-doc.cadn.net.cn

请参阅 Java 客户端文档,了解如何自定义EnvironmentConsumer.spring-doc.cadn.net.cn

使用@RabbitListener,配置StreamRabbitListenerContainerFactory;此时,大多数@RabbitListener属性 (concurrency等)将被忽略。只id,queues,autoStartupcontainerFactory受支持。 另外queues只能包含一个流名称。spring-doc.cadn.net.cn

4.2.3. 示例

@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
    RabbitStreamTemplate template = new RabbitStreamTemplate(env, "test.stream.queue1");
    template.setProducerCustomizer((name, builder) -> builder.name("test"));
    return template;
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> rabbitListenerContainerFactory(Environment env) {
    return new StreamRabbitListenerContainerFactory(env);
}

@RabbitListener(queues = "test.stream.queue1")
void listen(String in) {
    ...
}

@Bean
RabbitListenerContainerFactory<StreamListenerContainer> nativeFactory(Environment env) {
    StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(env);
    factory.setNativeListener(true);
    factory.setConsumerCustomizer((id, builder) -> {
        builder.name("myConsumer")
                .offset(OffsetSpecification.first())
                .manualTrackingStrategy();
    });
    return factory;
}

@RabbitListener(id = "test", queues = "test.stream.queue2", containerFactory = "nativeFactory")
void nativeMsg(Message in, Context context) {
    ...
    context.storeOffset();
}

@Bean
Queue stream() {
    return QueueBuilder.durable("test.stream.queue1")
            .stream()
            .build();
}

@Bean
Queue stream() {
    return QueueBuilder.durable("test.stream.queue2")
            .stream()
            .build();
}

版本 2.4.5 添加了adviceChain属性设置为StreamListenerContainer(及其工厂)。 还提供了一个新的工厂 Bean 来创建无状态重试拦截器,其中包含可选的StreamMessageRecoverer用于使用 Raw Stream 消息。spring-doc.cadn.net.cn

@Bean
public StreamRetryOperationsInterceptorFactoryBean sfb(RetryTemplate retryTemplate) {
    StreamRetryOperationsInterceptorFactoryBean rfb =
            new StreamRetryOperationsInterceptorFactoryBean();
    rfb.setRetryOperations(retryTemplate);
    rfb.setStreamMessageRecoverer((msg, context, throwable) -> {
        ...
    });
    return rfb;
}
此容器不支持有状态重试。

4.2.4. 超级流

Super Stream 是分区流的抽象概念,通过将多个 stream 队列绑定到具有参数的 exchange 来实现x-super-stream: true.spring-doc.cadn.net.cn

供应

为方便起见,可以通过定义SuperStream.spring-doc.cadn.net.cn

@Bean
SuperStream superStream() {
    return new SuperStream("my.super.stream", 3);
}

RabbitAdmin检测到此 Bean 并将声明 Exchange (my.super.stream) 和 3 个队列(分区)-my.super-stream-n哪里n0,1,2绑定的路由密钥等于n.spring-doc.cadn.net.cn

如果您还希望通过 AMQP 向 Exchange 发布信息,则可以提供自定义路由密钥:spring-doc.cadn.net.cn

@Bean
SuperStream superStream() {
    return new SuperStream("my.super.stream", 3, (q, i) -> IntStream.range(0, i)
					.mapToObj(j -> "rk-" + j)
					.collect(Collectors.toList()));
}

键的数量必须等于分区的数量。spring-doc.cadn.net.cn

使用 SuperStream 进行制作

您必须添加superStreamRoutingFunctionRabbitStreamTemplate:spring-doc.cadn.net.cn

@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
    RabbitStreamTemplate template = new RabbitStreamTemplate(env, "stream.queue1");
    template.setSuperStreamRouting(message -> {
        // some logic to return a String for the client's hashing algorithm
    });
    return template;
}

您还可以使用RabbitTemplate.spring-doc.cadn.net.cn

使用具有单个活跃使用者的 Super Streams

调用superStream方法在 Super Stream 上启用单个活跃的 Consumer 的用户。spring-doc.cadn.net.cn

@Bean
StreamListenerContainer container(Environment env, String name) {
    StreamListenerContainer container = new StreamListenerContainer(env);
    container.superStream("ss.sac", "myConsumer", 3); // concurrency = 3
    container.setupMessageListener(msg -> {
        ...
    });
    container.setConsumerCustomizer((id, builder) -> builder.offset(OffsetSpecification.last()));
    return container;
}
此时,当并发数大于 1 时,实际的并发数由Environment;要实现完全并发,请将环境的maxConsumersByConnection设置为 1。 请参阅配置环境

4.2.5. 千分尺观察

现在支持使用 Micrometer 进行观察,从 3.0.5 版本开始,对于RabbitStreamTemplate以及流侦听器容器。 该容器现在还支持 Micrometer 计时器(未启用观察时)。spring-doc.cadn.net.cn

设置observationEnabled在每个组件上以便于观察;这将禁用 Micrometer Timers,因为计时器现在将随每个观测一起管理。 使用带注解的侦听器时,将observationEnabled在集装箱工厂。spring-doc.cadn.net.cn

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

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

默认实现会添加name标签,以及listener.id标记。spring-doc.cadn.net.cn

你可以子类化DefaultRabbitStreamTemplateObservationConventionDefaultStreamRabbitListenerObservationConvention或提供全新的实现。spring-doc.cadn.net.cn

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

4.3. 记录子系统 AMQP Appender

该框架为一些流行的日志记录子系统提供了 logging appender:spring-doc.cadn.net.cn

appender 是使用 logging 子系统的正常机制配置的,可用属性在以下部分中指定。spring-doc.cadn.net.cn

4.3.1. 通用属性

所有 appenders 都提供以下属性:spring-doc.cadn.net.cn

表 4.常见的 Appender 属性
财产 违约 描述
 exchangeName
 logs

要将日志事件发布到的 Exchange 的名称。spring-doc.cadn.net.cn

 exchangeType
 topic

要将日志事件发布到的 exchange 的类型 — 仅当 appender 声明 exchange 时才需要。 看declareExchange.spring-doc.cadn.net.cn

 routingKeyPattern
 %c.%p

用于生成路由密钥的 Logging subsystem 模式格式。spring-doc.cadn.net.cn

 applicationId

应用程序 ID — 如果模式包含%X{applicationId}.spring-doc.cadn.net.cn

 senderPoolSize
 2

用于发布日志事件的线程数。spring-doc.cadn.net.cn

 maxSenderRetries
 30

如果 Broker 不可用或出现其他错误,则重试发送消息的次数。 重试延迟如下:N ^ log(N)哪里N是重试编号。spring-doc.cadn.net.cn

 addresses

以下格式的代理地址列表(以逗号分隔):host:port[,host:port]*-重写hostport.spring-doc.cadn.net.cn

 host
 localhost

要连接到的 RabbitMQ 主机。spring-doc.cadn.net.cn

 port
 5672

要连接的 RabbitMQ 端口。spring-doc.cadn.net.cn

 virtualHost
 /

要连接到的 RabbitMQ 虚拟主机。spring-doc.cadn.net.cn

 username
 guest

RabbitMQ 用户。spring-doc.cadn.net.cn

 password
 guest

此用户的 RabbitMQ 密码。spring-doc.cadn.net.cn

 useSsl
 false

是否使用 SSL 进行 RabbitMQ 连接。 看RabbitConnectionFactoryBean和配置 SSLspring-doc.cadn.net.cn

 verifyHostname
 true

为 TLS 连接启用服务器主机名验证。 看RabbitConnectionFactoryBean和配置 SSLspring-doc.cadn.net.cn

 sslAlgorithm
 null

要使用的 SSL 算法。spring-doc.cadn.net.cn

 sslPropertiesLocation
 null

SSL 属性文件的位置。spring-doc.cadn.net.cn

 keyStore
 null

密钥库的位置。spring-doc.cadn.net.cn

 keyStorePassphrase
 null

密钥库的密码。spring-doc.cadn.net.cn

 keyStoreType
 JKS

密钥库类型。spring-doc.cadn.net.cn

 trustStore
 null

信任库的位置。spring-doc.cadn.net.cn

 trustStorePassphrase
 null

信任库的密码。spring-doc.cadn.net.cn

 trustStoreType
 JKS

信任库类型。spring-doc.cadn.net.cn

 saslConfig
 null (RabbitMQ client default applies)

saslConfig- 请参阅 Javadoc 以获取RabbitUtils.stringToSaslConfig以获取有效值。spring-doc.cadn.net.cn

 contentType
 text/plain

content-type日志消息的属性。spring-doc.cadn.net.cn

 contentEncoding

content-encoding日志消息的属性。spring-doc.cadn.net.cn

 declareExchange
 false

是否在此 appender 启动时声明配置的 exchange。 另请参阅durableautoDelete.spring-doc.cadn.net.cn

 durable
 true

什么时候declareExchangetrue,则 durable 标志将设置为此值。spring-doc.cadn.net.cn

 autoDelete
 false

什么时候declareExchangetrue,则 auto-delete 标志将设置为此值。spring-doc.cadn.net.cn

 charset
 null

转换时使用的字符集Stringbyte[]. 默认值:null(使用系统默认字符集)。 如果当前平台不支持该字符集,我们将回退到使用系统字符集。spring-doc.cadn.net.cn

 deliveryMode
 PERSISTENT

PERSISTENTNON_PERSISTENT来确定 RabbitMQ 是否应保留消息。spring-doc.cadn.net.cn

 generateId
 false

用于确定messageId属性设置为唯一值。spring-doc.cadn.net.cn

 clientConnectionProperties
 null

以逗号分隔的key:value对,用于自定义客户端属性到 RabbitMQ 连接。spring-doc.cadn.net.cn

 addMdcAsHeaders
 true

在引入此属性之前,MDC 属性始终添加到 RabbitMQ 消息头中。 这可能会导致大型 MDC 出现问题,因为 RabbitMQ 的所有标头的缓冲区大小有限,并且此缓冲区非常小。 引入此属性是为了避免在大型 MDC 的情况下出现问题。 默认情况下,此值设置为true以实现向后兼容性。 这false关闭将 MDC 序列化为标头。 请注意,JsonLayout默认情况下,将 MDC 添加到邮件中。spring-doc.cadn.net.cn

4.3.2. Log4j 2 附加程序

以下示例显示如何配置 Log4j 2 appender:spring-doc.cadn.net.cn

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        addresses="foo:5672,bar:5672" user="guest" password="guest" virtualHost="/"
        exchange="log4j2" exchangeType="topic" declareExchange="true" durable="true" autoDelete="false"
        applicationId="myAppId" routingKeyPattern="%X{applicationId}.%c.%p"
        contentType="text/plain" contentEncoding="UTF-8" generateId="true" deliveryMode="NON_PERSISTENT"
        charset="UTF-8"
        senderPoolSize="3" maxSenderRetries="5"
        addMdcAsHeaders="false">
    </RabbitMQ>
</Appenders>

从版本 1.6.10 和 1.7.3 开始,默认情况下,log4j2 appender 将消息发布到调用线程上的 RabbitMQ。 这是因为默认情况下,Log4j 2 不会创建线程安全事件。 如果代理宕机,则maxSenderRetries用于重试,重试之间没有延迟。 如果您希望恢复以前在单独的线程上发布消息的行为 (senderPoolSize),则可以设置asyncproperty 设置为true. 但是,您还需要将 Log4j 2 配置为使用DefaultLogEventFactory而不是ReusableLogEventFactory. 一种方法是将 system 属性-Dlog4j2.enable.threadlocals=false. 如果您使用异步发布和ReusableLogEventFactory,事件很有可能由于串扰而被损坏。spring-doc.cadn.net.cn

4.3.3. Logback Appender

以下示例显示如何配置 logback appender:spring-doc.cadn.net.cn

<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
    <layout>
        <pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
    </layout>
    <addresses>foo:5672,bar:5672</addresses>
    <abbreviation>36</abbreviation>
    <includeCallerData>false</includeCallerData>
    <applicationId>myApplication</applicationId>
    <routingKeyPattern>%property{applicationId}.%c.%p</routingKeyPattern>
    <generateId>true</generateId>
    <charset>UTF-8</charset>
    <durable>false</durable>
    <deliveryMode>NON_PERSISTENT</deliveryMode>
    <declareExchange>true</declareExchange>
    <addMdcAsHeaders>false</addMdcAsHeaders>
</appender>

从版本 1.7.1 开始, LogbackAmqpAppender提供了一个includeCallerData选项,即false默认情况下。 提取调用者数据可能相当昂贵,因为 log event 必须创建一个 throwable 并检查它以确定调用位置。 因此,默认情况下,在将事件添加到事件队列时,不会提取与事件关联的调用方数据。 您可以通过设置includeCallerDataproperty 设置为true.spring-doc.cadn.net.cn

从版本 2.0.0 开始,LogbackAmqpAppender支持使用encoder选择。 这encoderlayout选项是互斥的。spring-doc.cadn.net.cn

4.3.4. 自定义消息

默认情况下,AMQP 附加程序填充以下消息属性:spring-doc.cadn.net.cn

此外,它们还使用以下值填充标头:spring-doc.cadn.net.cn

每个 appender 都可以被子类化,从而允许您在发布之前修改消息。 以下示例显示如何自定义日志消息:spring-doc.cadn.net.cn

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    public Message postProcessMessageBeforeSend(Message message, Event event) {
        message.getMessageProperties().setHeader("foo", "bar");
        return message;
    }

}

从 2.2.4 开始,log4j2AmqpAppender可以使用@PluginBuilderFactory并且还扩展了AmqpAppender.Builderspring-doc.cadn.net.cn

@Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true)
public class MyEnhancedAppender extends AmqpAppender {

	public MyEnhancedAppender(String name, Filter filter, Layout<? extends Serializable> layout,
			boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue, String foo, String bar) {
		super(name, filter, layout, ignoreExceptions, manager, eventQueue);

	@Override
	public Message postProcessMessageBeforeSend(Message message, Event event) {
			message.getMessageProperties().setHeader("foo", "bar");
		return message;
	}

	@PluginBuilderFactory
	public static Builder newBuilder() {
		return new Builder();
	}

	protected static class Builder extends AmqpAppender.Builder {

		@Override
		protected AmqpAppender buildInstance(String name, Filter filter, Layout<? extends Serializable> layout,
				boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue) {
			return new MyEnhancedAppender(name, filter, layout, ignoreExceptions, manager, eventQueue);
		}
	}

}

4.3.5. 自定义客户端属性

您可以通过添加字符串属性或更复杂的属性来添加自定义客户端属性。spring-doc.cadn.net.cn

简单字符串属性

每个 appender 都支持将 Client 端属性添加到 RabbitMQ 连接。spring-doc.cadn.net.cn

以下示例显示了如何为 logback 添加自定义客户端属性:spring-doc.cadn.net.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:value对。 键和值不能包含逗号或冒号。spring-doc.cadn.net.cn

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

高级 Logback 技术

您可以将 Logback appender 子类化。 这样,您就可以在建立连接之前修改客户端连接属性。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

public class MyEnhancedAppender extends AmqpAppender {

    private String thing1;

    @Override
    protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
        clientProperties.put("thing1", this.thing1);
    }

    public void setThing1(String thing1) {
        this.thing1 = thing1;
    }

}

然后,您可以添加<thing1>thing2</thing1>logback.xml。spring-doc.cadn.net.cn

对于 String 属性(如前面的示例中所示的属性),可以使用前面的技术。 子类允许添加更丰富的属性(例如添加Map或数字属性)。spring-doc.cadn.net.cn

4.3.6. 提供自定义队列实现

AmqpAppenders使用BlockingQueue将日志记录事件异步发布到 RabbitMQ。 默认情况下,LinkedBlockingQueue被使用。 但是,您可以提供任何类型的自定义BlockingQueue实现。spring-doc.cadn.net.cn

以下示例显示了如何对 Logback 执行此作:spring-doc.cadn.net.cn

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}

Log4j 2 appender 支持使用BlockingQueueFactory,如下例所示:spring-doc.cadn.net.cn

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>

4.4. 示例应用程序

Spring AMQP Samples 项目包括两个示例应用程序。 第一个是一个简单的 “Hello World” 示例,它演示了同步和异步消息接收。 它为了解基本组件提供了一个很好的起点。 第二个示例基于股票交易用例,用于演示实际应用程序中常见的交互类型。 在本章中,我们提供了每个示例的快速演练,以便您可以专注于最重要的组件。 这些示例都是基于 Maven 的,因此您应该能够将它们直接导入到任何 Maven 感知的 IDE(比如 SpringSource Tool Suite)中。spring-doc.cadn.net.cn

4.4.1. “Hello World” 示例

“Hello World” 示例演示了同步和异步消息接收。 您可以导入spring-rabbit-helloworld样例导入 IDE,然后按照下面的讨论进行作。spring-doc.cadn.net.cn

同步示例

src/main/java目录中,导航到org.springframework.amqp.helloworld包。 打开HelloWorldConfiguration类,请注意它包含@Configuration注解,并注意一些@Bean方法级别的 annotations。 这是 Spring 基于 Java 的配置的一个例子。 您可以在此处阅读更多相关信息。spring-doc.cadn.net.cn

下面的清单显示了如何创建 connection factory:spring-doc.cadn.net.cn

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

该配置还包含一个RabbitAdmin,默认情况下,它会查找 exchange、queue 或 binding 类型的任何 bean,然后在代理上声明它们。 事实上,helloWorldQueueHelloWorldConfiguration是一个示例,因为它是Queue.spring-doc.cadn.net.cn

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

@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}

回头看rabbitTemplatebean 配置中,可以看到它的名称为helloWorldQueueset 作为其queue属性(用于接收消息)及其routingKey属性(用于发送消息)。spring-doc.cadn.net.cn

现在我们已经探索了配置,我们可以查看实际使用这些组件的代码。 首先,打开Producer类。 它包含一个main()方法中,SpringApplicationContext已创建。spring-doc.cadn.net.cn

下面的清单显示了main方法:spring-doc.cadn.net.cn

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    amqpTemplate.convertAndSend("Hello World");
    System.out.println("Sent: Hello World");
}

在前面的示例中,AmqpTemplatebean 被检索并用于发送Message. 由于客户端代码应尽可能依赖接口,因此类型为AmqpTemplate而不是RabbitTemplate. 即使HelloWorldConfigurationRabbitTemplate,依赖接口意味着这段代码的可移植性更强(可以独立于代码来更改配置)。 由于convertAndSend()方法,模板将委托给其MessageConverter实例。 在本例中,它使用默认的SimpleMessageConverter,但可以为rabbitTemplatebean,如HelloWorldConfiguration.spring-doc.cadn.net.cn

现在打开Consumer类。 它实际上共享相同的配置基类,这意味着它共享rabbitTemplate豆。 这就是为什么我们在routingKey(用于发送)和queue(用于接收)。 正如我们在AmqpTemplate,则可以将 'routingKey' 参数传递给 send 方法,将 'queue' 参数传递给 receive 方法。 这Consumercode 基本上是 Producer 的镜像,调用receiveAndConvert()而不是convertAndSend().spring-doc.cadn.net.cn

下面的清单显示了Consumer:spring-doc.cadn.net.cn

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}

如果运行Producer然后运行Consumer,您应该会看到Received: Hello World在控制台输出中。spring-doc.cadn.net.cn

异步示例

同步示例演练了同步 Hello World 示例。 本节介绍一个稍微高级一些但功能更强大的选项。 通过一些修改,Hello World 示例可以提供异步接收(也称为消息驱动的 POJO)的示例。 事实上,有一个子包恰好提供了:org.springframework.amqp.samples.helloworld.async.spring-doc.cadn.net.cn

同样,我们从发送方开始。 打开ProducerConfiguration类,请注意,它会创建一个connectionFactory以及rabbitTemplate豆。 这一次,由于配置专用于消息发送端,我们甚至不需要任何队列定义,并且RabbitTemplate仅设置了 'routingKey' 属性。 回想一下,消息是发送到 Exchange,而不是直接发送到队列。 AMQP 默认交换是没有名称的直接交换。 所有队列都绑定到该 default 交换,并将其名称作为路由键。 这就是为什么我们只需要在此处提供路由密钥。spring-doc.cadn.net.cn

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

public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}

由于此示例演示了异步消息接收,因此生产方设计为连续发送消息(如果它是像同步版本一样的每次执行消息的模型,那么它实际上是消息驱动的使用者就不会那么明显)。 负责持续发送消息的组件被定义为ProducerConfiguration. 它配置为每 3 秒运行一次。spring-doc.cadn.net.cn

下面的清单显示了该组件:spring-doc.cadn.net.cn

static class ScheduledProducer {

    @Autowired
    private volatile RabbitTemplate rabbitTemplate;

    private final AtomicInteger counter = new AtomicInteger();

    @Scheduled(fixedRate = 3000)
    public void sendMessage() {
        rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
    }
}

您不需要了解所有细节,因为真正的重点应该是接收方(我们接下来将介绍)。 但是,如果您还不熟悉 Spring 任务调度支持,可以在此处了解更多信息。 简单来说,postProcessorbean 中的ProducerConfiguration向调度程序注册任务。spring-doc.cadn.net.cn

现在我们可以转向接收方。 为了强调消息驱动的 POJO 行为,我们从对消息做出反应的组件开始。 该类称为HelloWorldHandler,如下面的清单所示:spring-doc.cadn.net.cn

public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}

该类是 POJO。 它不扩展任何基类,不实现任何接口,甚至不包含任何导入。 它正在被“适应”到MessageListenerSpring AMQP 的接口MessageListenerAdapter. 然后,您可以在SimpleMessageListenerContainer. 在此示例中,容器是在ConsumerConfiguration类。 你可以在那里看到 POJO 包装在适配器中。spring-doc.cadn.net.cn

下面的清单显示了listenerContainer定义如下:spring-doc.cadn.net.cn

@Bean
public SimpleMessageListenerContainer listenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueName(this.helloWorldQueueName);
    container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
    return container;
}

SimpleMessageListenerContainer是 Spring 生命周期组件,默认情况下,它会自动启动。 如果您查看Consumer类中,你可以看到它的main()方法只包含一个单行引导程序,用于创建ApplicationContext. 制作人的main()method 也是一个单行引导程序,因为其方法带有@Scheduled也会自动启动。 您可以启动ProducerConsumer按任意顺序,您应该每三秒看到一次发送和接收的消息。spring-doc.cadn.net.cn

4.4.2. 股票交易

Stock Trading 示例演示了比 Hello World 示例更高级的消息传送方案。 但是,配置非常相似,只是稍微复杂一些。 由于我们详细介绍了 Hello World 配置,因此,我们在这里重点介绍此示例的不同之处。 有一个服务器将市场数据(股票报价)推送到主题交易所。 然后,客户端可以通过将队列与路由模式(例如app.stock.quotes.nasdaq.*). 此演示的另一个主要功能是由客户端发起并由服务器处理的请求-回复“股票交易”交互。 这涉及到一个私有的replyToqueue 中,该队列由客户端在 order 请求消息本身中发送。spring-doc.cadn.net.cn

服务器的核心配置位于RabbitServerConfiguration类中的org.springframework.amqp.rabbit.stocks.config.server包。 它扩展了AbstractStockAppRabbitConfiguration. 这是定义服务器和客户端通用资源的地方,包括市场数据主题交换(其名称为“app.stock.marketdata”)和服务器为股票交易公开的队列(其名称为“app.stock.request”)。 在该通用配置文件中,您还会看到Jackson2JsonMessageConverterRabbitTemplate.spring-doc.cadn.net.cn

特定于服务器的配置由两部分组成。 首先,它在RabbitTemplate这样它就不需要在每次调用时都提供该 Exchange 名称来发送Message. 它在 base configuration 类中定义的抽象回调方法中执行此作。 下面的清单显示了该方法:spring-doc.cadn.net.cn

public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}

其次,声明股票请求队列。 在这种情况下,它不需要任何显式绑定,因为它绑定到默认的 no-name 交换,并将自己的名称作为路由键。 如前所述,AMQP 规范定义了该行为。 下面的清单显示了stockRequestQueue豆:spring-doc.cadn.net.cn

@Bean
public Queue stockRequestQueue() {
    return new Queue(STOCK_REQUEST_QUEUE_NAME);
}

现在,您已经了解了服务器的 AMQP 资源的配置,请导航到org.springframework.amqp.rabbit.stocks软件包的src/test/java目录。 在那里,您可以看到实际的Server类提供main()方法。 它会创建一个ApplicationContext基于server-bootstrap.xmlconfig 文件。 在那里,您可以看到发布虚拟市场数据的计划任务。 该配置依赖于 Spring 的task命名空间支持。 引导配置文件还导入了一些其他文件。 最有趣的是server-messaging.xml,它位于src/main/resources. 在那里,您可以看到messageListenerContainer负责处理 Stock Trade 请求的 bean。 最后,看看serverHandlerserver-handlers.xml(它也在 'src/main/resources' 中)。 该 bean 是ServerHandler类,是消息驱动的 POJO 的一个很好的例子,它也可以发送回复消息。 请注意,它本身并不与框架或任何 AMQP 概念耦合。 它接受一个TradeRequest并返回一个TradeResponse. 下面的清单显示了handleMessage方法:spring-doc.cadn.net.cn

public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}

现在我们已经看到了服务器最重要的配置和代码,我们可以转向客户端。 最好的起点可能是RabbitClientConfigurationorg.springframework.amqp.rabbit.stocks.config.client包。 请注意,它声明了两个队列,但没有提供显式名称。 下面的清单显示了两个队列的 bean 定义:spring-doc.cadn.net.cn

@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}

这些是私有队列,唯一名称是自动生成的。 客户端使用第一个生成的队列绑定到服务器已公开的市场数据交换。 回想一下,在 AMQP 中,使用者与队列交互,而生产者与交换交互。 队列与交易所的 “绑定” 是告诉 broker 将消息从给定交易所投递 (或路由) 到队列。 由于市场数据交换是主题交换,因此可以使用路由模式来表示绑定。 这RabbitClientConfiguration执行此作,并使用Bindingobject,该对象是使用BindingBuilderFluent API 的 API 创建。 下面的清单显示了Binding:spring-doc.cadn.net.cn

@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;

@Bean
public Binding marketDataBinding() {
    return BindingBuilder.bind(
        marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}

请注意,实际值已在属性文件 (client.propertiessrc/main/resources),并且我们使用 Spring 的@Valueannotation 注入该值。 这通常是一个好主意。 否则,该值将在类中硬编码,并且无需重新编译即可不可修改。 在这种情况下,在更改用于绑定的路由模式的同时运行多个版本的客户端要容易得多。 我们现在可以试试。spring-doc.cadn.net.cn

从运行 开始org.springframework.amqp.rabbit.stocks.Server然后org.springframework.amqp.rabbit.stocks.Client. 您应该会看到NASDAQstocks,因为与 client.properties 中的 'stocks.quote.pattern' 键关联的当前值是 'app.stock.quotes.nasdaq.'. 现在,在保持现有的ServerClient运行,将该属性值更改为 'app.stock.quotes.nyse.' 并开始第二个Client实例。 您应该看到,第一个客户仍然接收纳斯达克报价,而第二个客户接收纽约证券交易所报价。 相反,您可以更改模式以获取所有股票甚至单个股票代码。spring-doc.cadn.net.cn

我们探索的最后一个功能是从客户端的角度进行请求 - 回复交互。 回想一下,我们已经看到了ServerHandler接受TradeRequest对象和返回TradeResponse对象。 上面的相应代码Clientside 为RabbitStockServiceGatewayorg.springframework.amqp.rabbit.stocks.gateway包。 它将RabbitTemplate以便发送消息。 下面的清单显示了send方法:spring-doc.cadn.net.cn

public void send(TradeRequest tradeRequest) {
    getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setReplyTo(new Address(defaultReplyToQueue));
            try {
                message.getMessageProperties().setCorrelationId(
                    UUID.randomUUID().toString().getBytes("UTF-8"));
            }
            catch (UnsupportedEncodingException e) {
                throw new AmqpException(e);
            }
            return message;
        }
    });
}

请注意,在发送消息之前,它会设置replyTo地址。 它提供由traderJoeQueuebean 定义(如前所示)。 下面的清单显示了@Bean定义StockServiceGateway类本身:spring-doc.cadn.net.cn

@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}

如果您不再运行服务器和客户端,请立即启动它们。 尝试发送格式为 '100 TCKR' 的请求。 在模拟请求的 “处理” 的短暂人为延迟之后,您应该会看到客户端上出现一条确认消息。spring-doc.cadn.net.cn

4.4.3. 从非 Spring 应用程序接收 JSON

Spring 应用程序在发送 JSON 时,将TypeIdheader 转换为完全限定的类名,以帮助接收应用程序将 JSON 转换回 Java 对象。spring-doc.cadn.net.cn

spring-rabbit-jsonsample 探讨了从非 Spring 应用程序转换 JSON 的几种技术。spring-doc.cadn.net.cn

4.5. 测试支持

为异步应用程序编写集成必然比测试更简单的应用程序更复杂。 当抽象(如@RabbitListener注释出现。 问题是如何验证在发送消息后,侦听器是否按预期收到了消息。spring-doc.cadn.net.cn

框架本身有许多单元测试和集成测试。 一些使用模拟,而另一些则使用实时 RabbitMQ 代理的集成测试。 您可以查阅这些测试,了解测试方案的一些想法。spring-doc.cadn.net.cn

Spring AMQP 版本 1.6 引入了spring-rabbit-testjar 的 jar 中,它为测试其中一些更复杂的场景提供支持。 预计该项目将随着时间的推移而扩展,但我们需要社区反馈,以便为帮助测试所需的功能提出建议。 请使用 JIRAGitHub Issues 提供此类反馈。spring-doc.cadn.net.cn

4.5.1. @SpringRabbitTest

使用此注释将基础结构 bean 添加到 Spring 测试ApplicationContext. 例如,在使用时,这不是必需的@SpringBootTest因为 Spring Boot 的自动配置将添加 bean。spring-doc.cadn.net.cn

已注册的 Bean 包括:spring-doc.cadn.net.cn

此外,与@EnableRabbit(支持@RabbitListener) 添加。spring-doc.cadn.net.cn

例 8.Junit5 示例
@SpringJunitConfig
@SpringRabbitTest
public class MyRabbitTests {

	@Autowired
	private RabbitTemplate template;

	@Autowired
	private RabbitAdmin admin;

	@Autowired
	private RabbitListenerEndpointRegistry registry;

	@Test
	void test() {
        ...
	}

	@Configuration
	public static class Config {

        ...

	}

}

使用 JUnit4,将@SpringJunitConfig@RunWith(SpringRunnner.class).spring-doc.cadn.net.cn

4.5.2. 模拟Answer<?>实现

目前有两个Answer<?>implementations 来帮助进行测试。spring-doc.cadn.net.cn

第一个LatchCountDownAndCallRealMethodAnswer提供Answer<Void>返回null并倒计时一个闩锁。 以下示例演示如何使用LatchCountDownAndCallRealMethodAnswer:spring-doc.cadn.net.cn

LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();

第二个LambdaAnswer<T>提供一种机制来选择性地调用 Real 方法,并提供一个机会 返回自定义结果,请根据InvocationOnMock和结果 (如果有)。spring-doc.cadn.net.cn

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

public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}

以下类测试ThingPOJO:spring-doc.cadn.net.cn

Thing thing = spy(new Thing());

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + r))
    .when(thing).thing(anyString());
assertEquals("THINGTHING", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + i.getArguments()[0]))
    .when(thing).thing(anyString());
assertEquals("THINGthing", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(false, (i, r) ->
    "" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString());
assertEquals("thingthing", thing.thing("thing"));

从版本 2.2.3 开始,答案捕获被测方法引发的任何异常。 用answer.getExceptions()以获取对它们的引用。spring-doc.cadn.net.cn

当与@RabbitListenerTestRabbitListenerTestHarnessharness.getLambdaAnswerFor("listenerId", true, …​)为侦听器获取正确构建的答案。spring-doc.cadn.net.cn

4.5.3.@RabbitListenerTestRabbitListenerTestHarness

注释您的其中一个@Configuration@RabbitListenerTest使框架将 标准RabbitListenerAnnotationBeanPostProcessor替换为名为RabbitListenerTestHarness(它还使@RabbitListener检测方式@EnableRabbit).spring-doc.cadn.net.cn

RabbitListenerTestHarness以两种方式增强侦听器。 首先,它将侦听器包装在Mockito Spy,启用“法线”Mockito存根和验证作。 它还可以添加Advice添加到侦听器中,从而启用对参数、结果和引发的任何异常的访问。 您可以通过@RabbitListenerTest. 后者用于访问有关调用的较低级别数据。 它还支持阻塞测试线程,直到异步侦听器被调用。spring-doc.cadn.net.cn

final @RabbitListener方法不能被窥探或建议。 此外,只有id属性可以被窥探或建议。

请看看一些例子。spring-doc.cadn.net.cn

以下示例使用 spy:spring-doc.cadn.net.cn

@Configuration
@RabbitListenerTest
public class Config {

    @Bean
    public Listener listener() {
        return new Listener();
    }

    ...

}

public class Listener {

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

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        Listener listener = this.harness.getSpy("foo"); (2)
        assertNotNull(listener);
        verify(listener).foo("foo");
    }

    @Test
    public void testOneWay() throws Exception {
        Listener listener = this.harness.getSpy("bar");
        assertNotNull(listener);

        LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("bar", 2); (3)
        doAnswer(answer).when(listener).foo(anyString(), anyString()); (4)

        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");

        assertTrue(answer.await(10));
        verify(listener).foo("bar", this.queue2.getName());
        verify(listener).foo("baz", this.queue2.getName());
    }

}
1 将 Harness 注入到测试用例中,以便我们可以访问 spy。
2 获取对 spy 的引用,以便我们可以验证它是否按预期调用。 由于这是一个发送和接收作,因此无需暂停测试线程,因为它已经 suspended 在RabbitTemplate等待回复。
3 在这种情况下,我们只使用 send作,因此我们需要一个 latch 来等待对侦听器的异步调用 在容器线程上。 我们使用 Answer<?> 实现之一来帮助解决这个问题。 重要提示:由于侦听器的侦测方式,使用harness.getLatchAnswerFor()获取 SPY 的正确配置答案。
4 配置 spy 以调用Answer.

以下示例使用 capture 建议:spring-doc.cadn.net.cn

@Configuration
@ComponentScan
@RabbitListenerTest(spy = false, capture = true)
public class Config {

}

@Service
public class Listener {

    private boolean failed;

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

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        if (!failed && foo.equals("ex")) {
            failed = true;
            throw new RuntimeException(foo);
        }
        failed = false;
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("foo", 0, TimeUnit.SECONDS); (2)
        assertThat(invocationData.getArguments()[0], equalTo("foo"));     (3)
        assertThat((String) invocationData.getResult(), equalTo("FOO"));
    }

    @Test
    public void testOneWay() throws Exception {
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "ex");

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS); (4)
        Object[] args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("bar"));
        assertThat((String) args[1], equalTo(queue2.getName()));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("baz"));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("ex"));
        assertEquals("ex", invocationData.getThrowable().getMessage()); (5)
    }

}
1 将 Harness 注入到测试用例中,以便我们可以访问 spy。
2 harness.getNextInvocationDataFor()检索调用数据 - 在本例中,因为它是请求/回复 场景,则无需等待任何时间,因为测试线程在RabbitTemplate等待 以获得结果。
3 然后,我们可以验证参数和结果是否符合预期。
4 这一次我们需要一些时间来等待数据,因为它是容器线程上的异步作,我们需要 以暂停测试线程。
5 当侦听器抛出异常时,它在throwable属性。
使用自定义Answer<?>s 替换为 Harness,为了正常运行,此类 ANSWERS 应 subclassForwardsInvocation并从工具框架 (getDelegate("myListener")) 并调用super.answer(invocation). 请参阅提供的莫基托Answer<?>实现源代码。

4.5.4. 使用TestRabbitTemplate

TestRabbitTemplate用于执行一些基本的集成测试,而无需代理。 当您将其添加为@Bean在你的测试用例中,它会发现上下文中的所有侦听器容器,无论是声明为@Bean<bean/>或使用@RabbitListener注解。 它目前仅支持按队列名称进行路由。 该模板从容器中提取消息侦听器,并直接在测试线程上调用它。 请求-回复消息 (sendAndReceive方法)支持返回回复的侦听器。spring-doc.cadn.net.cn

以下测试用例使用模板:spring-doc.cadn.net.cn

@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {

    @Autowired
    private TestRabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void testSimpleSends() {
        this.template.convertAndSend("foo", "hello1");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello2");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:"));
        this.template.convertAndSend("foo", "hello3");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello4");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4"));

        this.template.setBroadcast(true);
        this.template.convertAndSend("foo", "hello5");
        assertThat(this.config.fooIn, equalTo("foo:hello1foo:hello5"));
        this.template.convertAndSend("bar", "hello6");
        assertThat(this.config.barIn, equalTo("bar:hello2bar:hello6"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4hello5hello6"));
    }

    @Test
    public void testSendAndReceive() {
        assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
    }
    @Configuration
    @EnableRabbit
    public static class Config {

        public String fooIn = "";

        public String barIn = "";

        public String smlc1In = "smlc1:";

        @Bean
        public TestRabbitTemplate template() throws IOException {
            return new TestRabbitTemplate(connectionFactory());
        }

        @Bean
        public ConnectionFactory connectionFactory() throws IOException {
            ConnectionFactory factory = mock(ConnectionFactory.class);
            Connection connection = mock(Connection.class);
            Channel channel = mock(Channel.class);
            willReturn(connection).given(factory).createConnection();
            willReturn(channel).given(connection).createChannel(anyBoolean());
            given(channel.isOpen()).willReturn(true);
            return factory;
        }

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

        @RabbitListener(queues = "foo")
        public void foo(String in) {
            this.fooIn += "foo:" + in;
        }

        @RabbitListener(queues = "bar")
        public void bar(String in) {
            this.barIn += "bar:" + in;
        }

        @RabbitListener(queues = "baz")
        public String baz(String in) {
            return "baz:" + in;
        }

        @Bean
        public SimpleMessageListenerContainer smlc1() throws IOException {
            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
            container.setQueueNames("foo", "bar");
            container.setMessageListener(new MessageListenerAdapter(new Object() {

                public void handleMessage(String in) {
                    smlc1In += in;
                }

            }));
            return container;
        }

    }

}

4.5.5. JUnit4@Rules

Spring AMQP 版本 1.7 及更高版本提供了一个名为spring-rabbit-junit. 这个 jar 包含几个实用程序@Rule实例,以便在运行 JUnit4 测试时使用。 请参阅 JUnit5 条件 以了解 JUnit5 测试。spring-doc.cadn.net.cn

BrokerRunning

BrokerRunning提供了一种机制,当代理未运行时(在localhost)。spring-doc.cadn.net.cn

它还具有用于初始化和清空队列以及删除队列和交换的实用方法。spring-doc.cadn.net.cn

以下示例显示了其用法:spring-doc.cadn.net.cn

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

有几个isRunning…​static 方法,例如isBrokerAndManagementRunning(),用于验证代理是否启用了管理插件。spring-doc.cadn.net.cn

配置规则

有时,如果没有代理,例如夜间 CI 构建,您希望测试失败。 要在运行时禁用该规则,请设置一个名为RABBITMQ_SERVER_REQUIREDtrue.spring-doc.cadn.net.cn

您可以使用 setter 或环境变量覆盖代理属性,例如 hostname:spring-doc.cadn.net.cn

以下示例演示如何使用 setter 覆盖属性:spring-doc.cadn.net.cn

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

static {
    brokerRunning.setHostName("10.0.0.1")
}

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

您还可以通过设置以下环境变量来覆盖属性:spring-doc.cadn.net.cn

public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI";
public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME";
public static final String BROKER_PORT = "RABBITMQ_TEST_PORT";
public static final String BROKER_USER = "RABBITMQ_TEST_USER";
public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD";
public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER";
public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD";

这些环境变量会覆盖默认设置 (localhost:5672对于 AMQP 和localhost:15672/api/对于管理 REST API)。spring-doc.cadn.net.cn

更改主机名会影响amqpmanagementREST API 连接(除非显式设置了管理员 URI)。spring-doc.cadn.net.cn

BrokerRunning还提供了一个static方法调用setEnvironmentVariableOverrides这样,您就可以传入包含这些变量的 Map。 它们会覆盖系统环境变量。 如果您希望对多个测试套件中的测试使用不同的配置,这可能很有用。 重要说明:在调用任何isRunning()static 方法。 变量值将应用于此调用后创建的所有实例。 调用clearEnvironmentVariableOverrides()将规则重置为使用默认值(包括任何实际的环境变量)。spring-doc.cadn.net.cn

在测试用例中,您可以使用brokerRunning创建 Connection Factory 时;getConnectionFactory()返回规则的 RabbitMQConnectionFactory. 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
LongRunningIntegrationTest

LongRunningIntegrationTest是禁用长时间运行的测试的规则。 您可能希望在开发人员系统上使用它,但请确保在夜间 CI 构建等上禁用该规则。spring-doc.cadn.net.cn

以下示例显示了其用法:spring-doc.cadn.net.cn

@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

要在运行时禁用该规则,请设置一个名为RUN_LONG_INTEGRATION_TESTStrue.spring-doc.cadn.net.cn

4.5.6. JUnit5 条件

版本 2.0.2 引入了对 JUnit5 的支持。spring-doc.cadn.net.cn

使用@RabbitAvailable注解

这个类级注解类似于BrokerRunning @Rule讨论于JUnit4@Rules. 它由RabbitAvailableCondition.spring-doc.cadn.net.cn

该批注具有三个属性:spring-doc.cadn.net.cn

  • queues:在每次测试之前声明(和清除)并在所有测试完成时删除的队列数组。spring-doc.cadn.net.cn

  • management:将此项设置为true如果您的测试还需要在 broker 上安装 Management 插件。spring-doc.cadn.net.cn

  • purgeAfterEach:(自版本 2.2 起)当true(默认)、queues将在测试之间清除。spring-doc.cadn.net.cn

它用于检查 broker 是否可用,如果没有,则跳过测试。 如配置规则中所述,名为RABBITMQ_SERVER_REQUIRED如果true,如果没有 Broker,则会导致测试快速失败。 您可以使用环境变量配置条件,如 配置规则中所述。spring-doc.cadn.net.cn

此外,RabbitAvailableCondition支持参数化测试构造函数和方法的参数解析。 支持两种参数类型:spring-doc.cadn.net.cn

以下示例显示了两者:spring-doc.cadn.net.cn

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final ConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory = brokerRunning.getConnectionFactory();
    }

    @Test
    public void test(ConnectionFactory cf) throws Exception {
        assertSame(cf, this.connectionFactory);
        Connection conn = this.connectionFactory.newConnection();
        Channel channel = conn.createChannel();
        DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
        assertEquals(0, declareOk.getConsumerCount());
        channel.close();
        conn.close();
    }

}

前面的测试在框架本身中,验证参数注入以及条件是否正确创建了队列。spring-doc.cadn.net.cn

实际的用户测试可能如下所示:spring-doc.cadn.net.cn

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final CachingConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory =
            new CachingConnectionFactory(brokerRunning.getConnectionFactory());
    }

    @Test
    public void test() throws Exception {
        RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
        ...
    }
}

当你在测试类中使用 Spring 注解应用程序上下文时,你可以通过一个名为RabbitAvailableCondition.getBrokerRunning().spring-doc.cadn.net.cn

从版本 2.2 开始,getBrokerRunning()返回BrokerRunningSupport对象;以前,JUnit 4BrokerRunnning实例。 新类具有与BrokerRunning.

以下测试来自框架,演示了用法:spring-doc.cadn.net.cn

@RabbitAvailable(queues = {
        RabbitTemplateMPPIntegrationTests.QUEUE,
        RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {

    public static final String QUEUE = "mpp.tests";

    public static final String REPLIES = "mpp.tests.replies";

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void test() {

        ...

    }

    @Configuration
    @EnableRabbit
    public static class Config {

        @Bean
        public CachingConnectionFactory cf() {
            return new CachingConnectionFactory(RabbitAvailableCondition
                    .getBrokerRunning()
                    .getConnectionFactory());
        }

        @Bean
        public RabbitTemplate template() {

            ...

        }

        @Bean
        public SimpleRabbitListenerContainerFactory
                            rabbitListenerContainerFactory() {

            ...

        }

        @RabbitListener(queues = QUEUE)
        public byte[] foo(byte[] in) {
            return in;
        }

    }

}
使用@LongRunning注解

LongRunningIntegrationTestJUnit4@Rule,则此注释会导致跳过测试,除非将环境变量(或系统属性)设置为true. 以下示例演示如何使用它:spring-doc.cadn.net.cn

@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}

默认情况下,变量为RUN_LONG_INTEGRATION_TESTS,但您可以在注解的value属性。spring-doc.cadn.net.cn