2.4.6

该文档也以PDF 格式提供。

© 2010 - 2021 VMware, Inc.

本文档的副本可以供您自己使用和分发给其他人,前提是您不对此类副本收取任何费用,并且进一步前提是每份副本都包含本版权声明,无论是印刷版还是电子版。

1.前言

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

2. 什么是新的

2.1。自 2.3 以来 2.4 的变化

本节介绍 2.4 版和 2.4 版之间的更改。有关以前版本的更改,请参阅更改历史记录。

2.1.1。@RabbitListener变化

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

2.1.2. RabbitAdmin变化

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

2.1.3。远程支持

不推荐使用 Spring Framework 的 RMI 支持进行远程处理,并将在 3.0 中删除。有关更多信息,请参阅使用 AMQP 进行 Spring 远程处理。

2.1.4。消息转换器更改

现在Jackson2JsonMessageConverter可以从contentEncoding标题中确定字符集。有关更多信息,请参阅Jackson2JsonMessageConverter

3.简介

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

3.1。不耐烦的快速浏览

3.1.1。介绍

这是 Spring AMQP 入门的五分钟导览。

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

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

对于 Gradle,您可以执行类似以下操作:

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

最低 Spring Framework 版本依赖项为 5.2.0。

最低amqp-clientJava 客户端库版本为 5.7.0。

非常非常快

本节提供最快的介绍。

首先,添加以下import语句以使本​​节后面的示例工作:

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

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

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

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

使用 XML 配置

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

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

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

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

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

    <rabbit:queue name="myqueue"/>

</beans>

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

使用 Java 配置

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

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

........

@Configuration
public class RabbitConfiguration {

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

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

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

    @Bean
    public Queue myQueue() {
       return new Queue("myqueue");
    }
}
使用 Spring Boot 自动配置和异步 POJO 侦听器

Spring Boot 自动配置基础设施 bean,如以下示例所示:

@SpringBootApplication
public class Application {

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

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

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

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

}

4. 参考

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

4.1。使用 Spring AMQP

本章探讨了使用 Spring AMQP 开发应用程序的基本组件的接口和类。

4.1.1。AMQP 抽象

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

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

Message

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

public class Message {

    private final MessageProperties messageProperties;

    private final byte[] body;

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

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

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

MessageProperties接口定义了几个常用属性,例如“messageId”、“timestamp”、“contentType”等等。您还可以通过调用该setHeader(String key, Object value)方法使用用户定义的“标题”扩展这些属性。

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

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

public interface Exchange {

    String getName();

    String getExchangeType();

    boolean isDurable();

    boolean isAutoDelete();

    Map<String, Object> getArguments();

}

如您所见, anExchange也有一个由定义在 中的常量表示的“类型” ExchangeTypes。基本类型是:directtopicfanoutheaders。在核心包中,您可以找到Exchange每种类型的接口实现。这些Exchange类型的行为在它们如何处理与队列的绑定方面有所不同。例如,一个Direct交换器让一个队列被一个固定的路由键(通常是队列的名字)绑定。交换支持与路由模式的Topic绑定,这些路由模式可能分别包含 'exactly-one' 和 'zero-or-more' 的 '*' 和 '#' 通配符。这Fanout交换发布到绑定到它的所有队列,而不考虑任何路由键。有关这些和其他 Exchange 类型的更多信息,请参阅其他资源

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

该类Queue表示消息使用者从中接收消息的组件。与各种Exchange类一样,我们的实现旨在成为此核心 AMQP 类型的抽象表示。以下清单显示了Queue该类:

public class Queue  {

    private final String name;

    private volatile boolean durable;

    private volatile boolean exclusive;

    private volatile boolean autoDelete;

    private volatile Map<String, Object> arguments;

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

    // Getters and Setters omitted for brevity

}

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

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

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

DirectExchange您可以使用固定的路由键将队列绑定到 a ,如以下示例所示:

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

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

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

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

new Binding(someQueue, someFanoutExchange);

我们还提供了一个BindingBuilder方便“流畅的 API”样式,如以下示例所示:

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

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

AmqpTemplate也在核心包中定义。作为实际 AMQP 消息传递中涉及的主要组件之一,它在其自己的部分中进行了详细讨论(请参阅 参考资料AmqpTemplate)。

4.1.2. 连接和资源管理

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

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

选择连接工厂

有三种连接工厂可供选择

  • PooledChannelConnectionFactory

  • ThreadChannelConnectionFactory

  • CachingConnectionFactory

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

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

所有三个工厂都支持简单的发布者确认。

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

PooledChannelConnectionFactory

该工厂基于 Apache Pool2 管理一个连接和两个通道池。一个池用于交易渠道,另一个用于非交易渠道。池是GenericObjectPool默认配置的;提供回调来配置池;有关更多信息,请参阅 Apache 文档。

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

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

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

CachingConnectionFactory

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

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

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

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

当缓存模式为CONNECTION时,不支持自动声明队列等(请参阅自动声明交换、队列和绑定)。

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

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

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

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

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

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

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

Connection connection = connectionFactory.createConnection();

使用 XML 时,配置可能类似于以下示例:

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

使用rabbit命名空间可以快速方便地创建A ConnectionFactory,如下:

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

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

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

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

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

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

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

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

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

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

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

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

以下示例带有一个自定义线程工厂,它在线程名称前加上rabbitmq-

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

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

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

命名连接

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

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

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

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

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

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

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

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

可能会阻止连接与与Memory Alarm对应的代理进行交互。从 2.0 版开始,org.springframework.amqp.rabbit.connection.Connection可以提供com.rabbitmq.client.BlockedListener实例以通知连接阻塞和未阻塞事件。此外,通过其内部实现分别AbstractConnectionFactory发出 aConnectionBlockedEvent和。这些使您可以提供应用程序逻辑以对代理上的问题做出适当的反应,并(例如)采取一些纠正措施。ConnectionUnblockedEventBlockedListener

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

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

从 1.7.7 版本开始,提供 an ,当无法创建 aAmqpResourceNotAvailableException时抛出(例如,因为达到限制并且缓存中没有可用通道)。您可以在一些退避后使用此异常来恢复操作。SimpleConnection.createChannel()ChannelchannelMaxRetryPolicy

配置底层客户端连接工厂

使用 Rabbit 客户端的CachingConnectionFactory一个实例ConnectionFactory。在. _ host_ port_ userName_ password_ requestedHeartBeat_ 要设置其他属性(例如 ),您可以定义 Rabbit 工厂的实例并使用. 使用命名空间时(如前所述),您需要在属性中提供对已配置工厂的引用。为方便起见,提供了一个工厂 bean 来帮助在 Spring 应用程序上下文中配置连接工厂,如下一节所述connectionTimeoutCachingConnectionFactoryclientPropertiesCachingConnectionFactoryconnection-factory

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

从版本 1.4 开始,RabbitConnectionFactoryBean提供了一种方便的方式,可以通过使用依赖注入在底层客户端连接工厂上方便地配置 SSL 属性。其他 setter 委托给底层工厂。以前,您必须以编程方式配置 SSL 选项。以下示例显示了如何配置RabbitConnectionFactoryBean

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

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

有关配置 SSL 的信息,请参阅RabbitMQ 文档。省略keyStoreandtrustStore配置以通过 SSL 连接而不进行证书验证。下一个示例展示了如何提供密钥和信任库配置。

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

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

和是 SpringkeyStore指向商店。通常,此属性文件由具有读取访问权限的应用程序的操作系统保护。truststoreResources

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

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

要连接到集群,请addresses在以下位置配置属性CachingConnectionFactory

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

每当建立新连接时,底层连接工厂将按顺序尝试连接到每个主机。从版本 2.1.8 开始,可以通过将addressShuffleMode属性设置为随机连接顺序RANDOM;在创建任何新连接之前将应用随机播放。从 2.6 版本开始,INORDER添加了 shuffle 模式,这意味着在创建连接后将第一个地址移动到末尾。如果您希望使用所有节点上的所有分片,您可能希望将此模式与RabbitMQ 分片插件一起使用并具有合适的并发性。CacheMode.CONNECTION

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

从 1.3 版开始,AbstractRoutingConnectionFactory引入了 。该工厂提供了一种机制来配置多个映射并在运行时由一些ConnectionFactories确定目标。通常,实现会检查线程绑定的上下文。为方便起见,Spring AMQP 提供了,它从. 以下示例显示了如何在 XML 和 Java 中配置 a:ConnectionFactorylookupKeySimpleRoutingConnectionFactorylookupKeySimpleResourceHolderSimpleRoutingConnectionFactory

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

}

使用后解绑资源很重要。有关更多信息,请参阅.AbstractRoutingConnectionFactory

从 1.4 版开始,RabbitTemplate支持 SpELsendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpression属性,它们在每个 AMQP 协议交互操作(、、、或)上进行评估send,解析sendAndReceive为提供的值。您可以使用 bean 引用,例如在表达式中。对于操作,要发送的消息是根评估对象。对于操作,是根评估对象。receivereceiveAndReplylookupKeyAbstractRoutingConnectionFactory@vHostResolver.getVHost(#root)sendreceivequeueName

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

命名空间支持还提供了组件的send-connection-factory-selector-expressionreceive-connection-factory-selector-expression属性<rabbit:template>

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

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

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

目标(和默认值,如果提供)连接工厂必须具有相同的发布者确认和返回设置。请参阅出版商确认和退货

从版本 2.4.4 开始,可以禁用此验证。如果您遇到确认和返回之间的值需要不相等的情况,您可以使用AbstractRoutingConnectionFactory#setConsistentConfirmsReturns关闭验证。请注意,添加到的第一个连接工厂将确定和AbstractRoutingConnectionFactory的一般值。confirmsreturns

如果您遇到您要检查的某些消息确认/返回而其他您不知道的情况,这可能会很有用。例如:

@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将通过缓存连接发送,您可以确保消息传递。有关确保消息传递的更多信息,请参阅发布者确认和退货

队列亲和力和LocalizedQueueConnectionFactory

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

和上面的路由连接工厂中讨论的那样,LocalizedQueueConnectionFactory它使用队列名称作为查找键。RoutingConnectionFactorySimpleMessageListenerContainer

由于这个原因(使用队列名称进行查找),LocalizedQueueConnectionFactory只能在容器配置为侦听单个队列时使用。
必须在每个节点上启用 RabbitMQ 管理插件。
此连接工厂适用于长期连接,例如SimpleMessageListenerContainer. 它不适用于短连接使用,例如,RabbitTemplate因为在建立连接之前调用 REST API 的开销。还有,对于发布操作来说,队列是未知的,反正消息是发布给所有集群成员的,所以查找节点的逻辑意义不大。

以下示例配置显示了如何配置工厂:

@Autowired
private ConfigurationProperties props;

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

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

请注意,前三个参数是addressesadminUris和的数组nodes。这些是定位的,当容器尝试连接到队列时,它使用管理 API 来确定哪个节点是队列的引导节点,并连接到与该节点相同的数组位置中的地址。

出版商确认并退货

CachingConnectionFactory通过将属性设置publisherConfirmTypeConfirmType.CORRELATED并将publisherReturns属性设置为“真”来支持确认(具有相关性)和返回的消息。

设置这些选项后,Channel工厂创建的实例被包装在一个PublisherCallbackChannel中,用于方便回调。当获得这样的通道时,客户端可以PublisherCallbackChannel.ListenerChannel. 该PublisherCallbackChannel实现包含将确认或返回路由到适当侦听器的逻辑。这些功能将在以下部分中进一步解释。

另请参阅Scoped OperationssimplePublisherConfirms

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

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

@FunctionalInterface
public interface ConnectionListener {

    void onCreate(Connection connection);

    default void onClose(Connection connection) {
    }

    default void onShutDown(ShutdownSignalException signal) {
    }

}

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

@FunctionalInterface
public interface ChannelListener {

    void onCreate(Channel channel, boolean transactional);

    default void onShutDown(ShutdownSignalException signal) {
    }

}

有关您可能想要注册ChannelListener.

记录通道关闭事件

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

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

  • 不记录正常通道关闭 (200 OK)。

  • 如果通道因被动队列声明失败而关闭,则会在调试级别记录。

  • 如果通道basic.consume由于排他性消费者条件而被拒绝而关闭,则将其记录在 INFO 级别。

  • 所有其他人都以错误级别记录。

要修改此行为,您可以在其属性中ConditionalExceptionLogger注入 自定义。CachingConnectionFactorycloseExceptionLogger

另请参阅消费者事件

运行时缓存属性

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

表 1. CacheMode.CHANNEL 的缓存属性
财产 意义
连接名称

生成的连接的名称ConnectionNameStrategy

通道缓存大小

当前配置的允许空闲的最大通道数。

本地端口

连接的本地端口(如果可用)。这可用于关联 RabbitMQ Admin UI 上的连接和通道。

idleChannelsTx

当前空闲(缓存)的事务通道数。

idleChannelsNotTx

当前空闲(缓存)的非事务性通道数。

idleChannelsTxHighWater

并发空闲(缓存)的最大事务通道数。

idleChannelsNotTxHighWater

非事务性通道的最大数量同时空闲(缓存)。

下表描述了这些CacheMode.CONNECTION属性:

表 2. CacheMode.CONNECTION 的缓存属性
财产 意义
连接名称:<本地端口>

生成的连接的名称ConnectionNameStrategy

开放连接

表示与代理的连接的连接对象的数量。

通道缓存大小

当前配置的允许空闲的最大通道数。

连接缓存大小

当前配置的允许空闲的最大连接数。

空闲连接

当前空闲的连接数。

idleConnectionsHighWater

并发空闲的最大连接数。

idleChannelsTx:<本地端口>

此连接当前空闲(缓存)的事务通道数。您可以使用localPort属性名称的一部分与 RabbitMQ Admin UI 上的连接和通道相关联。

idleChannelsNotTx:<localPort>

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

idleChannelsTxHighWater:<localPort>

并发空闲(缓存)的最大事务通道数。属性名称的 localPort 部分可用于与 RabbitMQ Admin UI 上的连接和通道相关联。

idleChannelsNotTxHighWater:<localPort>

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

cacheMode属性 (或CHANNEL)CONNECTION也包括在内。

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

从 Spring AMQP 的第一个版本开始,该框架提供了自己的连接和通道恢复,以防代理失败。此外,正如配置代理中所讨论的,当重新建立连接时,RabbitAdmin重新声明任何基础设施 bean(队列和其他)。因此,它不依赖于库现在提供的自动恢复amqp-client。Spring AMQP 现在使用 的4.0.x版本amqp-client,默认启用自动恢复。如果您愿意,Spring AMQP 仍然可以使用自己的恢复机制,在客户端禁用它,(通过将automaticRecoveryEnabled底层的属性设置RabbitMQ connectionFactoryfalse)。但是,该框架与启用的自动恢复完全兼容。这意味着您在代码中创建的任何消费者(可能通过RabbitTemplate.execute())都可以自动恢复。

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

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

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

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

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

4.1.4。AmqpTemplate

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

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

另请参阅异步兔子模板

添加重试功能

从 1.3 版开始,您现在可以配置RabbitTemplate以使用 aRetryTemplate来帮助处理代理连接问题。有关完整信息,请参阅spring-retry项目。以下只是一个使用指数退避策略和 default 的示例,SimpleRetryPolicy它在将异常抛出给调用者之前进行了三次尝试。

以下示例使用 XML 命名空间:

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

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

以下示例使用@ConfigurationJava 中的注解:

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

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

RecoveryCallback一些限制,因为重试上下文仅包含该lastThrowable字段。对于更复杂的用例,您应该使用外部RetryTemplate,以便您可以RecoveryCallback通过上下文的属性将附加信息传递给。以下示例显示了如何执行此操作:
retryTemplate.execute(
    new RetryCallback<Object, Exception>() {

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

    }, new RecoveryCallback<Object>() {

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

在这种情况下,您不会将 aRetryTemplate注入RabbitTemplate.

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

发布消息是一种异步机制,默认情况下,无法路由的消息会被 RabbitMQ 丢弃。为了成功发布,您可以收到异步确认,如相关发布者确认和返回中所述。考虑两种失败情况:

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

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

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

对于第二种情况,消息被丢弃并且不生成返回。底层通道异常关闭。默认情况下,会记录此异常,但您可以向 注册一个ChannelListenerCachingConnectionFactory获取此类事件的通知。以下示例显示了如何添加ConnectionListener

this.connectionFactory.addConnectionListener(new ConnectionListener() {

    @Override
    public void onCreate(Connection connection) {
    }

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

});

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

要检测发送线程上的异常,您可以setChannelTransacted(true)在 上RabbitTemplate检测到异常txCommit()。但是,事务会显着阻碍性能,因此在为这个用例启用事务之前要仔细考虑这一点。

相关出版商确认和退货

支持发布者确认和返回的RabbitTemplate执行。AmqpTemplate

对于返回的消息,模板的mandatory属性必须设置为truemandatory-expression 必须true为特定消息计算。此功能需要将CachingConnectionFactorypublisherReturns属性设置为的 a true(请参阅Publisher Confirms and Returns)。RabbitTemplate.ReturnsCallback返回通过调用注册 a 发送给客户端setReturnsCallback(ReturnsCallback callback)。回调必须实现以下方法:

void returnedMessage(ReturnedMessage returned);

具有以下ReturnedMessage属性:

  • message- 返回的消息本身

  • replyCode- 指示退货原因的代码

  • replyText- 退货的文字原因 - 例如NO_ROUTE

  • exchange- 消息发送到的交易所

  • routingKey- 使用的路由键

每个只ReturnsCallback支持一个RabbitTemplate。另请参阅回复超时

对于发布者确认(也称为发布者确认),模板需要将CachingConnectionFactorypublisherConfirm属性设置为 的ConfirmType.CORRELATEDRabbitTemplate.ConfirmCallback确认通过调用注册一个发送给客户端setConfirmCallback(ConfirmCallback callback)。回调必须实现此方法:

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

CorrelationData是客户端在发送原始消息时提供的对象。对aack为真,ack对 a 为假nack。例如nack,原因可能包含 的原因nack,如果它在nack生成时可用。一个例子是向不存在的交换机发送消息时。在这种情况下,代理关闭通道。关闭的原因包含在cause. cause是在 1.4 版中添加的。

ConfirmCallback支持一个RabbitTemplate

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

从 2.1 版开始,该CorrelationData对象具有ListenableFuture可用于获取结果的 a,而不是ConfirmCallback在模板上使用 a。以下示例显示了如何配置CorrelationData实例:

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

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

此外,当确认和返回都启用时,CorrelationData只要CorrelationData具有唯一的id;就会填充返回的消息。从 2.3 版开始,默认情况下总是如此。保证返回的消息在用ack.

另请参阅Scoped Operations以获得更简单的等待发布者确认的机制。

范围操作

通常,在使用模板时,aChannel会从缓存中检出(或创建),用于操作,然后返回缓存以供重用。在多线程环境中,不能保证下一个操作使用相同的通道。但是,有时您可能希望更好地控制通道的使用,并确保所有操作都在同一通道上执行。

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

@FunctionalInterface
public interface OperationsCallback<T> {

    T doInRabbit(RabbitOperations operations);

}

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

请注意,允许您将确认与请求相关联的更高级别的抽象在其他地方提供(请参阅相关的发布者确认和返回)。如果您只想等到经纪人确认交付,您可以使用以下示例中显示的技术:

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

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

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

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

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

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

以下示例日志acknack实例:

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

Scoped Operations中的讨论仅适用于在同一线程上执行操作时。

考虑以下情况:

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

  • thread-2向同一个队列发送消息

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

最好用一个简单的 Spring Boot 应用程序来说明这一点:

@SpringBootApplication
public class Application {

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

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

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

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

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

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


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

}

@Component
class Service {

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

	private final RabbitTemplate template;

	private final TaskExecutor exec;

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

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

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

}

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

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

@SpringBootApplication
public class Application {

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

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

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

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

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

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


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

}

@Component
class Service {

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

	private final RabbitTemplate template;

	private final TaskExecutor exec;

	private final ThreadChannelConnectionFactory connFactory;

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

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

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

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

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

从 1.4 版开始,( RabbitMessagingTemplate基于. 这使您可以使用抽象来发送和接收消息。这种抽象被其他 Spring 项目使用,例如 Spring Integration 和 Spring 的 STOMP 支持。涉及到两个消息转换器:一个在 spring-messaging和 Spring AMQP 的抽象之间进行转换,另一个在 Spring AMQP 的抽象和底层 RabbitMQ 客户端库所需的格式之间进行转换。默认情况下,消息负载由提供的实例的消息转换器转换。或者,您可以注入自定义RabbitTemplateorg.springframework.messaging.Messagespring-messaging Message<?>Message<?>MessageMessageRabbitTemplateMessagingMessageConverter使用其他一些有效负载转换器,如以下示例所示:

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

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

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

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

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

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

使用单独的连接

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

通常,不应将 aRabbitAdmin与设置为 的模板一起使用true。使用RabbitAdmin带有连接工厂的构造函数。如果您使用其他接受模板的构造函数,请确保模板的属性为false. 这是因为,管理员通常用于为侦听器容器声明队列。使用将属性设置为的模板true意味着独占队列(例如AnonymousQueue)将在与侦听器容器使用的连接不同的连接上声明。在这种情况下,容器不能使用队列。

4.1.5。发送消息

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

void send(Message message) throws AmqpException;

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

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

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

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

exchange如果您计划在大部分或所有时间使用该模板实例发送到同一个交易所,您可以在模板本身上设置该属性。在这种情况下,您可以使用上述清单中的第二种方法。下面的例子在功能上等同于前面的例子:

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

如果模板上同时设置了exchangeroutingKey属性,则可以使用只接受Message. 以下示例显示了如何执行此操作:

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

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

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

或者,您可以创建一个模板,该模板可用于主要或专门发布到单个队列。以下示例显示了如何执行此操作:

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

从版本 1.3 开始,消息构建器 API 由MessageBuilderand提供MessagePropertiesBuilder。这些方法提供了一种方便的“流畅”方式来创建消息或消息属性。以下示例展示了流畅的 API 的运行情况:

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

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

提供了五个静态方法来创建初始消息构建器:

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

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

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

public static MessageBuilder fromMessage(Message message) (4)

public static MessageBuilder fromClonedMessage(Message message) (5)
1 构建器创建的消息有一个直接引用参数的正文。
2 构建器创建的消息的主体是一个新数组,其中包含参数中的字节副本。
3 构建器创建的消息的主体是一个新数组,其中包含参数中的字节范围。有关Arrays.copyOfRange()更多详细信息,请参阅。
4 构建器创建的消息具有一个直接引用参数正文的正文。参数的属性被复制到一个新MessageProperties对象。
5 构建器创建的消息的主体是一个包含参数主体副本的新数组。参数的属性被复制到一个新MessageProperties对象。

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

public static MessagePropertiesBuilder newInstance() (1)

public static MessagePropertiesBuilder fromProperties(MessageProperties properties) (2)

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

随着 的RabbitTemplate实现AmqpTemplate,每个send()方法都有一个重载版本,它接受一个额外的CorrelationData对象。当发布者确认被启用时,这个对象在回调中被返回AmqpTemplate。这使发件人可以将确认(acknack)与发送的消息相关联。

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

Message postProcessMessage(Message message, Correlation correlation);

在 2.0 版中,此接口已弃用。该方法已移至MessagePostProcessor默认实现,该实现委托给postProcessMessage(Message message).

同样从 1.6.7 版本开始,提供了一个新的回调接口CorrelationDataPostProcessorMessagePostProcessor这在所有实例之后调用(在send()方法中提供以及在 中提供的那些setBeforePublishPostProcessors())。实现可以更新或替换方法中提供的相关数据send()(如果有)。和Messageoriginal CorrelationData(如果有)作为参数提供。以下示例显示了如何使用该postProcess方法:

CorrelationData postProcess(Message message, CorrelationData correlationData);
出版商退货

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

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

发布者返回也可以RabbitTemplate在发送和接收操作内部使用。有关详细信息,请参阅回复超时

配料

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

public interface BatchingStrategy {

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

    Date nextRelease();

    Collection<MessageBatch> releaseBatches();

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

提供了一个SimpleBatchingStrategy。它支持将消息发送到单个交换或路由密钥。它具有以下属性:

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

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

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

通过SimpleBatchingStrategy在每个嵌入的消息前面加上一个四字节的二进制长度来格式化批处理。springBatchFormat这通过将message 属性设置为 来传达给接收系统lengthHeader4

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

但是,请参阅@RabbitListener with Batching了解更多信息。

4.1.6。接收消息

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

轮询消费者

本身可AmqpTemplate用于轮询Message接收。默认情况下,如果没有消息可用,null则立即返回。没有阻塞。从 1.5 版开始,您可以设置 a receiveTimeout,以毫秒为单位,并且接收方法会阻塞很长时间,等待消息。小于零的值意味着无限期阻塞(或至少直到与代理的连接丢失)。1.6 版引入了receive允许在每次调用时传递超时的方法的变体。

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

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

Message receive() throws AmqpException;

Message receive(String queueName) throws AmqpException;

Message receive(long timeoutMillis) throws AmqpException;

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

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

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;

Object receiveAndConvert(long timeoutMillis) throws AmqpException;

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

从 2.0 版开始,这些方法有一些变体,它们需要一个额外的ParameterizedTypeReference参数来转换复杂类型。模板必须配置为SmartMessageConverter. 有关更多信息,请参阅MessageWithRabbitTemplate转换。

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

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

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

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

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

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

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

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

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

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

以下清单显示了基于 POJO 的接收和回复的示例:

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

                public Invoice handle(Order order) {
                        return processOrder(order);
                }
        });
if (received) {
        log.info("We received an order!");
}
异步消费者
Spring AMQP 还通过使用注解来支持带注解的监听器端点,@RabbitListener并提供一个开放的基础设施来以编程方式注册端点。这是迄今为止设置异步消费者最方便的方法。有关更多详细信息,请参阅注释驱动的侦听器端点

预取默认值曾经是 1,这可能导致高效消费者的利用不足。从 2.0 版开始,默认预取值现在是 250,这应该可以让消费者在大多数常见场景中保持忙碌,从而提高吞吐量。

然而,在某些情况下预取值应该很低:

  • 对于大型消息,尤其是在处理速度很慢的情况下(消息可能会在客户端进程中增加大量内存)

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

  • 其他特殊情况

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

有关预取的更多背景信息,请参阅这篇关于RabbitMQ 中消费者利用率的 文章和这篇关于排队理论的文章。

消息监听器

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

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

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

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

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

1.5 版引入了一种更灵活的 POJO 消息传递机制,即@RabbitListener注解。有关详细信息,请参阅注释驱动的侦听器端点

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

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

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

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

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

从 2.0 版开始,FunctionalInterface提供了一个方便的功能。以下清单显示了 的定义FunctionalInterface

@FunctionalInterface
public interface ReplyingMessageListener<T, R> {

    R handleMessage(T t);

}

该接口通过使用 Java 8 lambda 来方便地配置适配器,如以下示例所示:

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

从版本 2.2 开始,buildListenerArguments(Object)已弃用,buildListenerArguments(Object, Channel, Message)而是引入了新版本。新方法帮助侦听器获取ChannelMessage参数做更多事情,例如channel.basicReject(long, boolean)在手动确认模式下调用。以下清单显示了最基本的示例:

public class ExtendedListenerAdapter extends MessageListenerAdapter {

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

}

现在您可以ExtendedListenerAdapterMessageListenerAdapter需要接收“频道”和“消息”一样进行配置。listener 的参数应设置为buildListenerArguments(Object, Channel, Message)返回,如下面的 listener 示例所示:

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

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

在 2.0 版之前,只有一个侦听器容器,即SimpleMessageListenerContainer. 现在有第二个容器,DirectMessageListenerContainer. 选择容器中描述了容器和您在选择使用哪个时可能应用的标准之间的差异。

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

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

作为“活动”组件,最常见的做法是使用 bean 定义创建侦听器容器,以便它可以在后台运行。以下示例显示了使用 XML 执行此操作的一种方法:

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

以下清单显示了使用 XML 执行此操作的另一种方法:

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

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

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

@Configuration
public class ExampleAmqpConfiguration {

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

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

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

从 RabbitMQ 版本 3.2 开始,代理现在支持消费者优先级(请参阅将消费者优先级与 RabbitMQ 一起使用)。这是通过x-priority在消费者上设置参数来启用的。现在SimpleMessageListenerContainer支持设置消费者参数,如以下示例所示:

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

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

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

从版本 1.3 开始,您可以修改容器在运行时侦听的队列。请参阅侦听器容器队列

auto-delete队列

当一个容器被配置为监听auto-delete队列,队列有一个x-expires选项,或者Broker上配置了Time-To-Live策略,当容器停止时(即最后一个consumer时,队列被broker移除)取消)。在 1.3 版本之前,由于缺少队列,无法重启容器。唯一会在RabbitAdmin连接关闭或打开时自动重新声明队列等,而在容器停止和启动时不会发生这种情况。

从 1.3 版开始,容器RabbitAdmin在启动期间使用 a 重新声明任何丢失的队列。

您还可以与管理员一起使用条件声明(请参阅条件声明auto-startup="false"来推迟队列声明,直到容器启动。以下示例显示了如何执行此操作:

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

在这种情况下,队列和交换由 声明containerAdminauto-startup="false"因此在上下文初始化期间不声明元素。此外,出于同样的原因,容器没有启动。当容器稍后启动时,它使用它的引用containerAdmin来声明元素。

批量消息

批处理消息(由生产者创建)由侦听器容器自动取消批处理(使用springBatchFormat消息头)。拒绝批次中的任何消息会导致整个批次被拒绝。有关批处理的更多信息,请参阅批处理。

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

设置容器属性consumerBatchEnabled以启用此功能。 deBatchingEnabled也必须为真,以便容器负责处理这两种类型的批次。实施BatchMessageListenerChannelAwareBatchMessageListener何时consumerBatchEnabled为真。从版本 2.2.7 开始,SimpleMessageListenerContainerDirectMessageListenerContainercan debatch producer 创建的批次List<Message>. 有关将此功能与@RabbitListener.

消费者活动

每当侦听器(消费者)遇到某种故障时,容器就会发布应用程序事件。该事件ListenerContainerConsumerFailedEvent具有以下属性:

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

  • reason: 失败的文字原因。

  • fatal:一个布尔值,指示失败是否是致命的。对于非致命异常,容器会尝试根据recoveryIntervalor recoveryBackoff(for the SimpleMessageListenerContainer) 或 the monitorInterval(for the DirectMessageListenerContainer) 重启消费者。

  • throwable:Throwable被抓了。

这些事件可以通过实现来使用ApplicationListener<ListenerContainerConsumerFailedEvent>

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

如果消费者失败,因为如果它的队列被独占使用,默认情况下,以及发布事件,WARN都会发出日志。要更改此日志记录行为,ConditionalExceptionLogger请在SimpleMessageListenerContainer实例的exclusiveConsumerExceptionLogger属性中提供自定义。另请参阅记录通道关闭事件

致命错误始终记录在该ERROR级别。这是它不可修改的。

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

  • AsyncConsumerStartedEvent: 当消费者启动时。

  • AsyncConsumerRestartedEvent:SimpleMessageListenerContainer仅当消费者在失败后重新启动时。

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

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

  • ConsumeOkEventconsumeOk从代理接收到 a 时,包含队列名称和consumerTag

  • ListenerContainerIdleEvent:请参阅检测空闲异步消费者

  • MissingQueueEvent:检测到丢失队列时。

消费者标签

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

public interface ConsumerTagStrategy {

    String createConsumerTag(String queue);

}

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

注释驱动的侦听器端点

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

@Component
public class MyService {

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

}

前面示例的想法是,只要消息在名为 的队列上可用,就会相应地调用myQueue该方法(在这种情况下,使用消息的有效负载)。processOrder

带注释的端点基础设施通过使用RabbitListenerContainerFactory.

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

可以为注释属性 ( etc)指定 属性占位符 ( ${some.property}) 或 SpEL 表达式 ( )。请参阅Listening to Multiple Queues了解为什么您可能使用 SpEL 而不是属性占位符的示例。以下清单显示了如何声明 Rabbit 侦听器的三个示例: #{someExpression}queues
@Component
public class MyService {

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

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

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

}

在第一个示例中myQueue,如果需要,队列与交换机一起自动(持久)声明,并使用路由键绑定到交换机。在第二个示例中,声明并绑定了一个匿名(独占、自动删除)队列;队列名称由框架使用Base64UrlNamingStrategy. 您不能使用此技术声明以代理命名的队列;它们需要被声明为 bean 定义;请参阅容器和代理命名队列QueueBinding可以提供多个条目,让侦听器侦听多个队列。在第三个示例中my.queue,如果需要,声明一个具有从属性检索的名称的队列,并使用队列名称作为路由键默认绑定到默认交换器。

从 2.0 版开始,@Exchange注解支持任何交换类型,包括自定义。有关更多信息,请参阅AMQP 概念

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

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

从 2.0 版开始,您现在可以将队列绑定到具有多个路由键的交换,如以下示例所示:

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

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

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

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

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

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

元注释

有时您可能希望对多个侦听器使用相同的配置。为了减少样板配置,您可以使用元注释来创建自己的侦听器注释。以下示例显示了如何执行此操作:

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

public class MetaListener {

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

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

}

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

@Component
static class MetaAnnotationTestBean {

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

}


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

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

}

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

    MyListener[] value();

}
启用侦听器端点注释

要启用对@RabbitListener注释的支持,您可以添加@EnableRabbit到您的@Configuration类之一。以下示例显示了如何执行此操作:

@Configuration
@EnableRabbit
public class AppConfig {

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

从 2.0 版开始,aDirectMessageListenerContainerFactory也可用。它创建DirectMessageListenerContainer实例。

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

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

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

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

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

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

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

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>元素。检测到任何带有注释的 bean @RabbitListener

例如SimpleRabbitListenerContainer,您可以使用类似于以下内容的 XML:

<rabbit:annotation-driven/>

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

例如DirectMessageListenerContainer,您可以使用类似于以下内容的 XML:

<rabbit:annotation-driven/>

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

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

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

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

在任何一种情况下,此设置都会覆盖出厂设置。以前,如果您有需要不同并发的侦听器,则必须定义不同的容器工厂。

注释还允许通过和(自 2.2 起)注释属性覆盖工厂autoStartup和属性。为每个执行程序使用不同的执行程序可能有助于在日志和线程转储中识别与每个侦听器关联的线程。taskExecutorautoStartupexecutor

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

@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);
}
注释方法的消息转换

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

MessageConverter第一步的默认设置是SimpleMessageConverter处理对象转换 String的Spring AMQP java.io.Serializable。所有其他人都保留为byte[]. 在下面的讨论中,我们称之为“消息转换器”。

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

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

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

这将配置一个 Jackson2 转换器,该转换器期望存在标头信息来指导转换。

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

messageConverter从 2.3 版开始,您可以通过在属性中指定 bean 名称来覆盖工厂转换器。

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

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

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

在大多数情况下,没有必要自定义方法参数转换器,除非您想使用自定义的ConversionService.

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

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

有关更多信息,请参阅Jackson2JsonMessageConverter

如果你想自定义方法参数转换器,你可以这样做:

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

    ...

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

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

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

    ...

}
对于多方法侦听器(参见多方法侦听器),方法选择是根据消息转换后的消息负载。方法参数转换器仅在选择方法后调用。
HandlerMethodArgumentResolver向@RabbitListener添加自定义

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

@Configuration
class CustomRabbitConfig implements RabbitListenerConfigurer {

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

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

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

				}
			);
    }

}
程序化端点注册

RabbitListenerEndpoint提供 Rabbit 端点的模型,并负责为该模型配置容器。RabbitListener除了注释检测到的端点之外,该基础架构还允许您以编程方式配置端点。以下示例显示了如何执行此操作:

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

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

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

应该注意的是,您也可以完全跳过使用@RabbitListener并通过RabbitListenerConfigurer.

带注释的端点方法签名

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

@Component
public class MyService {

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

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

  • 原始的org.springframework.amqp.core.Message.

  • MessageProperties从生的Message

  • com.rabbitmq.client.Channel收到消息的那个。

  • 从传入的org.springframework.messaging.MessageAMQP 消息转换而来。

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

  • @Headers-annotated 参数,也必须分配给以java.util.Map访问所有标题。

  • 转换后的有效载荷

不是受支持类型之一的未注释元素(即 MessageMessageProperties和)Message<?>Channel有效负载匹配。您可以通过使用 注释参数来明确这一点@Payload。您还可以通过添加额外的@Valid.

注入 Spring 的消息抽象的能力对于受益于存储在特定于传输的消息中的所有信息而特别有用,而不依赖于特定于传​​输的 API。以下示例显示了如何执行此操作:

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

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

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

@Configuration
@EnableRabbit
public class AppConfig implements RabbitListenerConfigurer {

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

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

从版本 2.3.7 开始,现在可以更轻松地添加Validator验证@RabbitListener@RabbitHandler @Payload参数。现在,您可以简单地将验证器添加到注册商本身。

@Configuration
@EnableRabbit
public class Config implements RabbitListenerConfigurer {
    ...
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
      registrar.setValidator(new MyValidator());
    }
}
当使用带有验证启动器的 Spring Boot 时,aLocalValidatorFactoryBean是自动配置的:
@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 方法。以下示例显示了如何执行此操作:

@Component
public class MyService {

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

}

从 1.5 版开始,您可以使用属性占位符和 SpEL 将队列名称外部化。以下示例显示了如何执行此操作:

@Component
public class MyService {

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

}

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

回复管理

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

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

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

如果您需要以独立于传输的方式设置其他标头,Message则可以返回 a ,如下所示:

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

或者,您可以使用容器工厂属性MessagePostProcessor中的abeforeSendReplyMessagePostProcessors添加更多标头。从版本 2.2.3 开始,被调用的 bean/方法在回复消息中可用,可以在消息后处理器中使用以将信息传回给调用者:

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

从 2.2.5 版本开始,可以配置 aReplyPostProcessor来修改回复消息在发送之前;correlationId在设置标头以匹配请求后调用它。

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

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

@SendTo值被假定为遵循模式的回复exchange和对,其中可以省略其中一个部分。有效值如下:routingKeyexchange/routingKey

  • thing1/thing2:replyTo交易所和routingKey. thing1/replyTo交换和默认(空)routingKeything2or /thing2:replyTo routingKey和默认(空)交换。 /或空:replyTo默认交换和默认routingKey.

此外,您可以@SendTo在没有value属性的情况下使用。这种情况相当于一个空sendTo模式。 @SendTo仅在入站消息没有replyToAddress属性时使用。

从 1.5 版本开始,该@SendTo值可以是一个 bean 初始化 SpEL 表达式,如下例所示:

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

表达式的计算结果必须为 a String,它可以是简单的队列名称(发送到默认交换器)或具有exchange/routingKey前面示例之前讨论的形式。

表达式在初始化期间被#{…​}评估一次。

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

从版本 1.6 开始,@SendTo可以是在运行时针对请求和回复评估的 SpEL 表达式,如以下示例所示:

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

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

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

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

  • result: 方法结果。

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

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

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

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

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

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

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

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

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

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

此内容类型将传递MessageProperties给转换器。默认情况下,为了向后兼容,转换器设置的任何内容类型属性在转换后都将被此值覆盖。如果您希望覆盖该行为,也将设置为,转换器设置的AmqpHeaders.CONTENT_TYPE_CONVERTER_WINS任何true值都将被保留。

多方法监听器

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

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

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

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

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

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

}

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

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

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

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

代理@RabbitListener和泛型

如果您的服务打算被代理(例如,在 的情况下@Transactional),则当接口具有通用参数时,您应该牢记一些注意事项。考虑以下示例:

interface TxService<P> {

   String handle(P payload, String header);

}

static class TxServiceImpl implements TxService<Foo> {

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

}

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

static class TxServiceImpl implements TxService<Foo> {

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

}
处理异常

默认情况下,如果带注释的侦听器方法抛出异常,则将其抛出到容器中,并且消息被重新排队并重新传递、丢弃或路由到死信交换,具体取决于容器和代理配置。没有任何东西返回给发件人。

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

默认情况下未配置这些。

您可以使用errorHandler来提供实现的 bean 名称RabbitListenerErrorHandler。这个功能接口有一个方法,如下:

@FunctionalInterface
public interface RabbitListenerErrorHandler {

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

}

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

returnExceptions属性 when会true导致异常返回给发送者。异常被包装在一个RemoteInvocationResult对象中。在发送方,有一个 available RemoteInvocationAwareMessageConverterAdapter,如果配置到 中RabbitTemplate,它会重新抛出服务器端异常,包裹在AmqpRemoteException. 服务器异常的堆栈跟踪是通过合并服务器和客户端堆栈跟踪来合成的。

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

从版本 2.1.7 开始,Channel在消息消息头中可用;这允许您在使用时确认或确认失败的消息AcknowledgeMode.MANUAL

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 开始,如果抛出消息转换异常,将调用错误处理程序,null参数中带有message。这允许应用程序向调用者发送一些结果,表明收到了格式错误的消息。以前,此类错误由容器抛出和处理。

容器管理

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

您还可以通过使用它来获取对单个容器的引用id,使用getListenerContainer(String id) - 例如,registry.getListenerContainer("multi")对于由上面的代码片段创建的容器。

从 1.5.2 版本开始,您可以id使用getListenerContainerIds().

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

@RabbitListener 与批处理

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

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

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

// or

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

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

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

同样从 2.2 版开始。当一次接收一个批处理消息时,最后一条消息包含一个设置为true. 可以通过将@Header(AmqpHeaders.LAST_IN_BATCH)boolean last` 参数添加到侦听器方法来获取此标头。标头映射自MessageProperties.isLastInBatch(). 此外,AmqpHeaders.BATCH_SIZE在每个消息片段中填充批处理的大小。

此外,新属性consumerBatchEnabled已添加到SimpleMessageListenerContainer. 如果这是真的,容器将创建一批消息,最多batchSize; receiveTimeout如果没有新消息到达,则交付部分批次。如果收到生产者创建的批次,则将其分批并添加到消费者端批次;因此实际传递的消息数可能会超过batchSize,这表示从代理收到的消息数。 deBatchingEnabled为真时必须consumerBatchEnabled为真;容器工厂将强制执行此要求。

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

使用consumerBatchEnabled@RabbitListener

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

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

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

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

  • 第三个是使用转换后的有效负载调用的,无法访问 headers/properteis。

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

使用容器工厂

引入了侦听器容器工厂以支持@RabbitListener并使用 注册容器RabbitListenerEndpointRegistry,如编程端点注册中所述。

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

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

  • 使用 SimpleRabbitListenerEndpoint

  • 创建后添加监听器

以下示例显示如何使用 aSimpleRabbitListenerEndpoint创建侦听器容器:

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

以下示例展示了如何在创建后添加监听器:

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

在任何一种情况下,侦听器也可以是ChannelAwareMessageListener,因为它现在是 的子接口MessageListener

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

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

从 2.1 版开始,可以使用异步返回类型和指定@RabbitListener(and ) 方法,从而异步发送回复。@RabbitHandlerListenableFuture<?>Mono<?>

必须配置监听容器工厂AcknowledgeMode.MANUAL,使消费者线程不会确认消息;相反,异步完成将在异步操作完成时确认或确认消息。当异步结果完成并出现错误时,消息是否重新排队取决于抛出的异常类型、容器配置和容器错误处理程序。默认情况下,消息将被重新排队,除非容器的defaultRequeueRejected属性设置为falsetrue默认情况下)。如果异步结果以 完成AmqpRejectAndDontRequeueException,则消息将不会重新排队。如果容器的defaultRequeueRejected属性是false,您可以通过将未来的异常设置为ImmediateRequeueException并且消息将被重新排队。如果在侦听器方法中发生阻止创建异步结果对象的异常,则必须捕获该异常并返回适当的返回对象,该对象将导致消息被确认或重新排队。

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

线程和异步消费者

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

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

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

中的Executor配置在创建连接时CachingConnectionFactory传入RabbitMQ Client,它的线程用于向监听容器传递新消息。如果未配置,则客户端使用内部线程池执行器,(在撰写本文时)Runtime.getRuntime().availableProcessors() * 2每个连接的池大小为 。

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

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

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

选择容器

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

SMLC 提供以下功能,但 DMLC 不提供:

  • batchSize: 使用 SMLC,你可以设置这个来控制在一个事务中传递多少消息或者减少 ack 的数量,但是它可能会导致失败后重复传递的数量增加。(DMLC 确实有messagesPerAck,您可以使用它来减少确认,与 withbatchSize和 SMLC 相同,但它不能用于事务 - 每条消息都在单独的事务中传递和确认)。

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

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

但是,与 SMLC 相比,DMLC 具有以下优势:

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

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

  • 线程在消费者之间共享,而不是为 SMLC 中的每个消费者提供专用线程。但是,请参阅Threading and Asynchronous Consumers中有关连接工厂配置的重要说明。

有关适用于每个容器的配置属性的信息,请参阅消息侦听器容器配置。

检测空闲异步消费者

虽然效率很高,但异步消费者的一个问题是检测它们何时处于空闲状态——如果一段时间内没有消息到达,用户可能想要采取一些行动。

从 1.6 版开始,现在可以将侦听器容器配置为 ListenerContainerIdleEvent在经过一段时间后发布消息而没有消息传递。当容器空闲时,每idleEventInterval毫秒发布一个事件。

要配置此功能,idleEventInterval请在容器上设置。以下示例显示了如何在 XML 和 Java 中执行此操作(对于 aSimpleMessageListenerContainer和 a SimpleRabbitListenerContainerFactory):

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

在每种情况下,当容器空闲时,每分钟发布一次事件。

事件消费

您可以通过实现来捕获空闲事件ApplicationListener ——一个普通的监听器,或者一个缩小到只接收这个特定事件的监听器。您也可以使用@EventListenerSpring Framework 4.2 中引入的 ,。

下面的示例将@RabbitListenerand组合@EventListener成一个类。您需要了解应用程序侦听器获取所有容器的事件,因此如果您想根据哪个容器空闲采取特定操作,您可能需要检查侦听器 ID。您也可以将@EventListener condition用于此目的。

事件有四个属性:

  • source:监听器容器实例

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

  • idleTime: 发布事件时容器空闲的时间

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

下面的例子展示了如何使用@RabbitListener@EventListener注解来创建监听器:

public class Listener {

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

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

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

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

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

计时器被命名spring.rabbitmq.listener并具有以下标签:

  • listenerId:(侦听器 id 或容器 bean 名称)

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

  • resultsuccessfailure

  • exceptionnoneListenerExecutionFailedException

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

4.1.7. 容器和代理命名队列

虽然最好将AnonymousQueue实例用作自动删除队列,但从 2.1 版开始,您可以将代理命名队列与侦听器容器一起使用。以下示例显示了如何执行此操作:

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

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

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

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

4.1.8。消息转换器

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

public interface MessageConverter {

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

    Object fromMessage(Message message) throws MessageConversionException;

}

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

void convertAndSend(Object message) throws AmqpException;

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

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

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

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

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

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

Object receiveAndConvert() throws AmqpException;

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

MessageConverter策略的默认实现称为SimpleMessageConverter. RabbitTemplate如果您没有明确配置替代方案,这是一个实例使用的转换器。它处理基于文本的内容、序列化的 Java 对象和字节数组。

从一个转换Message

如果输入的内容类型Message以“text”开头(例如,“text/plain”),它还会检查 content-encoding 属性以确定将Message主体字节数组转换为 Java时要使用的字符集String。如果没有在 input 上设置 content-encoding 属性Message,则默认使用 UTF-8 字符集。如果您需要覆盖该默认设置,您可以配置 的实例SimpleMessageConverter,设置其defaultCharset属性,然后将其注入到RabbitTemplate实例中。

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

对于所有其他内容类型,直接SimpleMessageConverterMessage正文内容作为字节数组返回。

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

转换为Message

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

SerializerMessageConverter

这个转换器类似于,SimpleMessageConverter除了它可以配置其他 Spring Framework SerializerDeserializer实现进行application/x-java-serialized-object转换。

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

Jackson2JsonMessageConverter

本节介绍使用Jackson2JsonMessageConverterMessage. 它有以下几个部分:

转换为Message

上一节提到过,一般不推荐依赖Java序列化。JSON(JavaScript Object Notation)是一种相当常见的替代方案,它更灵活且可跨不同的语言和平台移植。转换器可以在任何RabbitTemplate实例上配置以覆盖其SimpleMessageConverter 默认使用。使用2.x 库Jackson2JsonMessageConvertercom.fasterxml.jackson以下示例配置了一个Jackson2JsonMessageConverter

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

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

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

此外,您可以根据TypeId标头中的值提供自定义映射。以下示例显示了如何执行此操作:

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

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

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

从 2.4.3 版本开始,如果有参数,转换器将不会添加contentEncoding消息属性;这也用于编码。添加了一个新方法:supportedMediaTypecharsetsetSupportedMediaType

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

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

从版本 2.4.3 开始,如果没有contentEncoding消息属性,转换器将尝试检测消息属性charset中的参数contentType并使用它。如果两者都不存在,如果supportedMediaType有一个charset参数,它将用于解码,并最终回退到该defaultCharset属性。添加了一个新方法setSupportedMediaType

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

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

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

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

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

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

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

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

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

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

但是,您可以创建自定义转换器并使用targetMethodmessage 属性来决定将 JSON 转换为哪种类型。

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

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

从版本 2.2.8 开始,默认情况下保留以前的行为。如果您有这样的自定义ObjectMapper并且想要忽略类型标头,并始终使用推断的类型进行转换,alwaysConvertToInferredType请将true. 这是为了向后兼容并避免尝试转换失败时的开销(使用标准ObjectMapper)。

使用 Spring 数据投影接口

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

interface SomeSample {

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

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

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

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

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

从一个Message转换RabbitTemplate

如前所述,类型信息在消息头中传达,以在从消息转换时帮助转换器。这在大多数情况下都可以正常工作。但是,当使用泛型类型时,它只能转换简单对象和已知的“容器”对象(列表、数组和映射)。从 2.0 版开始,Jackson2JsonMessageConverterimplementsSmartMessageConverter允许它与带有参数的新RabbitTemplate方法一起使用ParameterizedTypeReference。这允许转换复杂的泛型类型,如下例所示:

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

另一个选择是MarshallingMessageConverter. 它委托给 Spring OXM 库的实现MarshallerUnmarshaller策略接口。您可以在此处阅读有关该库的更多信息。在配置方面,最常见的是只提供构造函数参数,因为大多数实现Marshaller也实现了Unmarshaller。以下示例显示了如何配置MarshallingMessageConverter

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

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

两者都Jackson2XmlMessageConverter具有Jackson2JsonMessageConverter相同的基类:AbstractJackson2MessageConverter.

引入该类AbstractJackson2MessageConverter以替换已删除的类:AbstractJsonMessageConverter.

使用2.x 库Jackson2XmlMessageConvertercom.fasterxml.jackson

您可以像使用它一样使用它Jackson2JsonMessageConverter,只是它支持 XML 而不是 JSON。以下示例配置了一个Jackson2JsonMessageConverter

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

有关更多信息,请参阅Jackson2JsonMessageConverter

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

此类是在 1.4.2 版本中引入的,它允许MessageConverter基于MessageProperties. 默认情况下,SimpleMessageConverter如果没有contentType属性或存在与配置的转换器都不匹配的值,它会委托给 a。以下示例配置了一个ContentTypeDelegatingMessageConverter

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

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

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

如果您接受来自不受信任来源的带有content-typeof 的消息application/x-java-serialized-object,您应该考虑配置允许反序列化的包和类。这适用于SimpleMessageConverter以及SerializerMessageConverter当它被配置为 DefaultDeserializer隐式或通过配置使用时。

默认情况下,允许列表为空,这意味着所有类都被反序列化。

您可以设置模式列表,例如thing1.thing1.thing2.Cat.MySafeClass

按顺序检查模式,直到找到匹配项。如果没有匹配,SecurityException则抛出 a。

您可以使用allowedListPatterns这些转换器上的属性设置模式。

消息属性转换器

MessagePropertiesConverter策略接口用于 Rabbit ClientBasicProperties和 Spring AMQP之间的转换MessageProperties。默认实现 ( DefaultMessagePropertiesConverter) 通常足以满足大多数目的,但如果需要,您可以实现自己的。当大小不大于字节时,默认属性转换器将BasicProperties类型的元素转换LongString为实例。较大的实例不会被转换(参见下一段)。可以使用构造函数参数覆盖此限制。String1024LongString

从版本 1.6 开始,超过长字符串限制(默认值:1024)的标头现在 LongString默认保留为DefaultMessagePropertiesConverter. 您可以通过getBytes[]toString()getStream()方法访问内容。

以前,将DefaultMessagePropertiesConverter此类标头“转换”为 a DataInputStream(实际上它只是引用了LongString实例的DataInputStream)。在输出时,此标头未转换(转换为字符串除外——例如,java.io.DataInputStream@1d057a39通过调用toString()流)。

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

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

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

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

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

对于入站消息:

  • STRING: 只有correlationIdString属性被映射

  • BYTES: 只有correlationId属性被映射

  • BOTH: 两个属性都被映射

对于出站消息:

  • STRING: 只有correlationIdString属性被映射

  • BYTES: 只有correlationId属性被映射

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

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

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

4.1.9。修改消息 - 压缩等

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

从Message Converters中可以看出,一个这样的扩展点是在AmqpTemplate convertAndReceive操作中,您可以在其中提供一个MessagePostProcessor. 例如,在您的 POJO 转换后,您MessagePostProcessor可以在Message.

从版本 1.4.2 开始,额外的扩展点被添加到RabbitTemplate-setBeforePublishPostProcessors()setAfterReceivePostProcessors(). 第一个使后处理器在发送到 RabbitMQ 之前立即运行。使用批处理时(请参阅批处理),在组装批处理之后和发送批处理之前调用它。第二个在收到消息后立即调用。

这些扩展点用于压缩等功能,为此提供了几种MessagePostProcessor实现。 GZipPostProcessor,ZipPostProcessorDeflaterPostProcessor在发送前压缩消息, 和GUnzipPostProcessor,UnzipPostProcessorInflaterPostProcessor解压缩收到的消息。

从版本 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压缩器上的属性为:. 当您的消费者升级到 2.2.11 或更高版本时,您可以恢复为默认值 `, `。

类似的,SimpleMessageListenerContainer也有一个setAfterReceivePostProcessors()方法,让容器接收到消息后进行解压。

从版本 2.1.4 开始,addBeforePublishPostProcessors()addAfterReceivePostProcessors()已添加到RabbitTemplate允许将新的后处理器分别附加到发布前和接收后后处理器的列表中。还提供了删除后处理器的方法。同样,AbstractMessageListenerContainer也添加了addAfterReceivePostProcessors()removeAfterReceivePostProcessor()方法。有关更多详细信息,请参阅RabbitTemplate和的 Javadoc 。AbstractMessageListenerContainer

4.1.10。请求/回复消息

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

类似的请求-回复方法也MessageConverter适用于请求和回复。这些方法被命名为convertSendAndReceive. 有关更多详细信息,请参阅的Javadoc 。AmqpTemplate

从版本 1.5.0 开始,每个sendAndReceive方法变体都有一个重载版本,它采用CorrelationData. 与正确配置的连接工厂一起,这可以为操作的发送方接收发布者确认。有关详细信息,请参阅相关发布者确认和返回以及JavadocRabbitOperations

从 2.0 版开始,这些方法的变体 ( convertSendAndReceiveAsType) 采用附加ParameterizedTypeReference参数来转换复杂的返回类型。模板必须配置为SmartMessageConverter. 有关更多信息,请参阅MessageWithRabbitTemplate转换。

从 2.1 版开始,您可以配置RabbitTemplate选项noLocalReplyConsumer来控制noLocal回复消费者的标志。这是false默认设置。

回复超时

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

此功能使用发布商退货。您可以通过设置publisherReturnstrueon来启用它CachingConnectionFactory(请参阅Publisher Confirms and Returns)。此外,您一定没有ReturnCallbackRabbitTemplate.

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

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

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

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

从 1.6 版开始,如果您希望对每个回复使用临时的、独占的、自动删除队列,请将useTemporaryReplyQueues属性设置为true. 如果您设置一个replyAddress.

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

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

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

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

与回复队列的消息关联

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

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

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

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

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

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

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

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

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

在版本 1.5.0 之前,该reply-address属性不可用。始终使用默认交换和reply-queue名称作为路由键来路由回复。这仍然是默认设置,但您现在可以指定新reply-address属性。reply-address可以包含带有表单的地址,并且<exchange>/<routingKey>回复被路由到指定的交换器并路由到与路由键绑定的队列。reply-address优先于reply-queue。只有reply-address在使用时,<reply-listener>必须将其配置为单独的<listener-container>组件。reply-addressand reply-queue(或上的queues属性<listener-container>)必须在逻辑上引用相同的队列。

使用此配置,aSimpleListenerContainer用于接收回复,RabbitTemplateMessageListener. 在使用命名空间元素定义模板时<rabbit:template/>,如前面的示例所示,解析器将模板中的容器和连线定义为侦听器。

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

如果您定义 your RabbitTemplateas a<bean/>或使用@Configuration类将其定义为 an @Beanor 当您以编程方式创建模板时,您需要自己定义和连接回复侦听器容器。如果您不这样做,模板将永远不会收到回复并最终超时并返回 null 作为对sendAndReceive方法调用的回复。

从版本 1.5 开始,RabbitTemplate检测它是否已配置为MessageListener接收回复。如果没有,尝试发送和接收带有回复地址的消息会失败IllegalStateException(因为从未收到回复)。

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

在自己连接回复侦听器和模板时,确保模板replyAddress和容器的queues(或queueNames)属性引用同一个队列很重要。该模板将回复地址插入到出站消息replyTo属性中。

以下清单显示了如何手动连接 bean 的示例:

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

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

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

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

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

这个测试用例显示了一个连接固定回复队列的完整示例RabbitTemplate,以及一个处理请求并返回回复的“远程”侦听器容器。

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

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

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

异步兔子模板

1.6 版引入了AsyncRabbitTemplate. 这sendAndReceive与. convertSendAndReceive_ AmqpTemplate但是,它们不是阻塞,而是返回一个ListenableFuture.

这些sendAndReceive方法返回一个RabbitMessageFuture. 这些convertSendAndReceive方法返回一个RabbitConverterFuture.

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

@Autowired
private AsyncRabbitTemplate template;

...

public void doSomeWorkAndGetResultLater() {

    ...

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

    // do some more work

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

    ...

}

public void doSomeWorkAndGetResultAsync() {

    ...

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

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

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

    });

    ...

}

如果mandatory设置了,则消息无法投递,future 会抛出ExecutionException一个原因为 的AmqpMessageReturnedException,封装了返回的消息和返回的信息。

如果enableConfirms设置了,future 有一个名为 的属性confirm,它本身就是一个表明成功发布ListenableFuture<Boolean>的属性。true如果确认未来是false,则RabbitFuture有一个称为 的进一步属性nackCause,其中包含失败的原因(如果可用)。

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

您可以将receiveTimeout模板上的属性设置为超时回复(默认为30000- 30 秒)。如果发生超时,future 会以AmqpReplyTimeoutException.

该模板实现SmartLifecycle. 在有待处理的回复时停止模板会导致待处理的Future实例被取消。

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

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

public AsyncRabbitTemplate(RabbitTemplate template)

请参阅RabbitMQ 直接回复以将直接回复与同步RabbitTemplate.

2.0 版引入了这些方法的变体 ( convertSendAndReceiveAsType),它们采用附加ParameterizedTypeReference参数来转换复杂的返回类型。您必须RabbitTemplate使用SmartMessageConverter. 有关更多信息,请参阅MessageWithRabbitTemplate转换。

使用 AMQP 进行 Spring 远程处理
此功能已弃用,将在 3.0 中删除。它已被设置为 true的处理异常并在发送端配置 a取代了很长时间。有关详细信息,请参阅处理异常returnExceptionsRemoteInvocationAwareMessageConverterAdapter

Spring 框架具有通用的远程处理能力,允许使用各种传输的远程过程调用 (RPC) 。Spring-AMQP 支持类似的机制,AmqpProxyFactoryBean在客户端和AmqpInvokerServiceExporter服务器上都有 a。这提供了基于 AMQP 的 RPC。在客户端,如前所述RabbitTemplate使用a 。在服务器端,调用者(配置为 a )接收消息,调用配置的服务,并使用入站消息的信息返回回复。MessageListenerreplyTo

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

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

在服务器端,AmqpInvokerServiceExporter两者都有AmqpTemplateMessageConverter属性。MessageConverter目前,未使用模板。如果您需要提供自定义消息转换器,则应通过设置messageConverter属性来提供。在客户端,您可以将自定义消息转换器添加到,使用其属性AmqpTemplate提供给。AmqpProxyFactoryBeanamqpTemplate

以下清单显示了示例客户端和服务器配置:

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

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

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

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

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

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

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

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

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

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

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

4.1.11。配置代理

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

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

public interface AmqpAdmin {

    // Exchange Operations

    void declareExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();

    String declareQueue(Queue queue);

    void deleteQueue(String queueName);

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

    void purgeQueue(String queueName, boolean noWait);

    // Binding Operations

    void declareBinding(Binding binding);

    void removeBinding(Binding binding);

    Properties getQueueProperties(String queueName);

}

另请参阅范围操作

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

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

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

这与AnonymousQueue框架生成唯一的 ( UUID) 名称并设置durablefalseand exclusive, autoDeleteto的情况形成对比true<rabbit:queue/>具有空(或缺失)属性的Aname总是创建一个AnonymousQueue.

请参阅AnonymousQueue以了解为什么AnonymousQueue优先于代理生成的队列名称以及如何控制名称的格式。从 2.1 版开始,匿名队列的声明参数默认Queue.X_QUEUE_LEADER_LOCATOR设置为client-local。这确保在应用程序连接的节点上声明队列。声明性队列必须具有固定名称,因为它们可能在上下文的其他地方被引用——例如在以下示例中显示的侦听器中:

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

该接口的 RabbitMQ 实现是RabbitAdmin,使用 Spring XML 配置时,类似于以下示例:

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

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

CachingConnectionFactory缓存模式为CHANNEL(默认)时,RabbitAdmin实现会自动延迟声明队列、交换器和在同一ApplicationContext. 一旦Connection向代理打开 a ,就会声明这些组件。有一些命名空间特性使这非常方便——例如,在 Stocks 示例应用程序中,我们有以下内容:

<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

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

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

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

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

队列可以配置额外的参数——例如,x-message-ttl. 当您使用命名空间支持时,它们以Map参数-名称/参数-值对的形式提供,它们是通过使用<rabbit:queue-arguments>元素定义的。以下示例显示了如何执行此操作:

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

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

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

提供混合类型的参数时,您必须提供每个条目元素的类型。以下示例显示了如何执行此操作:

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

使用 Spring Framework 3.2 及更高版本,可以更简洁地声明这一点,如下所示:

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

当您使用 Java 配置时,通过类上的方法Queue.X_QUEUE_LEADER_LOCATOR支持将参数作为第一类属性。从 2.1 版开始,匿名队列的声明默认设置为该属性。这可确保在应用程序连接到的节点上声明队列。setLeaderLocator()Queueclient-local

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

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

可以通过将ignore-declaration-exceptions属性设置trueRabbitAdmin实例来修改此行为。此选项指示RabbitAdmin记录异常并继续声明其他元素。配置RabbitAdmin使用 Java 时,会调用此属性ignoreDeclarationExceptions。这是适用于所有元素的全局设置。队列、交换和绑定具有仅适用于这些元素的类似属性。

在 1.6 版之前,此属性仅在通道上发生事件时才会生效IOException,例如当前属性与所需属性不匹配时。现在,这个属性对任何异常都生效,包括TimeoutException和其他。

此外,任何声明异常都会导致发布 a DeclarationExceptionEvent,这是上下文中ApplicationEvent的任何人都可以使用的a 。ApplicationListener该事件包含对管理员的引用、正在声明的元素和Throwable.

标头交换

从 1.3 版开始,您可以将 配置HeadersExchange为匹配多个标头。您还可以指定是否必须匹配任何或所有标题。以下示例显示了如何执行此操作:

<rabbit:headers-exchange name="headers-test">
    <rabbit:bindings>
        <rabbit:binding queue="bucket">
            <rabbit:binding-arguments>
                <entry key="foo" value="bar"/>
                <entry key="baz" value="qux"/>
                <entry key="x-match" value="all"/>
            </rabbit:binding-arguments>
        </rabbit:binding>
    </rabbit:bindings>
</rabbit:headers-exchange>

从 1.6 版开始,您可以Exchanges使用internal标志(默认为false)进行 配置,并且通过 a (如果应用程序上下文中存在)在ExchangeBroker 上正确配置了这样的标志。RabbitAdmin如果internal标志true用于交换,RabbitMQ 不会让客户端使用交换。这对于死信交换或交换到交换绑定很有用,您不希望发布者直接使用交换。

要了解如何使用 Java 配置 AMQP 基础架构,请查看 Stock 示例应用程序,其中有@ConfigurationAbstractStockRabbitConfiguration,而类又具有 RabbitClientConfigurationRabbitServerConfiguration类。以下清单显示了 的代码AbstractStockRabbitConfiguration

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {

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

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }

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

    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }

    // additional code omitted for brevity

}

在 Stock 应用程序中,使用以下@Configuration类配置服务器:

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {

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

这是整个@Configuration类继承链的结束。最终结果是,TopicExchangeQueue在应用程序启动时向代理声明。在服务器配置中没有绑定TopicExchange到队列,因为这是在客户端应用程序中完成的。但是,库存请求队列会自动绑定到 AMQP 默认交换。此行为由规范定义。

客户端@Configuration类更有趣一些。其声明如下:

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {

    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;

    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }

    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }

    // additional code omitted for brevity

}

客户端declareQueue()通过AmqpAdmin. 它使用属性文件中外部化的路由模式将该队列绑定到市场数据交换。

用于队列和交换的 Builder API

1.6 版引入了一个方便的流式 API,用于在使用 Java 配置时进行配置Queue和对象。Exchange以下示例显示了如何使用它:

@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现在默认创建持久交换,以与各个AbstractExchange类上的简单构造函数保持一致。要与构建器进行非持久交换,请.durable(false)在调用之前使用.build(). durable()不再提供不带参数的方法。

2.2 版引入了流畅的 API 来添加“众所周知的”交换和队列参数……​

@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}
声明交换、队列和绑定的集合

Declarable您可以将对象集合(QueueExchangeBinding)包装在Declarables对象中。检测应用程序上下文中的RabbitAdmin此类 bean(以及离散Declarablebean),并在建立连接时(最初和连接失败后)在代理上声明包含的对象。以下示例显示了如何执行此操作:

@Configuration
public static class Config {

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

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

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

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}
在 2.1 之前的版本中,您可以Declarable通过定义 bean 类型来声明多个实例Collection<Declarable>。在某些情况下,这可能会导致不良副作用,因为管理员必须遍历所有Collection<?>bean。

2.2 版将getDeclarablesByType方法添加到Declarables; 这可以作为一种方便使用,例如,在声明侦听器容器 bean 时。

public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}
有条件的声明

默认情况下,所有队列、交换器和绑定都由应用程序上下文中的所有RabbitAdmin实例(假设它们具有)声明。auto-startup="true"

从版本 2.1.9 开始,RabbitAdmin有一个新属性explicitDeclarationsOnlyfalse默认情况下);当设置为 时true,管理员将仅声明明确配置为由该管理员声明的 bean。

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

代表这些元素的类实现Declarable了 ,它有两个方法:shouldDeclare()getDeclaringAdmins()。使用RabbitAdmin这些方法来确定特定实例是否应该实际处理其Connection.

这些属性可用作命名空间中的属性,如以下示例所示:

<rabbit:admin id="admin1" connection-factory="CF1" />

<rabbit:admin id="admin2" connection-factory="CF2" />

<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />

<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />

<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />

<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />

<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />

<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
    <rabbit:bindings>
        <rabbit:binding key="foo" queue="bar"/>
    </rabbit:bindings>
</rabbit:direct-exchange>
默认情况下,auto-declare属性是trueand,如果declared-by未提供(或为空),则所有RabbitAdmin实例都声明该对象(只要 admin 的auto-startup属性为true,默认值,并且 admin 的explicit-declarations-only属性为 false)。

同样,您可以使用基于 Java 的@Configuration方式来实现相同的效果。在以下示例中,组件由`admin2` 声明admin1但不是由`admin2` 声明:

@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}

@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}

@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}

@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}

@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}
关于idname属性的注释

和元素上的name属性反映了代理中实体的名称。对于队列,如果省略 ,则会创建一个匿名队列(请参阅 参考资料)。<rabbit:queue/><rabbit:exchange/>nameAnonymousQueue

在 2.0 之前的版本中,name也注册为 bean 名称别名(类似于nameon<bean/>元素)。

这导致了两个问题:

  • 它阻止了同名队列和交换的声明。

  • 如果别名包含 SpEL 表达式 ( #{…​}),则不会解析该别名。

从 2.0 版开始,如果您使用 anid aname属性声明这些元素之一,则该名称不再声明为 bean 名称别名。如果你想声明一个队列并与之交换name,你必须提供一个id.

如果元素只有一个name属性,则没有变化。bean 仍然可以被引用name ——例如,在绑定声明中。但是,如果名称中包含 SpEL,您仍然不能引用它——您必须提供一个id以供参考。

AnonymousQueue

通常,当您需要一个唯一命名的、排他的、自动删除的队列时,我们建议您使用AnonymousQueue 代替代理定义的队列名称(使用""作为Queue名称会导致代理生成队列名称)。

这是因为:

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

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

AnonymousQueue您可以控制实例使用的队列名称的格式。

默认情况下,队列名称以spring.gen-base64 表示为前缀UUID ,例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g.

AnonymousQueue.NamingStrategy您可以在构造函数参数中提供实现。以下示例显示了如何执行此操作:

@Bean
public Queue anon1() {
    return new AnonymousQueue();
}

@Bean
public Queue anon2() {
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}

@Bean
public Queue anon3() {
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}

第一个 bean 生成一个队列名称,前缀spring.gen-后跟一个 base64 表示的UUID - 例如:spring.gen-MRBv9sqISkuCiPfOYfpo4g. 第二个 bean 生成一个队列名称,前缀something-后跟一个 base64 表示的UUID. 第三个 bean 仅使用 UUID(无 base64 转换)生成名称——例如,f20c818a-006b-4416-bf91-643590fedb0e.

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

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

您可以在使用 XML 配置时指定命名策略。该naming-strategy属性存在于<rabbit:queue>实现AnonymousQueue.NamingStrategy. 以下示例显示了如何以各种方式指定命名策略:

<rabbit:queue id="uuidAnon" />

<rabbit:queue id="springAnon" naming-strategy="uuidNamer" />

<rabbit:queue id="customAnon" naming-strategy="customNamer" />

<bean id="uuidNamer" class="org.springframework.amqp.core.AnonymousQueue.UUIDNamingStrategy" />

<bean id="customNamer" class="org.springframework.amqp.core.AnonymousQueue.Base64UrlNamingStrategy">
    <constructor-arg value="custom.gen-" />
</bean>

第一个示例创建名称,例如spring.gen-MRBv9sqISkuCiPfOYfpo4g. 第二个示例使用 UUID 的字符串表示创建名称。第三个示例创建名称,例如custom.gen-MRBv9sqISkuCiPfOYfpo4g.

您还可以提供自己的命名策略 bean。

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

恢复自动删除声明

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

从 2.4 版开始,admin 有了一个新属性redeclareManualDeclarations;当为 true 时,除了应用程序上下文中的 bean 之外,管理员还将恢复这些实体。

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

最后,调用resetAllManualDeclarations()将阻止恢复任何先前声明的实体。

4.1.12。代理事件监听器

启用事件交换插件后,如果您将类型的 bean 添加BrokerEventListener到应用程序上下文,它会将选定的代理事件作为BrokerEvent实例发布,这些事件可以与普通的 SpringApplicationListener@EventListener方法一起使用。事件由代理发布到主题交换amq.rabbitmq.event,每种事件类型具有不同的路由键。侦听器使用事件键,用于将 an 绑定AnonymousQueue到交换,因此侦听器仅接收选定的事件。由于它是主题交换,因此可以使用通配符(以及显式请求特定事件),如以下示例所示:

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

您可以使用普通的 Spring 技术进一步缩小单个事件侦听器中接收到的事件的范围,如以下示例所示:

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

4.1.13。延迟消息交换

1.6 版引入了对 延迟消息交换插件的支持

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

要使用 aRabbitAdmin将交换声明为延迟,您可以将delayed交换 bean 上的属性设置为 true。使用RabbitAdmin交换类型(DirectFanout等)来设置x-delayed-type参数并使用类型声明交换x-delayed-message

使用 XML 配置交换 bean 时,该delayed属性(默认值:)false也可用。以下示例显示了如何使用它:

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

要发送延迟消息,您可以x-delay通过 设置标头MessageProperties,如以下示例所示:

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. 它是一个单独的属性,用于避免意外传播到从输入消息生成的输出消息。

4.1.14。RabbitMQ REST API

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

跃点依赖 ( com.rabbitmq:http-client) 现在也是optional.

有关更多信息,请参阅他们的 Javadoc。

4.1.15。异常处理

RabbitMQ Java 客户端的许多操作都可能引发检查异常。例如,有很多情况IOException下可能会抛出实例。、RabbitTemplateSimpleMessageListenerContainer其他 Spring AMQP 组件捕获这些异常并将它们转换为AmqpException层次结构中的异常之一。这些在“org.springframework.amqp”包中定义,AmqpException是层次结构的基础。

当监听器抛出异常时,它被包裹在一个ListenerExecutionFailedException. 通常消息被代理拒绝并重新排队。设置defaultRequeueRejectedfalse会导致消息被丢弃(或路由到死信交换)。正如消息侦听器和异步案例中所讨论的,侦听器可以抛出一个AmqpRejectAndDontRequeueException(或ImmediateRequeueAmqpException)来有条件地控制此行为。

但是,存在一类错误,侦听器无法控制行为。当遇到无法转换的消息(例如,无效的content_encoding标头)时,会在消息到达用户代码之前引发一些异常。defaultRequeueRejected设置为true(默认)(或抛出),此类消息将ImmediateRequeueAmqpException一遍又一遍地重新传递。在 1.3.2 版本之前,用户需要编写自定义ErrorHandler,如异常处理中所述,以避免这种情况。

从版本 1.3.2 开始,默认ErrorHandler值现在是ConditionalRejectingErrorHandler拒绝(并且不重新排队)因不可恢复错误而失败的消息。具体来说,它拒绝失败并出现以下错误的消息:

  • o.s.amqp…​MessageConversionException: 可以在使用 . 转换传入消息有效负载时抛出MessageConverter

  • o.s.messaging…​MessageConversionException@RabbitListener:如果在映射到方法时需要额外的转换,则可以由转换服务抛出。

  • o.s.messaging…​MethodArgumentNotValidException@Valid: 如果在侦听器中使用验证(例如,)并且验证失败,则可以抛出。

  • o.s.messaging…​MethodArgumentTypeMismatchException:如果入站消息被转换为对目标方法不正确的类型,则可以抛出。例如,参数被声明为Message<Foo>Message<Bar>被接收。

  • java.lang.NoSuchMethodException:在 1.6.3 版本中添加。

  • java.lang.ClassCastException:在 1.6.3 版本中添加。

您可以使用 a 配置此错误处理程序的实例,FatalExceptionStrategy以便用户可以为有条件的消息拒绝提供自己的规则 - 例如,对BinaryExceptionClassifierSpring 重试(消息侦听器和异步案例)的委托实现。此外,ListenerExecutionFailedExceptionnow 有一个failedMessage可以在决策中使用的属性。如果FatalExceptionStrategy.isFatal()方法返回true,错误处理程序将抛出AmqpRejectAndDontRequeueException. FatalExceptionStrategy当异常被确定为致命时,默认会记录一条警告消息。

从 1.6.3 版开始,将用户异常添加到致命列表的一种便捷方法是子类ConditionalRejectingErrorHandler.DefaultExceptionStrategy化并覆盖该isUserCauseFatal(Throwable cause)方法以返回true致命异常。

处理 DLQ 消息的常见模式是time-to-live在这些消息上设置 a 以及附加的 DLQ 配置,以便这些消息过期并被路由回主队列以重试。这种技术的问题是导致致命异常的消息永远循环。从 2.1 版开始,ConditionalRejectingErrorHandler检测x-death消息的标头会导致引发致命异常。消息被记录并丢弃。您可以通过在to上设置discardFatalsWithXDeath属性来恢复到以前的行为。ConditionalRejectingErrorHandlerfalse

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

4.1.16。交易

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

有两种方法可以向框架发送所需的事务语义信号。在RabbitTemplateandSimpleMessageListenerContainer中,都有一个标志channelTransactedif true,它告诉框架使用事务通道并以提交或回滚(取决于结果)结束所有操作(发送或接收),并发出回滚信号。另一个信号是提供一个带有 SpringPlatformTransactionManager实现的外部事务作为正在进行的操作的上下文。如果框架在发送或接收消息时已经有事务在进行,并且channelTransacted标志为true,则消息事务的提交或回滚将延迟到当前事务结束。如果channelTransacted国旗是false,没有事务语义适用于消息传递操作(它是自动确认的)。

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

对于带有 的同步用例RabbitTemplate,外部事务由调用者提供,根据喜好以声明方式或命令方式提供(通常的 Spring 事务模型)。以下示例显示了一种声明性方法(通常首选,因为它是非侵入性的),其中模板已配置为channelTransacted=true

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

在前面的示例中,String有效负载在标记为 的方法中作为消息正文被接收、转换和发送@Transactional。如果数据库处理因异常而失败,则将传入消息返回给代理,并且不发送传出消息。这适用于具有RabbitTemplate事务方法链内部的任何操作(除非,例如,Channel直接操纵 以提早提交事务)。

对于具有 的异步用例SimpleMessageListenerContainer,如果需要外部事务,则必须由容器在设置侦听器时请求它。为了发出需要外部事务的信号,用户PlatformTransactionManager在配置容器时提供容器的实现。以下示例显示了如何执行此操作:

@Configuration
public class ExampleExternalTransactionAmqpConfiguration {

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

}

在前面的示例中,事务管理器作为从另一个 bean 定义(未显示)注入的依赖项添加,并且channelTransacted标志也设置为true. 效果是,如果监听器因异常而失败,则事务回滚,消息也返回给代理。值得注意的是,如果事务未能提交(例如,由于数据库约束错误或连接问题),AMQP 事务也会回滚,并将消息返回给代理。这有时被称为“Best Efforts 1 Phase Commit”,是一种非常强大的可靠消息传递模式。如果channelTransacted标志设置为false(默认)在前面的例子中,仍然会为监听器提供外部事务,但是所有的消息传递操作都会被自动确认,所以效果是即使在业务操作回滚时也提交消息传递操作。

条件回滚

在 1.6.6 版本之前,在使用外部事务管理器(例如 JDBC)时向容器添加回滚规则transactionAttribute无效。异常总是回滚事务。

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

第一个问题已得到纠正,现在可以正确应用规则。此外,ListenerFailedRuleBasedTransactionAttribute现在提供。它是 的子类RuleBasedTransactionAttribute,唯一的区别是它知道ListenerExecutionFailedException并使用规则的此类异常的原因。该事务属性可以直接在容器中使用,也可以通过事务通知使用。

以下示例使用此规则:

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

AMQP 事务仅适用于发送到代理的消息和确认。因此,当 Spring 事务回滚并接收到消息时,Spring AMQP 不仅必须回滚事务,还必须手动拒绝消息(有点像 nack,但这不是规范所说的)。对消息拒绝采取的操作与事务无关,并且取决于defaultRequeueRejected属性(默认值:true)。有关拒绝失败消息的更多信息,请参阅消息侦听器和异步案例

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

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

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

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

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

使用 Java 配置,您可以使用以下 bean 设置新的 RabbitTransactionManager:

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

如果您更喜欢 XML 配置,可以在 XML 应用程序上下文文件中声明以下 bean:

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

将 RabbitMQ 事务与其他一些(例如 DBMS)事务同步提供了“尽力而为的一个阶段提交”语义。在事务同步的完成后阶段,RabbitMQ 事务可能无法提交。这被spring-tx基础设施记录为错误,但调用代码不会引发异常。从版本 2.3.10 开始,您可以ConnectionUtils.checkAfterCompletion()在事务提交到处理事务的同一线程上之后调用。如果没有发生异常,它将简单地返回;否则它将抛出一个AfterCompletionFailedException具有表示完成同步状态的属性。

通过调用启用此功能ConnectionFactoryUtils.enableAfterCompletionFailureCapture(true);这是一个全局标志,适用于所有线程。

4.1.17。消息侦听器容器配置

SimpleMessageListenerContainer配置与事务和服务质量相关的(SMLC) 和(DMLC)有很多选项,其中一些选项DirectMessageListenerContainer相互交互。适用于 SMLC、DMLC 或StreamListenerContainer(StLC) 的属性(请参阅使用 RabbitMQ 流插件由相应列中的复选标记指示。请参阅选择容器以获取帮助您决定哪个容器适合您的应用程序的信息。

下表显示了使用命名空间配置<rabbit:listener-container/>. 该type元素上的属性可以是simple(默认)或分别direct指定一个SMLCDMLC。命名空间未公开某些属性。这些由N/A属性指示。

表 3. 消息侦听器容器的配置选项
属性(属性) 描述 SMLC DMLC 标准液相色谱法

ackTimeout
(不适用)

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

刻度线

acknowledgeMode
(承认)

  • NONE: 不发送任何确认(与 不兼容channelTransacted=true)。RabbitMQ 将此称为“自动确认”,因为代理假定所有消息都已确认,而消费者没有任何操作。

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

  • AUTO:容器自动确认消息,除非MessageListener抛出异常。请注意,这acknowledgeMode是对channelTransacted - 如果通道进行了交易,代理需要除了 ack 之外的提交通知的补充。这是默认模式。另请参阅batchSize

刻度线
刻度线

adviceChain
(建议链)

应用于侦听器执行的 AOP Advice 数组。这可用于应用额外的横切关注点,例如在代理死亡的情况下自动重试。CachingConnectionFactory请注意,只要代理还活着,AMQP 错误后的简单重新连接就会由 处理。

刻度线
刻度线

afterReceivePostProcessors
(不适用)

MessagePostProcessor在调用侦听器之前调用的实例数组。后处理器可以实现PriorityOrderedOrdered. 该数组使用最后调用的未排序成员进行排序。如果后处理器返回null,则丢弃消息(并在适当的情况下确认)。

刻度线
刻度线

alwaysRequeueWithTxManagerRollback
(不适用)

设置为true在配置事务管理器时始终在回滚时重新排队消息。

刻度线
刻度线

autoDeclare
(自动声明)

当设置为true(默认)时,容器使用 aRabbitAdmin重新声明所有 AMQP 对象(队列、交换、绑定),如果它在启动期间检测到它的至少一个队列丢失,可能是因为它是一个auto-delete或过期队列,但是如果队列因任何原因丢失,则重新声明继续进行。要禁用此行为,请将此属性设置为false。请注意,如果缺少所有队列,则容器将无法启动。

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

autoStartup
(自动启动)

标志指示容器应该在执行时启动ApplicationContext(作为SmartLifecycle回调的一部分,在所有 bean 初始化后发生)。默认为true,但false如果您的代理在启动时可能不可用,您可以将其设置为,并start()在您知道代理已准备好时手动调用。

刻度线
刻度线
刻度线

batchSize
(事务大小) (批量大小)

当与acknowledgeModeset to一起使用时AUTO,容器会在发送 ack 之前尝试处理最多此数量的消息(等待每个消息直到接收超时设置)。这也是提交事务通道的时间。如果prefetchCount小于batchSize,则增加以匹配batchSize

刻度线

batchingStrategy
(不适用)

分批消息时使用的策略。默认SimpleDebatchingStrategy。请参阅Batching@RabbitListener with Batching

刻度线
刻度线

channelTransacted
(渠道交易)

布尔标志,表示应在事务中确认所有消息(手动或自动)。

刻度线
刻度线

concurrency
(不适用)

m-n每个侦听器的并发消费者范围(最小值,最大值)。如果只n提供,n是固定数量的消费者。请参阅侦听器并发

刻度线

concurrentConsumers
(并发)

最初为每个侦听器启动的并发使用者数量。请参阅侦听器并发

刻度线

connectionFactory
(连接工厂)

ConnectionFactory. 使用 XML 命名空间进行配置时,默认引用的 bean 名称是rabbitConnectionFactory.

刻度线
刻度线

consecutiveActiveTrigger
(最小连续活动)

在考虑启动新的消费者时,消费者接收到的连续消息的最小数量,没有发生接收超时。也受“batchSize”的影响。请参阅侦听器并发。默认值:10。

刻度线

consecutiveIdleTrigger
(最小连续空闲)

在考虑停止消费者之前,消费者必须经历的最小接收超时次数。也受“batchSize”的影响。请参阅侦听器并发。默认值:10。

刻度线

consumerBatchEnabled
(批量启用)

如果MessageListener支持它,将其设置为 true 可以批量处理离散消息,最多batchSize; 如果没有新消息到达,将发送部分批次receiveTimeout。如果为 false,则仅对生产者创建的批次支持批处理;请参阅批处理

刻度线

consumerCustomizer
(不适用)

ConsumerCustomizer用于修改容器创建的流消费者的bean。

刻度线

consumerStartTimeout
(不适用)

等待消费者线程启动的时间(以毫秒为单位)。如果此时间过去,则会写入错误日志。可能发生这种情况的一个例子是,如果配置taskExecutor的线程不足以支持容器concurrentConsumers

请参阅线程和异步消费者。默认值:60000(一分钟)。

刻度线

consumerTagStrategy
(消费者标签策略)

设置ConsumerTagStrategy的实现,为每个消费者创建一个(唯一的)标签。

刻度线
刻度线

consumersPerQueue
(每个队列的消费者)

为每个配置的队列创建的消费者数量。请参阅侦听器并发

刻度线

consumeDelay
(不适用)

RabbitMQ Sharding Plugin与 一起使用时concurrentConsumers > 1,存在一种竞争条件,可以防止消费者在分片之间均匀分布。使用此属性在消费者启动之间添加一个小的延迟以避免这种竞争条件。您应该试验值以确定适合您环境的延迟。

刻度线
刻度线

debatchingEnabled
(不适用)

当为 true 时,侦听器容器将分批批处理消息并使用批处理中的每条消息调用侦听器。从版本 2.2.7 开始,如果侦听器是 a或,生产者创建的批次将作为 a分批。否则,来自批次的消息将一次显示一个。默认为真。请参阅Batching@RabbitListener with BatchingList<Message>BatchMessageListenerChannelAwareBatchMessageListener

刻度线
刻度线

declarationRetries
(声明重试)

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

刻度线

defaultRequeueRejected
(requeue-rejected)

确定是否应重新排队因侦听器引发异常而被拒绝的消息。默认值:true.

刻度线
刻度线

errorHandler
(错误处理程序)

ErrorHandler对用于处理在 MessageListener 执行期间可能发生的任何未捕获异常的策略的引用。默认:ConditionalRejectingErrorHandler

刻度线
刻度线

exclusive
(独家的)

确定此容器中的单个使用者是否具有对队列的独占访问权限。当 this 为 时,容器的并发度必须为 1 truerecovery-interval如果另一个消费者具有独占访问权限,则容器会根据or尝试恢复消费者 recovery-back-off。使用命名空间时,此属性<rabbit:listener/>与队列名称一起出现在元素上。默认值:false.

刻度线
刻度线

exclusiveConsumerExceptionLogger
(不适用)

当独占消费者无法访问队列时使用的异常记录器。默认情况下,这是在WARN级别记录的。

刻度线
刻度线

failedDeclarationRetryInterval
(失败声明-重试间隔)

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

刻度线
刻度线

forceCloseChannel
(不适用)

如果消费者在 内没有响应关闭shutdownTimeout,则true通道将关闭,导致任何未确认的消息重新排队。默认true自 2.0 起。您可以将其设置false为恢复到以前的行为。

刻度线
刻度线

globalQos
(全局-qos)

如果为真,则将prefetchCount全局应用于通道而不是通道上的每个消费者。有关basicQos.global更多信息,请参阅。

刻度线
刻度线

(团体)

这仅在使用命名空间时可用。指定后,将Collection<MessageListenerContainer>使用此名称注册一个类型的 bean,并将每个<listener/>元素的容器添加到集合中。例如,这允许通过迭代集合来启动和停止容器组。如果多个<listener-container/>元素具有相同的组值,则集合中的容器形成所有如此指定的容器的集合。

刻度线
刻度线

idleEventInterval
(空闲事件间隔)

请参阅检测空闲异步消费者

刻度线
刻度线

javaLangErrorHandler
(不适用)

AbstractMessageListenerContainer.JavaLangErrorHandler容器线程捕获Error. 默认实现调用System.exit(99);要恢复到以前的行为(什么都不做),请添加一个无操作处理程序。

刻度线
刻度线

maxConcurrentConsumers
(最大并发)

如果需要,按需启动的最大并发消费者数。必须大于或等于“concurrentConsumers”。请参阅侦听器并发

刻度线

messagesPerAck
(不适用)

确认之间接收的消息数。使用它来减少发送到代理的确认数量(以增加重新传递消息的可能性为代价)。通常,您应该仅在大容量侦听器容器上设置此属性。如果设置了此项并且消息被拒绝(抛出异常),则待处理的确认被确认并且失败的消息被拒绝。不允许使用已交易的渠道。如果prefetchCount小于messagesPerAck,则增加以匹配messagesPerAck。默认值:确认每条消息。另请参阅ackTimeout此表。

刻度线

mismatchedQueuesFatal
(不匹配的队列致命)

容器启动时,如果此属性为true(默认值:)false,则容器检查上下文中声明的所有队列是否与代理上已存在的队列兼容。如果存在不匹配的属性(例如auto-delete)或参数(例如x-message-ttl),则容器(和应用程序上下文)无法以致命异常启动。

如果在恢复过程中检测到问题(例如,在失去连接之后),容器将停止。

在应用程序上下文中必须有一个RabbitAdmin(或使用该rabbitAdmin属性在容器上专门配置的一个)。否则,此属性必须为false

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

missingQueuesFatal
(缺少队列致命)

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

这在以前的版本中是不可配置的。

当设置为 时false,在进行了三次重试后,容器进入恢复模式,与其他问题一样,例如代理关闭。容器尝试根据recoveryInterval属性进行恢复。在每次恢复尝试期间,每个消费者再次尝试四次以每隔 5 秒被动声明队列。这个过程无限期地继续。

您还可以使用属性 bean 为所有容器全局设置属性,如下所示:

<util:properties
        id="spring.amqp.global.properties">
    <prop key="mlc.missing.queues.fatal">
        false
    </prop>
</util:properties>

此全局属性不适用于任何具有显式missingQueuesFatal属性集的容器。

可以通过设置以下属性来覆盖默认重试属性(以五秒为间隔重试三次)。

@RabbitListener为标记的 bean 中的 a 启动容器时,将禁用缺少队列检测@Lazy。这是为了避免潜在的死锁,这种死锁可能会延迟此类容器的启动长达 60 秒。使用惰性侦听器 bean 的应用程序应在获取对惰性 bean 的引用之前检查队列。
刻度线
刻度线

monitorInterval
(监控间隔)

使用 DMLC,任务计划在此时间间隔运行,以监视使用者的状态并恢复任何失败的。

刻度线

noLocal
(不适用)

设置为true禁用从服务器传递到在同一通道连接上发布的消费者消息。

刻度线
刻度线

phase
(阶段)

autoStartup什么时候true,这个容器应该启动和停止的生命周期阶段。该值越低,该容器启动得越早,停止得越晚。默认值为Integer.MAX_VALUE,这意味着容器尽可能晚地启动并尽快停止。

刻度线
刻度线

possibleAuthenticationFailureFatal
(可能的身份验证失败致命)

当设置为true(SMLC 的默认值)时,如果PossibleAuthenticationFailureException在连接期间抛出 a,则认为它是致命的。这会导致应用程序上下文在启动期间无法初始化(如果容器配置了自动启动)。

2.0 版开始

DirectMessageListenerContainer

当设置为false(默认)时,每个消费者将尝试根据monitorInterval.

SimpleMessageListenerContainer

当设置为 时false,在进行 3 次重试后,容器将进入恢复模式,与其他问题一样,例如代理关闭。容器将根据recoveryInterval属性尝试恢复。在每次恢复尝试期间,每个消费者将再次尝试启动 4 次。这个过程将无限期地继续下去。

您还可以使用属性 bean 为所有容器全局设置属性,如下所示:

<util:properties
    id="spring.amqp.global.properties">
  <prop
    key="mlc.possible.authentication.failure.fatal">
     false
  </prop>
</util:properties>

此全局属性不会应用于任何具有显式missingQueuesFatal属性集的容器。

默认重试属性(以 5 秒为间隔重试 3 次)可以使用此属性之后的属性进行覆盖。

刻度线
刻度线

prefetchCount
(预取)

每个消费者可以未确认的未确认消息的数量。此值越高,消息传递的速度越快,但非顺序处理的风险越高。acknowledgeMode如果 是则忽略NONE。如有必要,该值会增加以匹配batchSizeor messagePerAck。自 2.0 起默认为 250。您可以将其设置为 1 以恢复到以前的行为。

在某些情况下,预取值应该很低——例如,对于大消息,尤其是在处理速度很慢的情况下(消息可能会在客户端进程中增加大量内存),并且如果需要严格的消息排序(在这种情况下,预取值应设置回 1)。此外,对于低容量消息和多个消费者(包括单个侦听器容器实例中的并发),您可能希望减少预取以在消费者之间获得更均匀的消息分布。

另请参阅globalQos

刻度线
刻度线

rabbitAdmin
(行政)

当侦听器容器至少侦听一个自动删除队列并且在启动过程中发现它丢失时,容器使用 aRabbitAdmin来声明队列以及任何相关的绑定和交换。如果此类元素配置为使用条件声明(请参阅条件声明),则容器必须使用配置为声明这些元素的管理员。在此处指定该管理员。仅在使用带有条件声明的自动删除队列时才需要。如果您不希望在容器启动之前声明自动删除队列,auto-startupfalse在管理员上设置。默认为RabbitAdmin声明所有非条件元素的 a。

刻度线
刻度线

receiveTimeout
(接收超时)

等待每条消息的最长时间。如果acknowledgeMode=NONE,这几乎没有影响 - 容器旋转并要求另一条消息。它对带有 的事务性影响最大ChannelbatchSize > 1因为它可能导致已经消费的消息在超时到期之前不会被确认。当consumerBatchEnabled为 true 时,如果在批次完成之前发生此超时,则将交付部分批次。

刻度线

recoveryBackOff
(恢复回退)

如果由于非致命原因无法启动消费者,则指定BackOff尝试启动消费者的时间间隔。默认为FixedBackOff每五秒无限制重试一次。与 互斥recoveryInterval

刻度线
刻度线

recoveryInterval
(恢复间隔)

如果由于非致命原因无法启动消费者,则确定尝试启动消费者之间的时间(以毫秒为单位)。默认值:5000。与 互斥recoveryBackOff

刻度线
刻度线

retryDeclarationInterval
(缺少队列重试间隔)

如果在消费者初始化期间配置的队列的子集可用,则消费者开始从这些队列中消费。消费者尝试使用此时间间隔被动声明丢失的队列。当此间隔过去时,将再次使用“declarationRetries”和“failedDeclarationRetryInterval”。如果仍然缺少队列,消费者将再次等待此时间间隔,然后再重试。这个过程无限期地继续,直到所有队列都可用。默认值:60000(一分钟)。

刻度线

shutdownTimeout
(不适用)

当一个容器关闭时(例如,如果它的封闭ApplicationContext是关闭的),它会等待处理中的消息达到此限制。默认为五秒。

刻度线
刻度线

startConsumerMinInterval
(最小开始间隔)

在每个新消费者按需启动之前必须经过的时间(以毫秒为单位)。请参阅侦听器并发。默认值:10000(10 秒)。

刻度线

statefulRetryFatal
WithNullMessageId (N/A)

使用有状态重试建议时,如果收到缺少messageId属性的消息,默认情况下它对消费者来说是致命的(它被停止)。将此设置false为丢弃(或路由到死信队列)此类消息。

刻度线
刻度线

stopConsumerMinInterval
(最小停止间隔)

自检测到空闲消费者时停止最后一个消费者以来,消费者停止之前必须经过的时间(以毫秒为单位)。请参阅侦听器并发。默认值:60000(一分钟)。

刻度线

streamConverter
(不适用)

AStreamMessageConverter将原生 Stream 消息转换为 Spring AMQP 消息。

刻度线

taskExecutor
(任务执行者)

对用于执行侦听器调用程序的 Spring TaskExecutor(或标准 JDK 1.5+ )的引用。Executor默认是 a SimpleAsyncTaskExecutor,使用内部管理的线程。

刻度线
刻度线

taskScheduler
(任务调度器)

使用 DMLC,调度程序用于在“monitorInterval”运行监控任务。

刻度线

transactionManager
(交易经理)

外部事务管理器用于监听器的操作。还补充了channelTransacted - 如果进行Channel交易,则其交易与外部交易同步。

刻度线
刻度线

4.1.18。侦听器并发

SimpleMessageListenerContainer

默认情况下,侦听器容器启动一个从队列接收消息的消费者。

在检查上一节中的表格时,您可以看到许多控制并发性的属性和属性。最简单的是concurrentConsumers,它创建了(固定)数量的并发处理消息的消费者。

在 1.3.0 版本之前,这是唯一可用的设置,必须停止并重新启动容器才能更改设置。

从 1.3.0 版本开始,您现在可以动态调整concurrentConsumers属性。如果在容器运行时更​​改它,则根据需要添加或删除消费者以适应新设置。

此外,maxConcurrentConsumers还添加了一个名为的新属性,容器根据工作负载动态调整并发。这与四个附加属性结合使用:consecutiveActiveTriggerstartConsumerMinIntervalconsecutiveIdleTriggerstopConsumerMinInterval。在默认设置下,增加消费者的算法如下:

如果maxConcurrentConsumers尚未达到并且现有消费者连续十个周期处于活动状态并且自最后一个消费者启动以来已经过去了至少 10 秒,则启动一个新消费者。batchSize如果消费者在*receiveTimeout毫秒内收到至少一条消息,则认为该消费者处于活动状态。

在默认设置下,减少消费者的算法工作如下:

如果有多个concurrentConsumers正在运行并且消费者检测到十个连续超时(空闲)并且最后一个消费者至少在 60 秒前停止,则消费者将被停止。超时取决于receiveTimeoutbatchSize属性。batchSize如果消费者在*receiveTimeout毫秒内没有收到任何消息,则认为它是空闲的。因此,使用默认超时(1 秒)和 abatchSize为 4,在 40 秒的空闲时间(四个超时对应于一个空闲检测)后考虑停止消费者。

实际上,只有在整个容器空闲一段时间后才能停止消费者。这是因为经纪人在所有活跃的消费者之间共享其工作。

无论配置的队列数量如何,每个消费者都使用单个通道。

从 2.0 版开始,可以使用属性设置 和concurrentConsumers属性,例如.maxConcurrentConsumersconcurrency2-4

使用DirectMessageListenerContainer

使用此容器,并发性基于配置的队列和consumersPerQueue. 每个队列的每个消费者使用一个单独的通道,并发由兔子客户端库控制。默认情况下,在撰写本文时,它使用DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2线程池。

您可以配置 ataskExecutor以提供所需的最大并发性。

4.1.19。独家消费者

从版本 1.3 开始,您可以使用单个独占消费者配置侦听器容器。这可以防止其他容器从队列中消费,直到当前消费者被取消。这种容器的并发性必须是1.

使用独占消费者时,其他容器根据recoveryInterval属性尝试从队列中消费,WARN如果尝试失败,则记录消息。

4.1.20。侦听器容器队列

1.3 版引入了许多改进,用于在侦听器容器中处理多个队列。

容器必须配置为至少侦听一个队列。以前也是如此,但现在可以在运行时添加和删除队列。当处理任何预取的消息时,容器会回收(取消和重新创建)消费者。请参阅Javadoc了解addQueuesaddQueueNamesremoveQueues方法removeQueueNames。移除队列时,必须至少保留一个队列。

如果有任何队列可用,则消费者现在开始。以前,如果任何队列不可用,容器将停止。现在,只有在没有可用队列的情况下才会出现这种情况。如果并非所有队列都可用,则容器每 60 秒尝试被动声明(并从中消耗)丢失的队列。

此外,如果消费者从代理收到取消消息(例如,如果队列被删除),消费者会尝试恢复,并且恢复的消费者会继续处理来自任何其他已配置队列的消息。以前,取消一个队列会取消整个消费者,最终,容器将由于缺少队列而停止。

如果您希望永久删除队列,则应在删除队列之前或之后更新容器,以避免将来尝试使用它。

4.1.21。弹性:从错误和代理故障中恢复

Spring AMQP 提供的一些关键(也是最流行的)高级特性与协议错误或代理失败时的恢复和自动重新连接有关。我们已经在本指南中看到了所有相关组件,但将它们全部放在一起并单独调用功能和恢复方案应该会有所帮助。

主要的重新连接功能由CachingConnectionFactory自身启用。RabbitAdmin使用自动声明功能通常也是有益的。此外,如果您关心保证交付,您可能channelTransactedRabbitTemplate需要在.SimpleMessageListenerContainerAcknowledgeMode.AUTOSimpleMessageListenerContainer

交换、队列和绑定的自动声明

RabbitAdmin组件可以在启动时声明交换、队列和绑定。它通过ConnectionListener. 因此,如果代理在启动时不存在,那也没关系。第一次使用 a 时Connection(例如,通过发送消息),侦听器触发并应用管理功能。在侦听器中进行自动声明的另一个好处是,如果连接因任何原因(例如,代理死亡、网络故障等)而断开,它们会在重新建立连接时再次应用。

以这种方式声明的队列必须具有固定的名称——要么显式声明,要么由框架为AnonymousQueue实例生成。匿名队列是非持久的、排他的和自动删除的。
仅当CachingConnectionFactory缓存模式为CHANNEL(默认)时才执行自动声明。存在此限制是因为排他和自动删除队列绑定到连接。

从版本 2.2.2 开始,RabbitAdmin将检测类型的 beanDeclarableCustomizer并在实际处理声明之前应用函数。这很有用,例如,在框架内具有一流支持之前设置新参数(属性)。

@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 定义的直接访问的项目中也很有用。

同步操作失败和重试选项

如果在使用RabbitTemplate(例如)时以同步顺序失去与代理的连接,Spring AMQP 会抛出一个AmqpException(通常,但不总是,AmqpIOException)。我们不会试图隐藏存在问题的事实,因此您必须能够捕获并响应异常。如果您怀疑连接丢失(这不是您的错),最简单的方法是再次尝试该操作。您可以手动执行此操作,或者您可以查看使用 Spring Retry 来处理重试(命令式或声明式)。

Spring Retry 提供了几个 AOP 拦截器和很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法等)。Spring AMQP 还提供了一些方便的工厂 bean,用于以方便的形式为 AMQP 用例创建 Spring Retry 拦截器,并具有可用于实现自定义恢复逻辑的强类型回调接口。请参阅和的 Javadoc 和StatefulRetryOperationsInterceptor属性StatelessRetryOperationsInterceptor了解更多详情。如果没有事务或事务在重试回调中启动,则无状态重试是合适的。请注意,无状态重试比有状态重试更易于配置和分析,但如果有一个正在进行的事务必须回滚或肯定要回滚,则通常不合适。在事务中间断开的连接应该与回滚具有相同的效果。因此,对于在堆栈更高层开始事务的重新连接,有状态重试通常是最佳选择。有状态重试需要一种机制来唯一标识一条消息。最简单的方法是让发送者在MessageId消息属性中放置一个唯一值。提供的消息转换器提供了一个选项来执行此操作:您可以设置createMessageIdstrue. 否则,您可以将MessageKeyGenerator实现注入拦截器。密钥生成器必须为每条消息返回一个唯一的密钥。在 2.0 之前的版本MissingMessageIdAdvice中,提供了 a。它使没有messageId属性的消息只重试一次(忽略重试设置)。不再提供此建议,因为与spring-retry1.2 版一起,它的功能内置于拦截器和消息侦听器容器中。

为了向后兼容,默认情况下(一次重试后),消息 ID 为空的消息对消费者来说是致命的(消费者已停止)。要复制 提供的功能MissingMessageIdAdvice,您可以在侦听器容器上将statefulRetryFatalWithNullMessageId属性设置为。false使用该设置,消费者继续运行并且消息被拒绝(重试后)。它被丢弃或路由到死信队列(如果已配置)。

从 1.3 版开始,提供了一个构建器 API 来帮助使用 Java(在@Configuration类中)组装这些拦截器。以下示例显示了如何执行此操作:

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}

只能以这种方式配置重试功能的一个子集。更高级的功能需要将 a 配置RetryTemplate为 Spring bean。有关可用策略及其配置的完整信息,请参阅Spring Retry Javadoc 。

使用批处理侦听器重试

不建议使用批处理侦听器配置重试,除非批处理是由生产者在单个记录中创建的。有关消费者和生产者创建的批处理的信息,请参阅批处理消息。对于消费者创建的批处理,框架不知道批处理中的哪个消息导致了失败,因此在重试用尽后恢复是不可能的。对于生产者创建的批次,由于实际上只有一条消息失败,因此可以恢复整条消息。应用程序可能希望通过设置抛出异常的索引属性来通知自定义恢复器在批处理中发生故障的位置。

批处理侦听器的重试恢复器必须实现MessageBatchRecoverer

消息侦听器和异步案例

如果MessageListener由于业务异常而失败,则异常由消息侦听器容器处理,然后返回侦听另一条消息。如果失败是由于连接断开(不是业务异常)导致的,则必须取消并重新启动正在为侦听器收集消息的消费者。无缝处理这个SimpleMessageListenerContainer,它留下一个日志说监听器正在重新启动。事实上,它无休止地循环,试图重启消费者。只有当消费者确实表现得很糟糕时,它才会放弃。一个副作用是,如果代理在容器启动时关闭,它会一直尝试直到可以建立连接。

与协议错误和断开连接相比,业务异常处理可能需要更多思考和一些自定义配置,尤其是在使用事务或容器确认时。在 2.8.x 之前,RabbitMQ 没有死信行为的定义。因此,默认情况下,由于业务异常而被拒绝或回滚的消息可以无休止地重新传递。为了限制客户重新交付的次数,一种选择是StatefulRetryOperationsInterceptor在听众的建议链中。拦截器可以有一个实现自定义死信操作的恢复回调 - 任何适合您的特定环境的东西。

另一种选择是将容器的defaultRequeueRejected属性设置为false. 这会导致所有失败的消息都被丢弃。当使用 RabbitMQ 2.8.x 或更高版本时,这也有助于将消息传递到死信交换。

或者,您可以抛出一个AmqpRejectAndDontRequeueException. 这样做可以防止消息重新排队,无论defaultRequeueRejected属性的设置如何。

从 2.1 版本开始,ImmediateRequeueAmqpException引入了 an 来执行完全相反的逻辑:消息将被重新排队,而不管defaultRequeueRejected属性的设置如何。

通常,使用这两种技术的组合。您可以StatefulRetryOperationsInterceptor在建议链中将 aMessageRecoverer与抛出 . 的 a 一起使用AmqpRejectAndDontRequeueException。当MessageRecover所有重试都用尽时调用。RejectAndDontRequeueRecoverer正是这样做的。默认MessageRecoverer使用错误消息并发出WARN消息。

从 1.3 版开始,RepublishMessageRecoverer提供了一个新功能,允许在重试用尽后发布失败的消息。

当恢复者消费最终异常时,消息被确认并且不会发送到死信交换(如果有的话)。

RepublishMessageRecoverer在消费者端使用时,接收到的消息deliveryModereceivedDeliveryModemessage 属性中。在这种情况下deliveryModenull。这意味着NON_PERSISTENT经纪人的交付模式。从 2.0 版开始,您可以将设置RepublishMessageRecovererdeliveryMode要重新发布的消息(如果它是null. 默认情况下,它使用MessageProperties默认值 - MessageDeliveryMode.PERSISTENT

以下示例显示如何将 a 设置RepublishMessageRecoverer为恢复者:

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

RepublishMessageRecoverer消息头中发布带有附加信息的消息,例如异常消息、堆栈跟踪、原始交换和路由密钥。可以通过创建子类和覆盖来添加额外的头文件additionalHeaders()。(deliveryMode或任何其他属性)也可以在 中更改additionalHeaders(),如以下示例所示:

RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(amqpTemplate, "error") {

    protected Map<? extends String, ? extends Object> additionalHeaders(Message message, Throwable cause) {
        message.getMessageProperties()
            .setDeliveryMode(message.getMessageProperties().getReceivedDeliveryMode());
        return null;
    }

};

从 2.0.5 版本开始,如果堆栈跟踪太大,可能会被截断;这是因为所有标题都必须适合单个帧。默认情况下,如果堆栈跟踪将导致少于 20,000 字节(“headroom”)可用于其他标头,它将被截断。这可以通过设置恢复器的frameMaxHeadroom属性来调整,如果您需要更多或更少的空间用于其他标题。从版本 2.1.13、2.2.3 开始,异常消息包含在此计算中,堆栈跟踪量将使用以下算法最大化:

  • 如果仅堆栈跟踪超出限制,则异常消息头将被截断为 97 字节以上…​,堆栈跟踪也将被截断。

  • 如果堆栈跟踪很小,则消息将被截断(加号…​)以适合可用字节(但堆栈跟踪本身中的消息被截断为 97 个字节加号…​)。

每当发生任何类型的截断时,都会记录原始异常以保留完整信息。

从版本 2.3.3 开始,RepublishMessageRecovererWithConfirms提供了一个新的子类;这支持两种风格的发布者确认,并在返回之前等待确认(如果未确认或返回消息,则抛出异常)。

如果确认类型为CORRELATED,子类也会检测是否返回消息并抛出AmqpMessageReturnedException; 如果该出版物被否定承认,它将抛出一个AmqpNackReceivedException.

如果确认类型是SIMPLE,子类将调用waitForConfirmsOrDie通道上的方法。

有关确认和退货的更多信息,请参阅出版商确认和退货。

从 2.1 版本开始,ImmediateRequeueMessageRecoverer添加了 an 以 throw an ImmediateRequeueAmqpException,它通知侦听器容器将当前失败的消息重新排队。

Spring Retry 的异常分类

Spring Retry 在确定哪些异常可以调用重试方面具有很大的灵活性。默认配置重试所有异常。鉴于用户异常被包装在 a 中ListenerExecutionFailedException,我们需要确保分类检查异常原因。默认分类器仅查看顶级异常。

从 Spring Retry 1.0.3 开始,BinaryExceptionClassifier有一个名为traverseCauses(default: false) 的属性。时true,它会遍历异常原因,直到找到匹配项或没有原因。

要使用此分类器进行重试,您可以使用SimpleRetryPolicy带最大尝试次数、实例数和布尔值 ( ) 的构造函数 createdMap并将ExceptiontraverseCauses策略注入RetryTemplate.

4.1.22。多代理(或集群)支持

2.3 版在单个应用程序与多个代理或代理集群之间进行通信时增加了更多便利。在消费者方面,主要好处是基础设施可以自动将自动声明的队列与适当的代理相关联。

最好用一个例子来说明这一点:

@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
public class Application {

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

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

    @Bean
    CachingConnectionFactory cf2() {
        return new CachingConnectionFactory("otherHost");
    }

    @Bean
    CachingConnectionFactory cf3() {
        return new CachingConnectionFactory("thirdHost");
    }

    @Bean
    SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1,
            CachingConnectionFactory cf2, CachingConnectionFactory cf3) {

        SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
        rcf.setDefaultTargetConnectionFactory(cf1);
        rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3));
        return rcf;
    }

    @Bean("factory1-admin")
    RabbitAdmin admin1(CachingConnectionFactory cf1) {
        return new RabbitAdmin(cf1);
    }

    @Bean("factory2-admin")
    RabbitAdmin admin2(CachingConnectionFactory cf2) {
        return new RabbitAdmin(cf2);
    }

    @Bean("factory3-admin")
    RabbitAdmin admin3(CachingConnectionFactory cf3) {
        return new RabbitAdmin(cf3);
    }

    @Bean
    public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
        return new RabbitListenerEndpointRegistry();
    }

    @Bean
    public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) {
        MultiRabbitListenerAnnotationBeanPostProcessor postProcessor
                = new MultiRabbitListenerAnnotationBeanPostProcessor();
        postProcessor.setEndpointRegistry(registry);
        postProcessor.setContainerFactoryBeanName("defaultContainerFactory");
        return postProcessor;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf1);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf2);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cf3);
        return factory;
    }

    @Bean
    RabbitTemplate template(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可以定义使用哪个容器工厂;在这种情况下,他们还使用queuesToDeclarewhich 导致在代理上声明队列(如果它不存在)。通过使用约定命名RabbitAdminbean <container-factory-name>-admin,基础设施能够确定哪个管理员应该声明队列。这也将与bindings = @QueueBinding(…​)声明交换和绑定一起使用。它不适用于queues,因为它期望队列已经存在。

在生产者方面,ConnectionFactoryContextWrapper提供了一个方便的类,以使使用RoutingConnectionFactory(请参阅路由连接工厂)更简单。

正如您在上面看到的,添加了一个带有路由键的SimpleRoutingConnectionFactorybean和. 还有一个使用那个工厂。这是一个使用该模板与包装器一起路由到其中一个代理集群的示例。onetwothreeRabbitTemplate

@Bean
public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) {
    return args -> {
        wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1"));
        wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2"));
        wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3"));
    };
}

4.1.23。调试

Spring AMQP 提供了广泛的日志记录,尤其是在DEBUG级别。

如果您希望监控应用程序和代理之间的 AMQP 协议,您可以使用诸如 WireShark 之类的工具,它有一个插件来解码协议。或者,RabbitMQ Java 客户端带有一个非常有用的类,称为Tracer. 当作为 a 运行时main,默认情况下,它侦听端口 5673 并连接到 localhost 上的端口 5672。您可以运行它并更改连接工厂配置以连接到 localhost 上的端口 5673。它在控制台上显示解码的协议。有关详细信息,请参阅TracerJavadoc。

4.2. 使用 RabbitMQ 流插件

  • RabbitStreamTemplate

  • StreamListenerContainer

4.2.1。发送消息

RabbitStreamTemplate提供了RabbitTemplate(AMQP) 功能的一个子集。

示例 1. RabbitStreamOperations
public interface RabbitStreamOperations extends AutoCloseable {

	ListenableFuture<Boolean> send(Message message);

	ListenableFuture<Boolean> convertAndSend(Object message);

	ListenableFuture<Boolean> convertAndSend(Object message, @Nullable MessagePostProcessor mpp);

	ListenableFuture<Boolean> send(com.rabbitmq.stream.Message message);

	MessageBuilder messageBuilder();

	MessageConverter messageConverter();

	StreamMessageConverter streamMessageConverter();

	@Override
	void close() throws AmqpException;

}

RabbitStreamTemplate实现具有以下构造函数和属性:

示例 2. RabbitStreamTemplate
public RabbitStreamTemplate(Environment environment, String streamName) {
}

public void setMessageConverter(MessageConverter messageConverter) {
}

public void setStreamConverter(StreamMessageConverter streamConverter) {
}

public synchronized void setProducerCustomizer(ProducerCustomizer producerCustomizer) {
}

MessageConverter用于将对象转换为 Spring AMQP 的方法convertAndSendMessage

StreamMessageConverter用于从 Spring AMQP 转换为Message原生流Message

也可以Message直接发送原生流;使用提供对' 的消息构建器的messageBuilder()访问权限的方法。Producer

提供ProducerCustomizer了一种机制来在构建之前自定义生产者。

请参阅Java 客户端文档以了解自定义EnvironmentProducer.

4.2.2. 接收消息

异步消息接收由StreamListenerContainer(以及StreamRabbitListenerContainerFactory使用时@RabbitListener)提供。

侦听器容器需要一个Environment以及单个流名称。

您可以Message使用 classic接收 Spring AMQP MessageListener,也可以Message使用新接口接收本机 stream :

public interface StreamMessageListener extends MessageListener {

	void onStreamMessage(Message message, Context context);

}

有关支持的属性的信息,请参阅消息侦听器容器配置

与模板类似,容器也有一个ConsumerCustomizer属性。

请参阅Java 客户端文档以了解自定义EnvironmentConsumer.

使用时@RabbitListener,配置一个StreamRabbitListenerContainerFactory;此时,大多数@RabbitListener属性(concurrency等)被忽略。仅支持idqueues和。此外,只能包含一个流名称。autoStartupcontainerFactoryqueues

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

版本 2.4.5 将adviceChain属性添加到StreamListenerContainer(及其工厂)。还提供了一个新的工厂 bean 来创建一个无状态重试拦截器,StreamMessageRecoverer在使用原始流消息时可以选择使用。

@Bean
public StreamRetryOperationsInterceptorFactoryBean sfb(RetryTemplate retryTemplate) {
    StreamRetryOperationsInterceptorFactoryBean rfb =
            new StreamRetryOperationsInterceptorFactoryBean();
    rfb.setRetryOperations(retryTemplate);
    rfb.setStreamMessageRecoverer((msg, context, throwable) -> {
        ...
    });
    return rfb;
}
此容器不支持有状态重试。

4.3. 记录子系统 AMQP 附加器

该框架为一些流行的日志子系统提供了日志附加器:

  • logback(从 Spring AMQP 1.4 版开始)

  • log4j2(自 Spring AMQP 1.6 版起)

附加程序是使用日志子系统的正常机制配置的,可用属性在以下部分中指定。

4.3.1。共同属性

以下属性可用于所有附加程序:

表 4. 常见的 Appender 属性
财产 默认 描述
交换名称
日志

要将日志事件发布到的交易所的名称。

交换类型
话题

向其发布日志事件的交换类型——仅当附加程序声明交换时才需要。见declareExchange

路由键模式
%c.%p

用于生成路由密钥的日志记录子系统模式格式。

应用程序 ID

应用程序 ID — 如果模式包含 .,则添加到路由键中%X{applicationId}

发件人池大小
2

用于发布日志事件的线程数。

maxSenderRetries
30

如果代理不可用或出现其他错误,重试发送消息的次数。重试延迟如下:N ^ log(N),其中N是重试次数。

地址

以逗号分隔的代理地址列表,格式如下:host:port[,host:port]*- 覆盖hostport.

主持人
本地主机

要连接的 RabbitMQ 主机。

港口
5672

要连接的 RabbitMQ 端口。

虚拟主机
/

要连接的 RabbitMQ 虚拟主机。

用户名
来宾

连接时使用的 RabbitMQ 用户。

密码
来宾

此用户的 RabbitMQ 密码。

使用SSL
错误的

是否为 RabbitMQ 连接使用 SSL。查看RabbitConnectionFactoryBean和配置 SSL

验证主机名
真的

为 TLS 连接启用服务器主机名验证。查看RabbitConnectionFactoryBean和配置 SSL

ssl算法
无效的

要使用的 SSL 算法。

sslProperties位置
无效的

SSL 属性文件的位置。

密钥库
无效的

密钥库的位置。

密钥存储密码
无效的

密钥库的密码。

密钥存储类型
JKS

密钥库类型。

信任商店
无效的

信任库的位置。

信任商店密码
无效的

信任库的密码。

信任存储类型
JKS

信任库类型。

saslConfig
null(RabbitMQ 客户端默认适用)

-有关有效值,saslConfig请参阅 javadoc 。RabbitUtils.stringToSaslConfig

内容类型
文本/纯文本

content-type日志消息的属性。

内容编码

content-encoding日志消息的属性。

申报交易所
错误的

此附加程序启动时是否声明配置的交换。另见durableautoDelete

耐用的
真的

declareExchangetrue时,持久标志设置为此值。

自动删除
错误的

declareExchangetrue时,自动删除标志设置为此值。

字符集
无效的

转换Stringbyte[]. 默认值:null(使用系统默认字符集)。如果当前平台不支持该字符集,我们就回退到使用系统字符集。

交付方式
执着的

PERSISTENTNON_PERSISTENT确定 RabbitMQ 是否应该持久化消息。

生成Id
错误的

用于确定messageId属性是否设置为唯一值。

客户端连接属性
无效的

key:valueRabbitMQ 连接的自定义客户端属性对的逗号分隔列表。

addMdcAsHeaders
真的

在引入此属性之前,始终将 MDC 属性添加到 RabbitMQ 消息头中。它可能会导致大型 MDC 出现问题,因为 RabbitMQ 对所有标头的缓冲区大小有限,而且这个缓冲区非常小。引入此属性是为了避免在大型 MDC 的情况下出现问题。默认情况下,此值设置true为向后兼容。false关闭序列化 MDC 到标头。请注意,JsonLayout默认情况下会将 MDC 添加到消息中。

4.3.2. Log4j 2 附加器

以下示例显示了如何配置 Log4j 2 附加程序:

<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),您可以将async属性设置为true。但是,您还需要将 Log4j 2 配置为DefaultLogEventFactory使用ReusableLogEventFactory. 一种方法是设置系统属性-Dlog4j2.enable.threadlocals=false。如果您将异步发布与 一起使用ReusableLogEventFactory,则事件很可能由于串扰而被破坏。

4.3.3. Logback 附加程序

以下示例显示了如何配置 logback appender:

<appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
    <layout>
        <pattern><![CDATA[ %d %p %t [%c] - <%m>%n ]]></pattern>
    </layout>
    <addresses>foo:5672,bar:5672</addresses>
    <abbreviation>36</abbreviation>
    <includeCallerData>false</includeCallerData>
    <applicationId>myApplication</applicationId>
    <routingKeyPattern>%property{applicationId}.%c.%p</routingKeyPattern>
    <generateId>true</generateId>
    <charset>UTF-8</charset>
    <durable>false</durable>
    <deliveryMode>NON_PERSISTENT</deliveryMode>
    <declareExchange>true</declareExchange>
    <addMdcAsHeaders>false</addMdcAsHeaders>
</appender>

从 1.7.1 版本开始,LogbackAmqpAppender提供了一个默认includeCallerData选项false。提取调用者数据可能相当昂贵,因为日志事件必须创建一个 throwable 并检查它以确定调用位置。因此,默认情况下,在将事件添加到事件队列时,不会提取与事件关联的调用方数据。includeCallerData您可以通过将属性设置为 来配置附加程序以包含调用者数据true

从 2.0.0 版本开始,LogbackAmqpAppender支持带有选项的Logback 编码器encoderencoderlayout选项是互斥的。

4.3.4. 自定义消息

默认情况下,AMQP 附加程序填充以下消息属性:

  • deliveryMode

  • 内容类型

  • contentEncoding, 如果配置

  • messageId, 如果generateId已配置

  • timestamp日志事件的

  • appId, 如果配置了 applicationId

此外,它们使用以下值填充标题:

  • categoryName日志事件的

  • 日志事件的级别

  • thread:发生日志事件的线程的名称

  • 日志事件调用的堆栈跟踪位置

  • 所有 MDC 属性的副本(除非addMdcAsHeaders设置为false

每个附加程序都可以子类化,让您在发布之前修改消息。以下示例显示了如何自定义日志消息:

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    public Message postProcessMessageBeforeSend(Message message, Event event) {
        message.getMessageProperties().setHeader("foo", "bar");
        return message;
    }

}

从 2.2.4 开始,log4j2AmqpAppender可以使用扩展@PluginBuilderFactory,也可以扩展AmqpAppender.Builder

@Plugin(name = "MyEnhancedAppender", category = "Core", elementType = "appender", printObject = true)
public class MyEnhancedAppender extends AmqpAppender {

	public MyEnhancedAppender(String name, Filter filter, Layout<? extends Serializable> layout,
			boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue, String foo, String bar) {
		super(name, filter, layout, ignoreExceptions, manager, eventQueue);

	@Override
	public Message postProcessMessageBeforeSend(Message message, Event event) {
			message.getMessageProperties().setHeader("foo", "bar");
		return message;
	}

	@PluginBuilderFactory
	public static Builder newBuilder() {
		return new Builder();
	}

	protected static class Builder extends AmqpAppender.Builder {

		@Override
		protected AmqpAppender buildInstance(String name, Filter filter, Layout<? extends Serializable> layout,
				boolean ignoreExceptions, AmqpManager manager, BlockingQueue<Event> eventQueue) {
			return new MyEnhancedAppender(name, filter, layout, ignoreExceptions, manager, eventQueue);
		}
	}

}

4.3.5。自定义客户端属性

您可以通过添加字符串属性或更复杂的属性来添加自定义客户端属性。

简单字符串属性

每个 appender 都支持将客户端属性添加到 RabbitMQ 连接。

以下示例显示了如何为 logback 添加自定义客户端属性:

<appender name="AMQP" ...>
    ...
    <clientConnectionProperties>thing1:thing2,cat:hat</clientConnectionProperties>
    ...
</appender>
示例 3.log4j2
<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
        ...
        clientConnectionProperties="thing1:thing2,cat:hat"
        ...
    </RabbitMQ>
</Appenders>

这些属性是以逗号分隔的key:value对列表。键和值不能包含逗号或冒号。

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

Logback 的高级技术

您可以对 Logback 附加程序进行子类化。这样做可以让您在建立连接之前修改客户端连接属性。以下示例显示了如何执行此操作:

public class MyEnhancedAppender extends AmqpAppender {

    private String thing1;

    @Override
    protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
        clientProperties.put("thing1", this.thing1);
    }

    public void setThing1(String thing1) {
        this.thing1 = thing1;
    }

}

然后你可以添加<thing1>thing2</thing1>到 logback.xml。

对于前面示例中所示的字符串属性,可以使用前面的技术。子类允许添加更丰富的属性(例如添加一个Map或数字属性)。

4.3.6. 提供自定义队列实现

使用AmqpAppendersaBlockingQueue将日志事件异步发布到 RabbitMQ。默认情况下,LinkedBlockingQueue使用 a。但是,您可以提供任何类型的自定义BlockingQueue实现。

以下示例显示了如何为 Logback 执行此操作:

public class MyEnhancedAppender extends AmqpAppender {

    @Override
    protected BlockingQueue<Event> createEventQueue() {
        return new ArrayBlockingQueue();
    }

}

Log4j 2 appender 支持使用 a BlockingQueueFactory,如以下示例所示:

<Appenders>
    ...
    <RabbitMQ name="rabbitmq"
              bufferSize="10" ... >
        <ArrayBlockingQueue/>
    </RabbitMQ>
</Appenders>

4.4. 示例应用程序

Spring AMQP Samples项目包括两个示例应用程序。第一个是一个简单的“Hello World”示例,演示同步和异步消息接收。它为了解基本组件提供了一个极好的起点。第二个示例基于股票交易用例,以演示在现实世界应用程序中常见的交互类型。在本章中,我们提供了每个示例的快速演练,以便您可以专注于最重要的组件。这些示例都是基于 Maven 的,因此您应该能够将它们直接导入任何支持 Maven 的 IDE(例如SpringSource Tool Suite)。

4.4.1。“Hello World”示例

“Hello World”示例演示了同步和异步消息接收。您可以将spring-rabbit-helloworld示例导入 IDE,然后按照下面的讨论进行操作。

同步示例

src/main/java目录中,导航到org.springframework.amqp.helloworld包。打开HelloWorldConfiguration类并注意它包含@Configuration类级别的注释,并注意一些@Bean方法级别的注释。这是 Spring 基于 Java 的配置的示例。您可以在此处阅读更多相关信息。

以下清单显示了如何创建连接工厂:

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

配置还包含 的实例RabbitAdmin,默认情况下,它会查找任何类型为交换、队列或绑定的 bean,然后在代理上声明它们。实际上,其中helloWorldQueue生成的 beanHelloWorldConfiguration是一个示例,因为它是Queue.

以下清单显示了helloWorldQueuebean 定义:

@Bean
public Queue helloWorldQueue() {
    return new Queue(this.helloWorldQueueName);
}

回顾rabbitTemplatebean 配置,您可以看到它的属性(用于接收消息)和属性(用于发送消息)都有helloWorldQueueset的名称。queueroutingKey

现在我们已经探索了配置,我们可以查看实际使用这些组件的代码。Producer首先,从同一个包中打开类。它包含一个创建main()Spring 的方法ApplicationContext

以下清单显示了该main方法:

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    amqpTemplate.convertAndSend("Hello World");
    System.out.println("Sent: Hello World");
}

在前面的示例中,AmqpTemplatebean 被检索并用于发送一个Message. 由于客户端代码应尽可能依赖接口,因此类型是AmqpTemplate而不是RabbitTemplate. 即使在 中创建的 beanHelloWorldConfiguration是 的实例RabbitTemplate,依赖接口也意味着此代码更具可移植性(您可以独立于代码更改配置)。由于convertAndSend()方法被调用,模板委托给它的MessageConverter实例。在这种情况下,它使用默认值SimpleMessageConverter,但可以为rabbitTemplatebean 提供不同的实现,如HelloWorldConfiguration.

现在开课Consumer。它实际上共享相同的配置基类,这意味着它共享rabbitTemplatebean。这就是我们为该模板配置a routingKey(用于发送)和a queue(用于接收)的原因。正如我们在 中描述的AmqpTemplate,您可以改为将“routingKey”参数传递给发送方法,将“队列”参数传递给接收方法。代码基本上是 Producer的Consumer镜像,调用receiveAndConvert()而不是convertAndSend().

以下清单显示了 的主要方法Consumer

public static void main(String[] args) {
    ApplicationContext context =
        new AnnotationConfigApplicationContext(RabbitConfiguration.class);
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    System.out.println("Received: " + amqpTemplate.receiveAndConvert());
}

如果您运行Producer然后运行Consumer​​,您应该Received: Hello World会在控制台输出中看到。

异步示例

同步示例介绍了同步 Hello World 示例。本节描述了一个稍微高级但功能更强大的选项。通过一些修改,Hello World 示例可以提供异步接收的示例,也称为消息驱动的 POJO。事实上,有一个子包正好提供:org.springframework.amqp.samples.helloworld.async.

同样,我们从发送方开始。打开ProducerConfiguration该类并注意它创建了一个connectionFactory和一个rabbitTemplatebean。这一次,由于配置专用于消息发送端,我们甚至不需要任何队列定义,并且RabbitTemplate只设置了'routingKey'属性。回想一下,消息被发送到交换而不是直接发送到队列。AMQP 默认交换是没有名称的直接交换。所有队列都绑定到该默认交换,并以其名称作为路由键。这就是为什么我们只需要在这里提供路由键。

以下清单显示了rabbitTemplate定义:

public RabbitTemplate rabbitTemplate() {
    RabbitTemplate template = new RabbitTemplate(connectionFactory());
    template.setRoutingKey(this.helloWorldQueueName);
    return template;
}

由于此示例演示了异步消息接收,因此生产端被设计为连续发送消息(如果它是像同步版本那样的每次执行消息模型,那么它实际上是一个消息就不会那么明显了——驱动型消费者)。负责连续发送消息的组件被定义为ProducerConfiguration. 它被配置为每三秒运行一次。

以下清单显示了该组件:

static class ScheduledProducer {

    @Autowired
    private volatile RabbitTemplate rabbitTemplate;

    private final AtomicInteger counter = new AtomicInteger();

    @Scheduled(fixedRate = 3000)
    public void sendMessage() {
        rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
    }
}

您不需要了解所有细节,因为真正的重点应该放在接收方(我们接下来会介绍)。但是,如果您还不熟悉 Spring 任务调度支持,您可以在此处了解更多信息。简短的故事是postProcessorbean 向ProducerConfiguration调度程序注册任务。

现在我们可以转向接收方。为了强调消息驱动的 POJO 行为,我们从响应消息的组件开始。该类被调用HelloWorldHandler并显示在以下清单中:

public class HelloWorldHandler {

    public void handleMessage(String text) {
        System.out.println("Received: " + text);
    }

}

该类是 POJO。它不扩展任何基类,不实现任何接口,甚至不包含任何导入。MessageListenerSpring AMQP正在“适应”该接口MessageListenerAdapter。然后,您可以在SimpleMessageListenerContainer. 对于此示例,容器是在ConsumerConfiguration类中创建的。您可以在那里看到包装在适配器中的 POJO。

以下清单显示了如何listenerContainer定义:

@Bean
public SimpleMessageListenerContainer listenerContainer() {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory());
    container.setQueueName(this.helloWorldQueueName);
    container.setMessageListener(new MessageListenerAdapter(new HelloWorldHandler()));
    return container;
}

SimpleMessageListenerContainer是一个 Spring 生命周期组件,默认情况下会自动启动。如果您查看Consumer该类,您会发现它的main()方法仅包含用于创建ApplicationContext. Producer 的main()方法也是单行引导程序,因为其方法被注解的组件@Scheduled也会自动启动。您可以按任何顺序启动Producerand Consumer,您应该会看到每三秒发送和接收一次消息。

4.4.2. 股票交易

Stock Trading 示例演示了比Hello World 示例更高级的消息传递方案。但是,如果涉及更多一点,配置非常相似。由于我们详细介绍了 Hello World 配置,因此在这里,我们将重点介绍使该示例与众不同的原因。有一个服务器将市场数据(股票报价)推送到主题交易所。然后,客户端可以通过将队列与路由模式(例如, app.stock.quotes.nasdaq.*)绑定来订阅市场数据馈送。该演示的另一个主要特点是由客户端发起并由服务器处理的请求-回复“股票交易”交互。这涉及replyTo由客户端在订单请求消息本身中发送的私有队列。

服务器的核心配置在包RabbitServerConfiguration内的类中org.springframework.amqp.rabbit.stocks.config.server。它扩展了AbstractStockAppRabbitConfiguration. 这是定义服务器和客户端公共资源的地方,包括市场数据主题交换(其名称为'app.stock.marketdata')和服务器为股票交易公开的队列(其名称为'app.stock 。要求')。在该通用配置文件中,您还可以看到Jackson2JsonMessageConverterRabbitTemplate.

服务器特定的配置由两部分组成。首先,它在 上配置市场数据交换,RabbitTemplate因此它不需要在每次调用时都提供该交换名称来发送Message. 它在基本配置类中定义的抽象回调方法中执行此操作。以下清单显示了该方法:

public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
    rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}

其次,声明库存请求队列。在这种情况下,它不需要任何显式绑定,因为它以自己的名称作为路由键绑定到默认的无名称交换。如前所述,AMQP 规范定义了该行为。以下清单显示了stockRequestQueuebean 的定义:

@Bean
public Queue stockRequestQueue() {
    return new Queue(STOCK_REQUEST_QUEUE_NAME);
}

现在您已经看到了服务器的 AMQP 资源的配置,导航到目录org.springframework.amqp.rabbit.stocks下的包src/test/javaServer在那里,您可以看到提供方法的实际类main()ApplicationContext它基于server-bootstrap.xml配置文件创建一个。在那里,您可以看到发布虚拟市场数据的计划任务。该配置依赖于 Spring 的task命名空间支持。引导配置文件还导入了一些其他文件。最有趣的是server-messaging.xml,它是直属下的src/main/resources。在那里,您可以看到messageListenerContainer负责处理股票交易请求的 bean。最后,看看serverHandler定义在 中的 bean server-handlers.xml(它也在 'src/main/resources' 中)。该 bean 是ServerHandler类,是消息驱动的 POJO 的一个很好的例子,它也可以发送回复消息。请注意,它本身并不与框架或任何 AMQP 概念耦合。它接受 aTradeRequest并返回 a TradeResponse。以下清单显示了该handleMessage方法的定义:

public TradeResponse handleMessage(TradeRequest tradeRequest) { ...
}

现在我们已经看到了服务器最重要的配置和代码,我们可以转向客户端。最好的起点可能是RabbitClientConfiguration, 在org.springframework.amqp.rabbit.stocks.config.client包中。请注意,它声明了两个队列,但没有提供明确的名称。以下清单显示了两个队列的 bean 定义:

@Bean
public Queue marketDataQueue() {
    return amqpAdmin().declareQueue();
}

@Bean
public Queue traderJoeQueue() {
    return amqpAdmin().declareQueue();
}

这些是私有队列,并且会自动生成唯一名称。客户端使用第一个生成的队列绑定到服务器公开的市场数据交换。回想一下,在 AMQP 中,消费者与队列交互,而生产者与交换交互。队列到交换的“绑定”是告诉代理将消息从给定交换传递(或路由)到队列。由于市场数据交换是一个主题交换,绑定可以用路由模式来表示。使用对象RabbitClientConfiguration执行此操作Binding,并且该对象是使用BindingBuilderfluent API 生成的。以下清单显示了Binding

@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;

@Bean
public Binding marketDataBinding() {
    return BindingBuilder.bind(
        marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}

请注意,实际值已在属性文件(client.propertiessrc/main/resources)中外部化,并且我们使用 Spring 的@Value注释来注入该值。这通常是一个好主意。否则,该值将被硬编码在一个类中,并且无需重新编译就无法修改。在这种情况下,在更改用于绑定的路由模式时运行客户端的多个版本要容易得多。我们现在可以试试。

首先运行org.springframework.amqp.rabbit.stocks.Server,然后org.springframework.amqp.rabbit.stocks.Client。您应该会看到股票的虚拟报价NASDAQ,因为与 client.properties 中的 'stocks.quote.pattern' 键关联的当前值为 'app.stock.quotes.nasdaq。'。现在,在保持现有ServerClient运行的同时,将该属性值更改为 'app.stock.quotes.nyse。' 并开始第二个Client实例。您应该看到第一个客户仍然收到 NASDAQ 报价,而第二个客户收到 NYSE 报价。您可以改为更改模式以获取所有股票甚至单个股票代码。

我们探索的最后一个特性是从客户端的角度来看的请求-回复交互。回想一下,我们已经看到了ServerHandler接受TradeRequest对象并返回TradeResponse对象的方法。Client侧面对应的代码在RabbitStockServiceGateway包里org.springframework.amqp.rabbit.stocks.gateway。它委托给RabbitTemplate以发送消息。以下清单显示了该send方法:

public void send(TradeRequest tradeRequest) {
    getRabbitTemplate().convertAndSend(tradeRequest, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setReplyTo(new Address(defaultReplyToQueue));
            try {
                message.getMessageProperties().setCorrelationId(
                    UUID.randomUUID().toString().getBytes("UTF-8"));
            }
            catch (UnsupportedEncodingException e) {
                throw new AmqpException(e);
            }
            return message;
        }
    });
}

请注意,在发送消息之前,它会设置replyTo地址。traderJoeQueue它提供了由bean 定义生成的队列(如前所示)。以下清单显示了类本身的@Bean定义:StockServiceGateway

@Bean
public StockServiceGateway stockServiceGateway() {
    RabbitStockServiceGateway gateway = new RabbitStockServiceGateway();
    gateway.setRabbitTemplate(rabbitTemplate());
    gateway.setDefaultReplyToQueue(traderJoeQueue());
    return gateway;
}

如果您不再运行服务器和客户端,请立即启动它们。尝试发送格式为“100 TCKR”的请求。在模拟请求“处理”的短暂人为延迟之后,您应该会看到客户端上出现一条确认消息。

4.4.3. 从非 Spring 应用程序接收 JSON

Spring 应用程序在发送 JSON 时,将TypeId标头设置为完全限定的类名,以帮助接收应用程序将 JSON 转换回 Java 对象。

spring-rabbit-json示例探索了几种从非 Spring 应用程序转换 JSON 的技术。

4.5. 测试支持

为异步应用程序编写集成必然比测试更简单的应用程序更复杂。当诸如@RabbitListener注释之类的抽象出现时,这会变得更加复杂。问题是如何验证在发送消息后,侦听器是否按预期收到了消息。

框架本身有许多单元和集成测试。一些使用模拟,而另一些则使用实时 RabbitMQ 代理进行集成测试。您可以查阅这些测试以了解测试场景的一些想法。

Spring AMQP 1.6 版引入了spring-rabbit-testjar,它为测试其中一些更复杂的场景提供了支持。预计该项目将随着时间的推移而扩展,但我们需要社区反馈来为帮助测试所需的功能提出建议。请使用JIRAGitHub 问题提供此类反馈。

4.5.1。@SpringRabbitTest

使用此注解将基础设施 bean 添加到 Spring 测试ApplicationContext中。这在使用时不是必需的,例如@SpringBootTest因为 Spring Boot 的自动配置会添加 bean。

注册的bean是:

  • CachingConnectionFactory( autoConnectionFactory)。如果@RabbitEnabled存在,则使用其连接工厂。

  • RabbitTemplate( autoRabbitTemplate)

  • RabbitAdmin( autoRabbitAdmin)

  • RabbitListenerContainerFactory( autoContainerFactory)

此外,添加了与@EnableRabbit(to support @RabbitListener) 关联的 bean。

示例 4. Junit5 示例
@SpringJunitConfig
@SpringRabbitTest
public class MyRabbitTests {

	@Autowired
	private RabbitTemplate template;

	@Autowired
	private RabbitAdmin admin;

	@Autowired
	private RabbitListenerEndpointRegistry registry;

	@Test
	void test() {
        ...
	}

	@Configuration
	public static class Config {

        ...

	}

}

使用 JUnit4,替换@SpringJunitConfig@RunWith(SpringRunnner.class).

4.5.2. MockitoAnswer<?>实现

目前有两种Answer<?>实现可以帮助进行测试。

第一个,LatchCountDownAndCallRealMethodAnswer,提供一个Answer<Void>返回null并倒计时一个锁存器。下面的例子展示了如何使用LatchCountDownAndCallRealMethodAnswer

LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("myListener", 2);
doAnswer(answer)
    .when(listener).foo(anyString(), anyString());

...

assertThat(answer.await(10)).isTrue();

第二,LambdaAnswer<T>提供一种机制来选择性地调用真实方法,并提供基于InvocationOnMock结果(如果有)返回自定义结果的机会。

考虑以下 POJO:

public class Thing {

    public String thing(String thing) {
        return thing.toUpperCase();
    }

}

以下类测试ThingPOJO:

Thing thing = spy(new Thing());

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + r))
    .when(thing).thing(anyString());
assertEquals("THINGTHING", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(true, (i, r) -> r + i.getArguments()[0]))
    .when(thing).thing(anyString());
assertEquals("THINGthing", thing.thing("thing"));

doAnswer(new LambdaAnswer<String>(false, (i, r) ->
    "" + i.getArguments()[0] + i.getArguments()[0])).when(thing).thing(anyString());
assertEquals("thingthing", thing.thing("thing"));

从版本 2.2.3 开始,答案会捕获被测方法引发的任何异常。用于answer.getExceptions()获取对它们的引用。

当与@RabbitListenerTestRabbitListenerTestHarness一起使用harness.getLambdaAnswerFor("listenerId", true, …​)时,可以为听众获得正确构建的答案。

4.5.3. @RabbitListenerTestRabbitListenerTestHarness

用注释你的一个@Configuration@RabbitListenerTest会导致框架RabbitListenerAnnotationBeanPostProcessor用一个名为的子类替换标准RabbitListenerTestHarness(它还可以 @RabbitListener通过 进行检测@EnableRabbit)。

RabbitListenerTestHarness两种方式增强听众。首先,它将侦听器包装在 a 中Mockito Spy,从而启用正常的Mockito存根和验证操作。它还可以Advice向侦听器添加一个,从而可以访问参数、结果和引发的任何异常。您可以使用@RabbitListenerTest. 后者用于访问有关调用的较低级别数据。它还支持阻塞测试线程,直到调用异步侦听器。

final @RabbitListener方法不能被窥探或建议。此外,只能监视或建议具有id属性的侦听器。

考虑一些例子。

以下示例使用间谍:

@Configuration
@RabbitListenerTest
public class Config {

    @Bean
    public Listener listener() {
        return new Listener();
    }

    ...

}

public class Listener {

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

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        ...
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        Listener listener = this.harness.getSpy("foo"); (2)
        assertNotNull(listener);
        verify(listener).foo("foo");
    }

    @Test
    public void testOneWay() throws Exception {
        Listener listener = this.harness.getSpy("bar");
        assertNotNull(listener);

        LatchCountDownAndCallRealMethodAnswer answer = this.harness.getLatchAnswerFor("bar", 2); (3)
        doAnswer(answer).when(listener).foo(anyString(), anyString()); (4)

        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");

        assertTrue(answer.await(10));
        verify(listener).foo("bar", this.queue2.getName());
        verify(listener).foo("baz", this.queue2.getName());
    }

}
1 将工具注入测试用例,以便我们可以访问间谍。
2 获取对间谍的引用,以便我们可以验证它是否按预期调用。由于这是一个发送和接收操作,因此无需暂停测试线程,因为它在RabbitTemplate等待回复时已经暂停。
3 在这种情况下,我们只使用了发送操作,因此我们需要一个闩锁来等待容器线程上对侦听器的异步调用。我们使用一个Answer<?>实现来帮助解决这个问题。重要提示:由于侦听器被监视的方式,使用它为harness.getLatchAnswerFor()间谍获取正确配置的答案很重要。
4 配置间谍以调用Answer.

以下示例使用捕获建议:

@Configuration
@ComponentScan
@RabbitListenerTest(spy = false, capture = true)
public class Config {

}

@Service
public class Listener {

    private boolean failed;

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

    @RabbitListener(id="bar", queues="#{queue2.name}")
    public void foo(@Payload String foo, @Header("amqp_receivedRoutingKey") String rk) {
        if (!failed && foo.equals("ex")) {
            failed = true;
            throw new RuntimeException(foo);
        }
        failed = false;
    }

}

public class MyTests {

    @Autowired
    private RabbitListenerTestHarness harness; (1)

    @Test
    public void testTwoWay() throws Exception {
        assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("foo", 0, TimeUnit.SECONDS); (2)
        assertThat(invocationData.getArguments()[0], equalTo("foo"));     (3)
        assertThat((String) invocationData.getResult(), equalTo("FOO"));
    }

    @Test
    public void testOneWay() throws Exception {
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "bar");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "baz");
        this.rabbitTemplate.convertAndSend(this.queue2.getName(), "ex");

        InvocationData invocationData =
            this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS); (4)
        Object[] args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("bar"));
        assertThat((String) args[1], equalTo(queue2.getName()));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("baz"));

        invocationData = this.harness.getNextInvocationDataFor("bar", 10, TimeUnit.SECONDS);
        args = invocationData.getArguments();
        assertThat((String) args[0], equalTo("ex"));
        assertEquals("ex", invocationData.getThrowable().getMessage()); (5)
    }

}
1 将工具注入测试用例,以便我们可以访问间谍。
2 用于harness.getNextInvocationDataFor()检索调用数据 - 在这种情况下,由于它是请求/回复场景,因此无需等待任何时间,因为测试线程在RabbitTemplate等待结果时被挂起。
3 然后我们可以验证参数和结果是否符合预期。
4 这次我们需要一些时间来等待数据,因为它是容器线程上的异步操作,我们需要暂停测试线程。
5 当侦听器抛出异常时,它在throwable调用数据的属性中可用。
将 customAnswer<?>与线束一起使用时,为了正确操作,此类答案应子类化并从线束 ( ) 和 call中ForwardsInvocation获取实际的侦听器(而不是间谍)。有关示例,请参阅提供的Mockito实现源代码。 getDelegate("myListener")super.answer(invocation)Answer<?>

4.5.4. 使用TestRabbitTemplate

提供它TestRabbitTemplate是为了执行一些基本的集成测试,而不需要代理。@Bean当您在测试用例中将其添加为 a时,它会发现上下文中的所有侦听器容器,无论是声明为@Bean还是<bean/>使用@RabbitListener注释。它目前仅支持按队列名称路由。模板从容器中提取消息侦听器并直接在测试线程上调用它。返回回复的侦听器支持请求-回复消息传递(sendAndReceive方法)。

以下测试用例使用模板:

@RunWith(SpringRunner.class)
public class TestRabbitTemplateTests {

    @Autowired
    private TestRabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void testSimpleSends() {
        this.template.convertAndSend("foo", "hello1");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello2");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:"));
        this.template.convertAndSend("foo", "hello3");
        assertThat(this.config.fooIn, equalTo("foo:hello1"));
        this.template.convertAndSend("bar", "hello4");
        assertThat(this.config.barIn, equalTo("bar:hello2"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4"));

        this.template.setBroadcast(true);
        this.template.convertAndSend("foo", "hello5");
        assertThat(this.config.fooIn, equalTo("foo:hello1foo:hello5"));
        this.template.convertAndSend("bar", "hello6");
        assertThat(this.config.barIn, equalTo("bar:hello2bar:hello6"));
        assertThat(this.config.smlc1In, equalTo("smlc1:hello3hello4hello5hello6"));
    }

    @Test
    public void testSendAndReceive() {
        assertThat(this.template.convertSendAndReceive("baz", "hello"), equalTo("baz:hello"));
    }
    @Configuration
    @EnableRabbit
    public static class Config {

        public String fooIn = "";

        public String barIn = "";

        public String smlc1In = "smlc1:";

        @Bean
        public TestRabbitTemplate template() throws IOException {
            return new TestRabbitTemplate(connectionFactory());
        }

        @Bean
        public ConnectionFactory connectionFactory() throws IOException {
            ConnectionFactory factory = mock(ConnectionFactory.class);
            Connection connection = mock(Connection.class);
            Channel channel = mock(Channel.class);
            willReturn(connection).given(factory).createConnection();
            willReturn(channel).given(connection).createChannel(anyBoolean());
            given(channel.isOpen()).willReturn(true);
            return factory;
        }

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

        @RabbitListener(queues = "foo")
        public void foo(String in) {
            this.fooIn += "foo:" + in;
        }

        @RabbitListener(queues = "bar")
        public void bar(String in) {
            this.barIn += "bar:" + in;
        }

        @RabbitListener(queues = "baz")
        public String baz(String in) {
            return "baz:" + in;
        }

        @Bean
        public SimpleMessageListenerContainer smlc1() throws IOException {
            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
            container.setQueueNames("foo", "bar");
            container.setMessageListener(new MessageListenerAdapter(new Object() {

                @SuppressWarnings("unused")
                public void handleMessage(String in) {
                    smlc1In += in;
                }

            }));
            return container;
        }

    }

}

4.5.5。JUnit4@Rules

Spring AMQP 1.7 及更高版本提供了一个名为spring-rabbit-junit. 这个 jar 包含几个实用程序@Rule实例,供运行 JUnit4 测试时使用。请参阅JUnit5 测试的JUnit5 条件

使用BrokerRunning

BrokerRunning提供了一种在代理未运行时让测试成功的机制(localhost默认情况下为 on )。

它还具有用于初始化和清空队列以及删除队列和交换的实用方法。

下面的例子展示了它的用法:

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

有几种isRunning…​静态方法,例如isBrokerAndManagementRunning()验证代理是否启用了管理插件。

配置规则

如果没有代理,有时您希望测试失败,例如每晚的 CI 构建。要在运行时禁用规则,请设置一个名为 的环境RABBITMQ_SERVER_REQUIRED变量true

您可以使用设置器或环境变量覆盖代理属性,例如主机名:

以下示例显示了如何使用 setter 覆盖属性:

@ClassRule
public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar");

static {
    brokerRunning.setHostName("10.0.0.1")
}

@AfterClass
public static void tearDown() {
    brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well
}

您还可以通过设置以下环境变量来覆盖属性:

public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI";
public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME";
public static final String BROKER_PORT = "RABBITMQ_TEST_PORT";
public static final String BROKER_USER = "RABBITMQ_TEST_USER";
public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD";
public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER";
public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD";

这些环境变量会覆盖默认设置(localhost:5672对于 amqp 和localhost:15672/api/管理 REST API)。

amqp更改主机名会影响managementREST API 连接(除非明确设置了管理 uri)。

BrokerRunning还提供了一个static名为的方法setEnvironmentVariableOverrides,让您可以传入包含这些变量的映射。它们覆盖系统环境变量。如果您希望对多个测试套件中的测试使用不同的配置,这可能会很有用。重要提示:必须在调用任何isRunning()创建规则实例的静态方法之前调用该方法。变量值应用于此调用后创建的所有实例。调用clearEnvironmentVariableOverrides()以重置规则以使用默认值(包括任何实际环境变量)。

在您的测试用例中,您可以brokerRunning在创建连接工厂时使用;getConnectionFactory()返回规则的 RabbitMQ ConnectionFactory。以下示例显示了如何执行此操作:

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
使用LongRunningIntegrationTest

LongRunningIntegrationTest是禁用长时间运行测试的规则。您可能希望在开发人员系统上使用它,但要确保在夜间 CI 构建中禁用该规则。

下面的例子展示了它的用法:

@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

要在运行时禁用规则,请设置一个名为 的环境RUN_LONG_INTEGRATION_TESTS变量true

4.5.6。JUnit5 条件

2.0.2 版引入了对 JUnit5 的支持。

使用@RabbitAvailable注解

这个类级别的注释类似于JUnit4BrokerRunning @Rule中讨论的。它由.@RulesRabbitAvailableCondition

注释具有三个属性:

  • queues:在每个测试之前声明(并清除)并在所有测试完成后删除的队列数组。

  • managementtrue如果您的测试还需要在代理上安装管理插件,请将此设置为。

  • purgeAfterEach:(自 2.2 版以来)当true(默认)时,queues将在测试之间清除。

它用于检查代理是否可用,如果不可用则跳过测试。正如配置规则中所讨论的,如果没有代理,名为RABBITMQ_SERVER_REQUIREDif的环境变量会导致测试快速失败。您可以使用配置规则true中讨论的环境变量来配置条件。

此外,RabbitAvailableCondition支持参数化测试构造函数和方法的参数解析。支持两种参数类型:

  • BrokerRunningSupport: 实例(在 2.2 之前,这是一个 JUnit 4BrokerRunning实例)

  • ConnectionFactory:BrokerRunningSupport实例的 RabbitMQ 连接工厂

以下示例显示了两者:

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final ConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory = brokerRunning.getConnectionFactory();
    }

    @Test
    public void test(ConnectionFactory cf) throws Exception {
        assertSame(cf, this.connectionFactory);
        Connection conn = this.connectionFactory.newConnection();
        Channel channel = conn.createChannel();
        DeclareOk declareOk = channel.queueDeclarePassive("rabbitAvailableTests.queue");
        assertEquals(0, declareOk.getConsumerCount());
        channel.close();
        conn.close();
    }

}

前面的测试在框架本身中,并验证参数注入以及条件是否正确创建了队列。

一个实际的用户测试可能如下:

@RabbitAvailable(queues = "rabbitAvailableTests.queue")
public class RabbitAvailableCTORInjectionTests {

    private final CachingConnectionFactory connectionFactory;

    public RabbitAvailableCTORInjectionTests(BrokerRunningSupport brokerRunning) {
        this.connectionFactory =
            new CachingConnectionFactory(brokerRunning.getConnectionFactory());
    }

    @Test
    public void test() throws Exception {
        RabbitTemplate template = new RabbitTemplate(this.connectionFactory);
        ...
    }
}

当您在测试类中使用 Spring 注释应用程序上下文时,您可以通过名为RabbitAvailableCondition.getBrokerRunning().

从 2.2 版本开始,getBrokerRunning()返回一个BrokerRunningSupport对象;以前,返回的是 JUnit 4BrokerRunnning实例。新类具有与BrokerRunning.

以下测试来自框架并演示了用法:

@RabbitAvailable(queues = {
        RabbitTemplateMPPIntegrationTests.QUEUE,
        RabbitTemplateMPPIntegrationTests.REPLIES })
@SpringJUnitConfig
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RabbitTemplateMPPIntegrationTests {

    public static final String QUEUE = "mpp.tests";

    public static final String REPLIES = "mpp.tests.replies";

    @Autowired
    private RabbitTemplate template;

    @Autowired
    private Config config;

    @Test
    public void test() {

        ...

    }

    @Configuration
    @EnableRabbit
    public static class Config {

        @Bean
        public CachingConnectionFactory cf() {
            return new CachingConnectionFactory(RabbitAvailableCondition
                    .getBrokerRunning()
                    .getConnectionFactory());
        }

        @Bean
        public RabbitTemplate template() {

            ...

        }

        @Bean
        public SimpleRabbitListenerContainerFactory
                            rabbitListenerContainerFactory() {

            ...

        }

        @RabbitListener(queues = QUEUE)
        public byte[] foo(byte[] in) {
            return in;
        }

    }

}
使用@LongRunning注解

LongRunningIntegrationTestJUnit4类似@Rule,此注释会导致跳过测试,除非环境变量(或系统属性)设置为true. 以下示例显示了如何使用它:

@RabbitAvailable(queues = SimpleMessageListenerContainerLongTests.QUEUE)
@LongRunning
public class SimpleMessageListenerContainerLongTests {

    public static final String QUEUE = "SimpleMessageListenerContainerLongTests.queue";

...

}

默认情况下,变量是RUN_LONG_INTEGRATION_TESTS,但您可以在注解的value属性中指定变量名称。

5. Spring集成-参考

参考文档的这一部分快速介绍了 Spring Integration 项目中的 AMQP 支持。

5.1。Spring 集成 AMQP 支持

这一简短的章节介绍了 Spring Integration 和 Spring AMQP 项目之间的关系。

5.1.1。介绍

Spring Integration项目包括基于 Spring AMQP 项目构建的 AMQP 通道适配器和网关。这些适配器是在 Spring Integration 项目中开发和发布的。在 Spring Integration 中,“通道适配器”是单向的(单向),而“网关”是双向的(请求-应答)。我们提供入站通道适配器、出站通道适配器、入站网关和出站网关。

由于 AMQP 适配器是 Spring Integration 版本的一部分,因此该文档作为 Spring Integration 发行版的一部分提供。我们在此处提供主要功能的快速概述。有关更多详细信息,请参阅Spring Integration 参考指南。

5.1.2. 入站通道适配器

要从队列接收 AMQP 消息,您可以配置<inbound-channel-adapter>. 以下示例显示如何配置入站通道适配器:

<amqp:inbound-channel-adapter channel="fromAMQP"
                              queue-names="some.queue"
                              connection-factory="rabbitConnectionFactory"/>

5.1.3. 出站通道适配器

要将 AMQP 消息发送到交换,您可以配置<outbound-channel-adapter>. 除了交换名称之外,您还可以选择提供“路由密钥”。以下示例显示了如何定义出站通道适配器:

<amqp:outbound-channel-adapter channel="toAMQP"
                               exchange-name="some.exchange"
                               routing-key="foo"
                               amqp-template="rabbitTemplate"/>

5.1.4。入站网关

要从队列接收 AMQP 消息并响应其回复地址,您可以配置<inbound-gateway>. 以下示例显示如何定义入站网关:

<amqp:inbound-gateway request-channel="fromAMQP"
                      reply-channel="toAMQP"
                      queue-names="some.queue"
                      connection-factory="rabbitConnectionFactory"/>

5.1.5。出站网关

要将 AMQP 消息发送到交换器并接收来自远程客户端的响应,您可以配置<outbound-gateway>. 除了交换名称之外,您还可以选择提供“路由密钥”。以下示例显示如何定义出站网关:

<amqp:outbound-gateway request-channel="toAMQP"
                       reply-channel="fromAMQP"
                       exchange-name="some.exchange"
                       routing-key="foo"
                       amqp-template="rabbitTemplate"/>

6. 其他资源

除了此参考文档之外,还有许多其他资源可以帮助您了解 AMQP。

6.1。延伸阅读

对于那些不熟悉 AMQP 的人来说,该规范实际上是相当可读的。当然,它是权威的信息来源,熟悉该规范的任何人都应该容易理解 Spring AMQP 代码。我们目前对 RabbitMQ 支持的实现是基于他们的 2.8.x 版本,它正式支持 AMQP 0.8 和 0.9.1。我们建议阅读 0.9.1 文档。

RabbitMQ入门页面上有许多很棒的文章、演示文稿和博客。由于这是 Spring AMQP 目前唯一支持的实现,我们还建议将其作为所有与代理相关的问题的一般起点。

附录 A:变更历史

本节描述了随着版本的变化而发生的变化。

A.1。当前的版本

请参阅新增功能

A2。以前的版本

A.2.1。自 2.2 以来 2.3 的变化

本节介绍 2.2 版和 2.3 版之间的更改。有关以前版本的更改,请参阅更改历史记录。

连接工厂更改

现在提供了两个额外的连接工厂。有关详细信息,请参阅选择连接工厂

@RabbitListener变化

您现在可以指定回复内容类型。有关详细信息,请参阅回复 ContentType

消息转换器更改

如果配置了自定义反序列化器,则Jackson2JMessageConverters 现在可以反序列化抽象类(包括接口) 。ObjectMapper有关详细信息,请参阅反序列化抽象类

测试更改

提供了一个新注释@SpringRabbitTest来自动配置一些基础设施 bean,以便在您不使用SpringBootTest. 有关更多信息,请参阅@SpringRabbitTest

RabbitTemplate 更改

模板ReturnCallback已被重构,ReturnsCallback以便在 lambda 表达式中更简单地使用。有关详细信息,请参阅相关发布者确认和退货

当使用返回和相关确认时,CorrelationData现在需要一个独特的id属性。有关详细信息,请参阅相关发布者确认和退货

使用直接回复时,您现在可以配置模板,以便服务器不需要返回相关数据和回复。有关更多信息,请参阅RabbitMQ 直接回复

侦听器容器更改

consumeDelay现在可以使用新的侦听器容器属性;在使用RabbitMQ Sharding Plugin时很有帮助。

现在默认JavaLangErrorHandler调用System.exit(99). 要恢复到以前的行为(什么都不做),请添加一个无操作处理程序。

容器现在支持globalQos属性prefetchCount为通道而不是通道上的每个消费者应用全局。

有关详细信息,请参阅消息侦听器容器配置

消息后处理器更改

compressing MessagePostProcessors 现在使用逗号而不是冒号来分隔多个内容编码。解压缩器可以处理这两种格式,但是,如果您使用此版本生成的消息被 2.2.12 之前的版本使用,您应该将压缩器配置为使用旧的分隔符。有关详细信息,请参阅修改消息 - 压缩等中的重要说明。

多经纪人支持改进

有关更多信息,请参阅多代理(或集群)支持

RepublishMessageRecoverer 更改

未提供支持发布者确认的此恢复器的新子类。有关详细信息,请参阅消息侦听器和异步案例

A.2.2。自 2.1 以来 2.2 的变化

本节介绍 2.1 版和 2.2 版之间的更改。

包更改

以下类/接口已从org.springframework.amqp.rabbit.core.support移至org.springframework.amqp.rabbit.batch

  • BatchingStrategy

  • MessageBatch

  • SimpleBatchingStrategy

此外,ListenerExecutionFailedException已从org.springframework.amqp.rabbit.listener.exception移至org.springframework.amqp.rabbit.support

依赖变化

JUnit (4) 现在是一个可选依赖项,将不再显示为传递依赖项。

spring-rabbit-junit模块现在是模块中的一个编译依赖项,spring-rabbit-test可提供更好的目标应用程序开发体验,而我们只需一个spring-rabbit-test即可获得 AMQP 组件的完整测试实用程序堆栈。

“破坏性”API 更改

JUnit (5)RabbitAvailableCondition.getBrokerRunning()现在返回一个BrokerRunningSupport实例而不是 a BrokerRunning,这取决于 JUnit 4。它具有相同的 API,因此只需更改任何引用的类名即可。有关更多信息,请参阅JUnit5 条件

ListenerContainer 更改

默认情况下,具有致命异常的消息现在会被拒绝并且不会重新排队,即使确认模式是手动的也是如此。有关详细信息,请参阅异常处理

现在可以使用 Micrometer 监控监听器性能Timer。有关详细信息,请参阅监控侦听器性能

@RabbitListener 更改

您现在可以executor在每个侦听器上配置一个,覆盖工厂配置,以便更轻松地识别与侦听器关联的线程。您现在可以acknowledgeMode使用注解的属性覆盖容器工厂的ackMode属性。有关详细信息,请参阅覆盖容器工厂属性

使用批处理时,@RabbitListener方法现在可以在一次调用中接收一整批消息,而不是一次获取一个。

当一次接收一个批处理消息时,最后一条消息的isLastInBatchmessage 属性设置为 true。

此外,收到的批处理消息现在包含amqp_batchSize标题。

侦听器还可以使用在 中创建的批次SimpleMessageListenerContainer,即使该批次不是由生产者创建的。有关详细信息,请参阅选择容器

Spring Data Projection 接口现在由Jackson2JsonMessageConverter. 有关更多信息,请参阅使用 Spring 数据投影接口

Jackson2JsonMessageConverter如果没有属性,则现在假定内容为 JSON ,contentType或者它是默认值 ( application/octet-string)。有关详细信息,请参阅从 a 转换Message

相似地。Jackson2XmlMessageConverter如果没有属性,则现在假定内容是 XML contentType,或者它是默认值 ( application/octet-string)。有关Jackson2XmlMessageConverter更多信息,请参阅。

@RabbitListener方法返回结果时,bean 和Method现在在回复消息属性中可用。这允许配置一个beforeSendReplyMessagePostProcessor,例如,在回复中设置一个标头以指示在服务器上调用了哪个方法。有关详细信息,请参阅回复管理

您现在可以配置 aReplyPostProcessor在发送回复消息之前对其进行修改。有关详细信息,请参阅回复管理

AMQP 记录附加程序更改

Log4J 和 LogbackAmqpAppender现在支持verifyHostnameSSL 选项。

此外,这些附加程序现在可以配置为不将 MDC 条目添加为标头。引入了addMdcAsHeaders布尔选项来配置这种行为。

appenders 现在支持该SaslConfig属性。

有关更多信息,请参阅记录子系统 AMQP 附加程序。

MessageListenerAdapter 更改

现在MessageListenerAdapter提供了一种新buildListenerArguments(Object, Channel, Message)方法来构建要传递给目标侦听器的参数数组,并且不推荐使用旧方法。有关MessageListenerAdapter更多信息,请参阅。

交换/队列声明更改

用于创建和声明对象的 fluent APIExchangeBuilder现在支持“众所周知的”参数。有关更多信息,请参阅队列和交换的 Builder API 。QueueBuilderExchangeQueueRabbitAdmin

有一个新的RabbitAdmin财产explicitDeclarationsOnly。有关详细信息,请参阅条件声明

连接工厂更改

有一个新的CachingConnectionFactory财产shuffleAddresses。在提供代理节点地址列表时,该列表将在创建连接之前被打乱,以便尝试连接的顺序是随机的。有关详细信息,请参阅连接到集群

当使用 Publisher 确认并返回时,回调现在在连接工厂的executor. amqp-clients如果您从回调中执行兔子操作,这可以避免库中可能出现的死锁。有关详细信息,请参阅相关发布者确认和退货

此外,现在使用ConfirmType枚举而不是两个互斥的 setter 方法指定发布者确认类型。

现在RabbitConnectionFactoryBean启用 SSL 时默认使用 TLS 1.2。有关详细信息,请参阅RabbitConnectionFactoryBean和配置 SSL

新的 MessagePostProcessor 类

当消息内容编码设置为 时,分别添加了类DeflaterPostProcessor和以支持压缩和解压缩。InflaterPostProcessordeflate

其他变化

Declarables对象(用于声明多个队列、交换、绑定)现在为每种类型都有一个过滤的 getter。有关详细信息,请参阅声明交换、队列和绑定的集合。

您现在可以在处理其声明Declarable之前自定义每个 bean 。RabbitAdmin有关详细信息,请参阅交换、队列和绑定的自动声明

singleActiveConsumer()已添加到QueueBuilder设置x-single-active-consumer队列参数。有关更多信息,请参阅队列和交换的 Builder API 。

具有类型值的出站标头Class<?>现在使用getName()而不是映射toString()。有关详细信息,请参阅消息属性转换器

现在支持恢复失败的生产者创建的批次。有关详细信息,请参阅使用批处理侦听器重试

A.2.3。自 2.0 以来 2.1 的变化

AMQP 客户端库

amqp-clientSpring AMQP 现在使用RabbitMQ 团队提供的 5.4.x 版本的库。此客户端默认配置了自动恢复。请参阅RabbitMQ 自动连接/拓扑恢复

从 4.0 版开始,客户端默认启用自动恢复。Spring AMQP 在兼容这个特性的同时,有自己的恢复机制,一般不需要客户端恢复特性。我们建议禁用amqp-client自动恢复,以避免AutoRecoverConnectionNotCurrentlyOpenException在代理可用但连接尚未恢复时获取实例。从版本 1.7.1 开始,Spring AMQP 禁用它,除非您明确创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory. 默认情况下,由创建的RabbitMQConnectionFactory实例RabbitConnectionFactoryBean也禁用该选项。
包更改

某些类已移至不同的包。大多数是内部类,不会影响用户应用程序。两个例外是ChannelAwareMessageListenerRabbitListenerErrorHandler。这些接口现在在org.springframework.amqp.rabbit.listener.api.

发布者确认更改

当有未完成的确认时,为发布者确认启用的通道不会返回到缓存中。有关详细信息,请参阅相关发布者确认和退货

侦听器容器工厂改进

您现在可以使用侦听器容器工厂来创建任何侦听器容器,而不仅仅是那些用于@RabbitListener注释或@RabbitListenerEndpointRegistry. 有关更多信息,请参阅使用容器工厂

ChannelAwareMessageListener现在继承自MessageListener.

代理事件监听器

引入ABrokerEventListener将选定的代理事件作为ApplicationEvent实例发布。有关更多信息,请参阅代理事件侦听器。

RabbitAdmin 更改

RabbitAdmin发现 bean 类型Declarables(它是Declarable- QueueExchangeBinding对象的容器)并在代理上声明包含的对象。不鼓励用户使用旧的声明机制<Collection<Queue>>(和其他机制),而应该使用Declarablesbean。默认情况下,旧机制被禁用。有关详细信息,请参阅声明交换、队列和绑定的集合。

AnonymousQueue现在声明实例时默认x-queue-master-locator设置为client-local,以确保在应用程序连接到的节点上创建队列。有关更多信息,请参阅配置代理

RabbitTemplate 更改

您现在可以使用选项配置RabbitTemplatenoLocalReplyConsumer控制操作noLocal中回复消费者的标志sendAndReceive()。有关更多信息,请参阅请求/回复消息

CorrelationData对于发布者确认现在有一个ListenableFuture,您可以使用它来获取确认而不是使用回调。当启用返回和确认时,相关数据(如果提供)将填充返回的消息。有关详细信息,请参阅相关发布者确认和退货

现在提供了一个调用方法replyTimedOut来通知子类回复已超时,从而允许进行任何状态清理。有关详细信息,请参阅回复超时

您现在可以指定ErrorHandler在使用带有 a(默认)的请求/回复时调用的 an,而DirectReplyToMessageListenerContainer在传递回复时发生异常(例如,迟到的回复)。见上。setReplyErrorHandler_ RabbitTemplate(也是从 2.0.11 开始)。

消息转换

我们引入了一个新Jackson2XmlMessageConverter功能来支持将消息从 XML 格式转换为 XML 格式。有关Jackson2XmlMessageConverter更多信息,请参阅。

管理 REST API

现在RabbitManagementTemplate不赞成直接使用com.rabbitmq.http.client.Client(或com.rabbitmq.http.client.ReactorNettyClient)。有关更多信息,请参阅RabbitMQ REST API

@RabbitListener变化

侦听器容器工厂现在可以配置为发送回复时使用的 aRetryTemplate和可选的 a 。RecoveryCallback有关详细信息,请参阅启用侦听器端点注释

异步@RabbitListener返回

@RabbitListener方法现在可以返回ListenableFuture<?>Mono<?>。有关详细信息,请参阅异步@RabbitListener返回类型

连接工厂 Bean 更改

默认情况下,RabbitConnectionFactoryBeannow 调用enableHostnameVerification(). 要恢复到以前的行为,请将enableHostnameVerification属性设置为false

连接工厂更改

现在CachingConnectionFactory无条件地禁用底层 RabbitMQ 中的自动恢复ConnectionFactory,即使在构造函数中提供了预配置的实例。尽管已采取措施使 Spring AMQP 与自动恢复兼容,但仍出现了某些问题仍然存在的极端情况。Spring AMQP 从 1.0.0 开始就有了自己的恢复机制,不需要使用客户端提供的恢复。虽然在构建之后仍然可以启用该功能(使用cachingConnectionFactory.getRabbitConnectionFactory() .setAutomaticRecoveryEnabled()) ,但我们强烈建议您不要这样做。如果您在直接使用客户端工厂(而不是使用 Spring AMQP 组件)时需要自动恢复连接,我们建议您使用单独的 RabbitMQ 。CachingConnectionFactoryConnectionFactory

侦听器容器更改

如果存在标头,默认值ConditionalRejectingErrorHandler现在完全丢弃导致致命错误的消息x-death。有关详细信息,请参阅异常处理

立即重新排队

引入了一个新ImmediateRequeueAmqpException的来通知侦听器容器该消息必须重新排队。为了使用这个特性,ImmediateRequeueMessageRecoverer添加了一个新的实现。

有关详细信息,请参阅消息侦听器和异步案例

A.2.4。自 1.7 以来 2.0 的变化

使用CachingConnectionFactory

从版本 2.0.2 开始,您可以将 配置RabbitTemplate为使用与侦听器容器使用的连接不同的连接。当生产者因任何原因被阻塞时,此更改避免了死锁的消费者。有关详细信息,请参阅使用单独的连接

AMQP 客户端库

Spring AMQP 现在使用amqp-clientRabbitMQ 团队提供的新 5.0.x 版本的库。此客户端默认配置了自动恢复。请参阅RabbitMQ 自动连接/拓扑恢复

从 4.0 版开始,客户端默认启用自动恢复。Spring AMQP 在兼容这个特性的同时,有自己的恢复机制,一般不需要客户端恢复特性。我们建议您禁用amqp-client自动恢复,以避免AutoRecoverConnectionNotCurrentlyOpenException在代理可用但连接尚未恢复时获取实例。从版本 1.7.1 开始,Spring AMQP 禁用它,除非您明确创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory. 默认情况下,由创建的RabbitMQConnectionFactory实例RabbitConnectionFactoryBean也禁用该选项。
一般变更

现在ExchangeBuilder默认建立持久的交换。默认情况下,在 a 中使用的@Exchange注释@QeueueBinding还声明了持久交换。默认情况下,在 a 中使用的@Queue注释在@RabbitListener命名时声明持久队列,如果匿名则声明非持久队列。有关更多信息,请参阅Builder API for Queues and ExchangesAnnotation-driven Listener Endpoints

已删除的课程

UniquelyNameQueue不再提供。创建具有唯一名称的持久非自动删除队列是不常见的。该课程已被删除。如果您需要它的功能,请使用new Queue(UUID.randomUUID().toString()).

新的侦听器容器

DirectMessageListenerContainer与现有的SimpleMessageListenerContainer. 有关选择要使用的容器以及如何配置它们的信息,请参阅选择容器消息侦听器容器配置。

Log4j 附加程序

由于 log4j 的生命周期结束,此附加程序不再可用。有关可用日志附加器的信息,请参阅记录子系统 AMQP附加器。

RabbitTemplate变化
以前,如果非事务性RabbitTemplate在事务性侦听器容器线程上运行,则它会参与现有事务。这是一个严重的错误。但是,用户可能依赖于这种行为。从版本 1.6.2 开始,您必须channelTransacted在模板上设置布尔值,以使其参与容器事务。

现在RabbitTemplate使用DirectReplyToMessageListenerContainer(默认情况下)而不是为每个请求创建一个新的消费者。有关更多信息,请参阅RabbitMQ 直接回复

现在AsyncRabbitTemplate支持直接回复。有关详细信息,请参阅异步兔子模板

和now haveRabbitTemplate和方法接受一个参数,让调用者指定将结果转换为的类型。这对于复杂类型或消息头中未传达类型信息特别有用。它需要一个诸如. 有关详细信息,请参阅接收消息请求/回复消息异步 Rabbit 模板With转换。AsyncRabbitTemplatereceiveAndConvertconvertSendAndReceiveAsTypeParameterizedTypeReference<T>SmartMessageConverterJackson2JsonMessageConverterMessageRabbitTemplate

您现在可以使用 aRabbitTemplate在专用通道上执行多项操作。有关详细信息,请参阅范围操作

侦听器适配器

FunctionalInterfacelambdas 与MessageListenerAdapter. 有关MessageListenerAdapter更多信息,请参阅。

侦听器容器更改
预取默认值

预取默认值曾经是 1,这可能导致高效消费者的利用不足。默认预取值现在是 250,这应该让消费者在最常见的场景中保持忙碌,从而提高吞吐量。

在某些情况下,预取值应该很低——例如,对于大消息,尤其是在处理速度很慢的情况下(消息可能会在客户端进程中增加大量内存),并且如果需要严格的消息排序(在这种情况下,预取值应设置回 1)。此外,对于低容量消息和多个消费者(包括单个侦听器容器实例中的并发),您可能希望减少预取以在消费者之间获得更均匀的消息分布。

有关预取的更多背景信息,请参阅这篇关于RabbitMQ 中消费者利用率的 文章和这篇关于排队理论的文章。

消息数

以前,为容器发出的消息MessageProperties.getMessageCount()返回。0此属性仅在您使用basicGet(例如,来自RabbitTemplate.receive()方法)时适用,并且现在已初始化为null用于容器消息。

事务回滚行为

无论是否配置了事务管理器,事务回滚时的消息重新排队现在都是一致的。有关详细信息,请参阅有关回滚收到的消息的说明。

关机行为

如果容器线程在 内没有响应关闭shutdownTimeout,则默认情况下会强制关闭通道。有关详细信息,请参阅消息侦听器容器配置

收到消息后处理器

如果属性MessagePostProcessor中的aafterReceiveMessagePostProcessors返回null,则丢弃消息(并在适当时确认)。

连接工厂更改

连接和通道侦听器接口现在提供了一种机制来获取有关异常的信息。有关更多信息,请参阅连接和通道侦听器发布是异步的 - 如何检测成功和失败

ConnectionNameStrategy现在提供了一个新的来填充目标 RabbitMQ 连接的应用程序特定标识AbstractConnectionFactory。有关详细信息,请参阅连接和资源管理

重试更改

MissingMessageIdAdvice不再提供。它的功能现在是内置的。有关更多信息,请参阅同步操作中的失败和重试选项

匿名队列命名

默认情况下,AnonymousQueues现在使用默认值Base64UrlNamingStrategy而不是简单UUID字符串命名。有关AnonymousQueue更多信息,请参阅。

@RabbitListener变化

您现在可以在@RabbitListener注释中提供简单的队列声明(仅绑定到默认交换)。有关详细信息,请参阅注释驱动的侦听器端点

您现在可以配置@RabbitListener注释,以便将任何异常返回给发件人。您还可以配置 aRabbitListenerErrorHandler来处理异常。有关详细信息,请参阅处理异常

现在,您可以在使用@QueueBinding注解时将队列与多个路由键绑定。现在还@QueueBinding.exchange()支持自定义交换类型并默认声明持久交换。

您现在可以concurrency在注释级别设置侦听器容器,而不必为不同的并发设置配置不同的容器工厂。

您现在可以autoStartup在注释级别设置侦听器容器的属性,覆盖容器工厂中的默认设置。

您现在可以在容器工厂中的接收之后和发送(回复)MessagePostProcessor实例之前进行设置。RabbitListener

有关详细信息,请参阅注释驱动的侦听器端点

从版本 2.0.3 开始,可以将@RabbitHandler类级别的注释之一@RabbitListener指定为默认值。有关详细信息,请参阅多方法侦听器

容器条件回滚

在使用外部事务管理器(例如 JDBC)时,现在在您为容器提供事务属性时支持基于规则的回滚。当您使用交易建议时,它现在也更加灵活。有关详细信息,请参阅条件回滚

删除 Jackson 1.x 支持

在以前的版本中已弃用,杰克逊1.x转换器和相关组件现在已被删除。您可以使用基于 Jackson 2.x 的类似组件。有关更多信息,请参阅Jackson2JsonMessageConverter

JSON 消息转换器

当为入站 JSON 消息TypeId设置Hashtable为时,默认转换类型为 now LinkedHashMap。以前,它是Hashtable. 要恢复为Hashtable,您可以setDefaultMapType使用DefaultClassMapper

XML 解析器

在解析QueueExchangeXML 组件时,如果存在属性,解析器不再将name属性值注册为 bean 别名id。有关详细信息,请参阅关于idname属性的注释。

阻塞连接

您现在可以将 注入com.rabbitmq.client.BlockedListenerorg.springframework.amqp.rabbit.connection.Connection对象中。此外,当连接被代理阻塞或解除阻塞时,ConnectionBlockedEventConnectionUnblockedEvent事件会被发出。ConnectionFactory

有关详细信息,请参阅连接和资源管理

A.2.5。自 1.6 以来 1.7 的变化

AMQP 客户端库

amqp-clientSpring AMQP 现在使用RabbitMQ 团队提供的新 4.0.x 版本的库。此客户端默认配置了自动恢复。请参阅RabbitMQ 自动连接/拓扑恢复

4.0.x 客户端默认启用自动恢复。Spring AMQP 在兼容这个特性的同时,有自己的恢复机制,一般不需要客户端恢复特性。我们建议禁用amqp-client自动恢复,以避免AutoRecoverConnectionNotCurrentlyOpenException在代理可用但连接尚未恢复时获取实例。从版本 1.7.1 开始,Spring AMQP 禁用它,除非您明确创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory. 默认情况下,由创建的RabbitMQConnectionFactory实例RabbitConnectionFactoryBean也禁用该选项。
Log4j 2 升级

最低 Log4j 2 版本(用于AmqpAppender)现在是2.7. 该框架不再与以前的版本兼容。有关更多信息,请参阅记录子系统 AMQP 附加程序。

Logback 附加程序

默认情况下,此附加程序不再捕获调用方数据(方法、行号)。includeCallerData您可以通过设置配置选项重新启用它。有关可用日志附加器的信息,请参阅记录子系统 AMQP附加器。

Spring重试升级

最低 Spring Retry 版本现在是1.2. 该框架不再与以前的版本兼容。

关机行为

您现在可以设置forceCloseChanneltrue,如果容器线程在 内不响应关闭shutdownTimeout,则通道将被强制关闭,从而导致任何未确认的消息重新排队。有关详细信息,请参阅消息侦听器容器配置

FasterXML Jackson 升级

Jackson 的最低版本现在是2.8. 该框架不再与以前的版本兼容。

JUnit@Rules

以前由框架内部使用的规则现在可以在一个名为spring-rabbit-junit. 有关详细信息,请参阅JUnit4@Rules

容器条件回滚

当您使用外部事务管理器(例如 JDBC)时,当您为容器提供事务属性时,现在支持基于规则的回滚。现在,当您使用交易建议时,它也更加灵活。

连接命名策略

ConnectionNameStrategy现在提供了一个新的来填充目标 RabbitMQ 连接的应用程序特定标识AbstractConnectionFactory。有关详细信息,请参阅连接和资源管理

侦听器容器更改
事务回滚行为

您现在可以将事务回滚时的消息重新排队配置为一致,无论是否配置了事务管理器。有关详细信息,请参阅有关回滚收到的消息的说明。

A.2.6。早期版本

有关先前版本的更改,请参阅先前版本。

A.2.7. 自 1.5 以来 1.6 的变化

测试支持

现在提供了一个新的测试支持库。有关更多信息,请参阅测试支持

建造者

现在可以使用为配置Queue和对象提供流畅 API 的构建器。有关更多信息,Exchange请参阅队列和交换的 Builder API 。

命名空间更改
连接工厂

您现在可以thread-factory向连接工厂 bean 声明添加一个 - 例如,为amqp-client库创建的线程命名。有关详细信息,请参阅连接和资源管理

使用 时CacheMode.CONNECTION,您现在可以限制允许的连接总数。有关详细信息,请参阅连接和资源管理

队列定义

您现在可以为匿名队列提供命名策略。有关AnonymousQueue更多信息,请参阅。

侦听器容器更改
空闲消息侦听器检测

您现在可以配置侦听器容器以ApplicationEvent在空闲时发布实例。有关更多信息,请参阅检测空闲异步使用者

不匹配的队列检测

默认情况下,当侦听器容器启动时,如果检测到具有不匹配的属性或参数的队列,容器会记录异常但继续侦听。容器现在有一个名为 的属性mismatchedQueuesFatal,如果在启动期间检测到问题,它会阻止容器(和上下文)启动。如果稍后检测到问题,例如从连接失败中恢复后,它也会停止容器。有关详细信息,请参阅消息侦听器容器配置

侦听器容器日志记录

现在,侦听器容器将beanNameSimpleAsyncTaskExecutor作为threadNamePrefix. 它对日志分析很有用。

默认错误处理程序

默认错误处理程序 ( ConditionalRejectingErrorHandler) 现在将不可恢复@RabbitListener 的异常视为致命异常。有关详细信息,请参阅异常处理

AutoDeclareRabbitAdmin实例

有关在应用程序上下文中使用实例的选项语义的一些更改,请参阅消息侦听器容器配置( )。autoDeclareRabbitAdmin

AmqpTemplate: 超时接收

已经为 及其实现引入了许多新receive()方法。有关更多信息,请参阅轮询消费者timeoutAmqpTemplateRabbitTemplate

使用AsyncRabbitTemplate

一个新AsyncRabbitTemplate的被介绍了。该模板提供了多个发送和接收方法,返回值为 a ListenableFuture,稍后可以使用这些方法同步或异步获取结果。有关详细信息,请参阅异步兔子模板

RabbitTemplate变化

1.4.1 引入了在代理支持时使用直接回复的能力。它比为每个回复使用临时队列更有效。useTemporaryReplyQueues此版本允许您通过将属性设置为 来覆盖此默认行为并使用临时队列true。有关更多信息,请参阅RabbitMQ 直接回复

现在RabbitTemplate支持user-id-expressionuserIdExpression使用 Java 配置时)。有关更多信息,请参阅验证用户 ID RabbitMQ 文档验证用户 ID

消息属性
使用CorrelationId

message 属性现在correlationId可以是String. 有关详细信息,请参阅消息属性转换器

长字符串标题

以前,将DefaultMessagePropertiesConverter长于长字符串限制(默认为 1024)的“转换”标头转换为 a DataInputStream(实际上,它引用了LongString实例的DataInputStream)。在输出时,此标头未转换(转换为字符串除外——例如,java.io.DataInputStream@1d057a39通过调用 toString()流)。

在此版本中,长LongString实例现在LongString默认保留为实例。您可以使用getBytes[]toString()getStream()方法访问内容。大量输入LongString现在也可以在输出上正确“转换”。

有关详细信息,请参阅消息属性转换器

入库交付模式

deliveryMode属性不再映射到MessageProperties.deliveryMode. 如果使用同一MessageProperties对象发送出站消息,此更改可避免意外传播。相反,入站deliveryMode标头映射到MessageProperties.receivedDeliveryMode.

有关详细信息,请参阅消息属性转换器

使用带注释的端点时,标头在名为 的标头中提供AmqpHeaders.RECEIVED_DELIVERY_MODE

有关详细信息,请参阅带注释的端点方法签名

入站用户 ID

user_id属性不再映射到MessageProperties.userId. 如果使用同一MessageProperties对象发送出站消息,此更改可避免意外传播。相反,入站userId标头映射到MessageProperties.receivedUserId.

有关详细信息,请参阅消息属性转换器

当您使用带注释的端点时,标头在名为AmqpHeaders.RECEIVED_USER_ID.

有关详细信息,请参阅带注释的端点方法签名

RabbitAdmin变化
申报失败

以前,该ignoreDeclarationFailures标志仅在通道上生效IOException(例如不匹配的参数)。它现在对任何异常(例如TimeoutException)生效。此外,DeclarationExceptionEvent现在只要声明失败,就会发布 a。最后RabbitAdmin一个声明事件也可用作属性lastDeclarationExceptionEvent。有关更多信息,请参阅配置代理

@RabbitListener变化
每个 Bean 的多个容器

当您使用 Java 8 或更高版本时,您现在可以向类或其方法添加多个@RabbitListener注释。@Bean使用 Java 7 或更早版本时,您可以使用@RabbitListeners容器注解来提供相同的功能。有关@Repeatable @RabbitListener更多信息,请参阅。

@SendToSpEL 表达式

@SendTo用于路由没有replyTo属性的回复现在可以是针对请求/回复评估的 SpEL 表达式。有关详细信息,请参阅回复管理

@QueueBinding改进

您现在可以在@QueueBinding注释中为队列、交换和绑定指定参数。标头交换现在受@QueueBinding. 有关详细信息,请参阅注释驱动的侦听器端点

延迟消息交换

Spring AMQP 现在对 RabbitMQ 延迟消息交换插件具有一流的支持。有关详细信息,请参阅延迟消息交换

交换内部标志

任何Exchange定义现在都可以标记为internal,并RabbitAdmin在声明交换时将值传递给代理。有关更多信息,请参阅配置代理

CachingConnectionFactory变化
CachingConnectionFactory缓存统计

现在CachingConnectionFactory在运行时和通过 JMX 提供缓存属性。有关详细信息,请参阅运行时缓存属性

访问底层 RabbitMQ 连接工厂

添加了一个新的 getter 以提供对底层工厂的访问。例如,您可以使用此 getter 添加自定义连接属性。有关详细信息,请参阅添加自定义客户端连接属性

通道缓存

默认通道缓存大小已从 1 增加到 25。有关详细信息,请参阅连接和资源管理

此外,SimpleMessageListenerContainer不再将缓存大小调整为至少与数量一样大concurrentConsumers ——这是多余的,因为容器消费者通道永远不会被缓存。

使用RabbitConnectionFactoryBean

工厂 bean 现在公开一个属性,以将客户端连接属性添加到由结果工厂建立的连接。

Java反序列化

现在,您可以在使用 Java 反序列化时配置允许类的“允许列表”。如果您接受来自不受信任来源的带有序列化 java 对象的消息,您应该考虑创建一个允许列表。有关更多信息,请参阅Java 反序列化。

JSONMessageConverter

JSON 消息转换器的改进现在允许使用消息头中没有类型信息的消息。有关更多信息,请参阅注释方法的消息转换Jackson2JsonMessageConverter

记录附加器
日志4j 2

添加了一个 log4j 2 附加程序,现在可以使用addresses属性配置附加程序以连接到代理集群。

客户端连接属性

您现在可以将自定义客户端连接属性添加到 RabbitMQ 连接。

有关更多信息,请参阅记录子系统 AMQP 附加程序。

A.2.8。自 1.4 以来 1.5 的变化

spring-erlang不再支持

spring-erlangjar 不再包含在分发中。请改用RabbitMQ REST API

CachingConnectionFactory变化
空地址属性在CachingConnectionFactory

以前,如果连接工厂配置了主机和端口,但还为 提供了空字符串 addresses,则主机和端口将被忽略。现在,空addresses字符串被视为与 a 相同null,并且使用主机和端口。

URI 构造函数

CachingConnectionFactory有一个带有参数的附加构造函数来URI配置代理连接。

连接重置

添加了一个名为的新方法resetConnection(),让用户可以重置连接(或多个连接)。例如,您可以使用它在故障转移到辅助代理后重新连接到主代理。这确实会影响正在进行的操作。现有destroy()方法的作用完全相同,但新方法的名称不那么令人生畏。

控制容器队列声明行为的属性

当侦听器容器消费者启动时,他们尝试被动地声明队列以确保它们在代理上可用。以前,如果这些声明失败(例如,因为队列不存在)或当 HA 队列被移动时,重试逻辑被固定为以五秒为间隔的三次重试尝试。如果队列仍然不存在,则行为由missingQueuesFatal属性控制(默认值:true)。此外,对于配置为从多个队列进行侦听的容器,如果只有一部分队列可用,则消费者会以 60 秒的固定间隔重试丢失的队列。

declarationRetries和属性现在是可配置的failedDeclarationRetryIntervalretryDeclarationInterval有关详细信息,请参阅消息侦听器容器配置

课程包更改

课程RabbitGatewaySupport已从o.s.amqp.rabbit.core.support移至。o.s.amqp.rabbit.core

DefaultMessagePropertiesConverter变化

您现在可以配置以确定转换为 a而不是 aDefaultMessagePropertiesConverter的 a 的最大长度。转换器有一个替代构造函数,该构造函数将值作为限制。以前,此限制以字节为单位进行硬编码。(在 1.4.4 中也可用)。LongStringStringDataInputStream1024

@RabbitListener改进
@QueueBinding为了@RabbitListener

bindings属性已添加到@RabbitListener注释中,与该属性互斥,queues 以允许在Broker 上指定queue、其exchangebindingfor 声明。RabbitAdmin

输入@SendTo

a的默认回复地址 ( @SendTo)@RabbitListener现在可以是 SpEL 表达式。

通过属性的多个队列名称

您现在可以结合使用 SpEL 和属性占位符来为侦听器指定多个队列。

有关详细信息,请参阅注释驱动的侦听器端点

自动交换、队列和绑定声明

您现在可以声明定义这些实体集合的 bean,RabbitAdmin并将内容添加到它在建立连接时声明的实体列表中。有关详细信息,请参阅声明交换、队列和绑定的集合。

RabbitTemplate变化
reply-address添加

reply-address属性已<rabbit-template>作为替代添加到组件中reply-queue。有关更多信息,请参阅请求/回复消息。(也可在 1.4.4 中作为 的设置器使用RabbitTemplate)。

阻塞receive方法

现在RabbitTemplate支持阻塞receiveconvertAndReceive方法。有关更多信息,请参阅轮询消费者

sendAndReceive方法强制

如果在使用and方法mandatory时设置了标志,则调用线程会抛出一个如果请求消息无法传递。有关详细信息,请参阅回复超时sendAndReceiveconvertSendAndReceiveAmqpMessageReturnedException

不正确的回复侦听器配置

当使用命名回复队列时,框架会尝试验证回复侦听器容器的正确配置。

有关详细信息,请参阅回复侦听器容器

RabbitManagementTemplate添加

RabbitManagementTemplate引入通过使用其管理插件提供的 REST API 来监视和配置 RabbitMQ 代理。有关更多信息,请参阅RabbitMQ REST API

侦听器容器 Bean 名称 (XML)

元素上的id属性<listener-container/>已被删除。从此版本开始,子元素id上的<listener/>单独用于命名为每个侦听器元素创建的侦听器容器 bean。

应用正常的 Spring bean 名称覆盖。如果稍后使用与现有 bean<listener/>相同的解析,则新定义将覆盖现有定义。id以前,bean 名称由and元素的id属性组成。<listener-container/><listener/>

迁移到此版本时,如果您的元素有id属性,请<listener-container/>删除它们并改为id在子元素上设置 。<listener/>

但是,为了支持作为一个组启动和停止容器,group添加了一个新属性。定义此属性后,由此元素创建的容器将添加到具有此名称的 bean 中,类型为Collection<SimpleMessageListenerContainer>。您可以遍历该组来启动和停止容器。

班级级别@RabbitListener

现在@RabbitListener可以在类级别应用注释。与新的@RabbitHandler方法注释一起,这使您可以根据有效负载类型选择处理程序方法。有关详细信息,请参阅多方法侦听器

SimpleMessageListenerContainer: 退避支持

现在SimpleMessageListenerContainer可以为启动恢复提供一个BackOff实例。consumer有关详细信息,请参阅消息侦听器容器配置

通道关闭记录

引入了一种控制通道关闭日志级别的机制。请参阅记录通道关闭事件

应用程序事件

SimpleMessageListenerContainer消费者失败时,现在会发出应用程序事件。有关更多信息,请参阅消费者事件

消费者标签配置

以前,异步消费者的消费者标签是由代理生成的。在此版本中,现在可以为侦听器容器提供命名策略。请参阅消费者标签

使用MessageListenerAdapter

现在MessageListenerAdapter支持队列名称(或消费者标签)到方法名称的映射,以根据接收消息的队列来确定调用哪个委托方法。

LocalizedQueueConnectionFactory添加

LocalizedQueueConnectionFactory是一个新的连接工厂,它连接到镜像队列实际驻留的集群中的节点。

匿名队列命名

从版本 1.5.3 开始,您现在可以控制AnonymousQueue名称的生成方式。有关AnonymousQueue更多信息,请参阅。

A.2.9。自 1.3 以来 1.4 的变化

@RabbitListener注解

POJO 侦听器可以用或来注释@RabbitListener、启用。此功能需要 Spring Framework 4.1。有关详细信息,请参阅注释驱动的侦听器端点@EnableRabbit<rabbit:annotation-driven />

RabbitMessagingTemplate添加

一个新的RabbitMessagingTemplate允许您使用spring-messaging Message实例与 RabbitMQ 交互。在内部,它使用RabbitTemplate,您可以正常配置。此功能需要 Spring Framework 4.1。有关详细信息,请参阅消息集成

侦听器容器missingQueuesFatal属性

1.3.5 介绍了missingQueuesFatal上的属性SimpleMessageListenerContainer。这现在在侦听器容器命名空间元素上可用。请参阅消息侦听器容器配置

兔子模板ConfirmCallback接口

此接口上的confirm方法有一个名为 的附加参数cause。如果可用,此参数包含否定确认 (nack) 的原因。请参阅相关发布者确认和退货

RabbitConnectionFactoryBean添加

RabbitConnectionFactoryBean创建. ConnectionFactory_ CachingConnectionFactory这允许使用 Spring 的依赖注入配置 SSL 选项。请参阅配置基础客户端连接工厂

使用CachingConnectionFactory

现在CachingConnectionFactory允许将connectionTimeout其设置为名称空间中的属性或属性。它在底层 RabbitMQ 上设置属性ConnectionFactory。请参阅配置基础客户端连接工厂

日志附加器

引入了Logback org.springframework.amqp.rabbit.logback.AmqpAppender。它提供类似于 的选项org.springframework.amqp.rabbit.log4j.AmqpAppender。有关详细信息,请参阅这些类的 JavaDoc。

Log4jAmqpAppender现在支持该deliveryMode属性(PERSISTENTNON_PERSISTENT,默认值:) PERSISTENT。以前,所有 log4j 消息都是PERSISTENT.

appender 还支持Message在发送前修改 - 例如,允许添加自定义标头。子类应该覆盖postProcessMessageBeforeSend().

侦听器队列

现在,默认情况下,侦听器容器会在启动期间重新声明任何丢失的队列。添加了一个新auto-declare属性<rabbit:listener-container>以防止这些重新声明。请参阅auto-delete队列

RabbitTemplate:mandatoryconnectionFactorySelector表达式

、 和 SpEL 表达式的mandatoryExpression属性sendConnectionFactorySelectorExpressionreceiveConnectionFactorySelectorExpression添加到RabbitTemplate. mandatoryExpression用于在使用 a 时针对每个请求消息评估布尔mandatoryReturnCallback。请参阅相关发布者确认和退货sendConnectionFactorySelectorExpressionand在提供receiveConnectionFactorySelectorExpressionan 时使用AbstractRoutingConnectionFactory,以确定每个 AMQP 协议交互操作在运行时lookupKey的目标。ConnectionFactory请参阅路由连接工厂

侦听器和路由连接工厂

您可以SimpleMessageListenerContainer使用路由连接工厂配置 a 以启用基于队列名称的连接选择。请参阅路由连接工厂

RabbitTemplate:RecoveryCallback选项

recoveryCallback属性已添加到retryTemplate.execute(). 请参阅添加重试功能

MessageConversionException改变

此异常现在是AmqpException. 考虑以下代码:

try {
    template.convertAndSend("thing1", "thing2", "cat");
}
catch (AmqpException e) {
	...
}
catch (MessageConversionException e) {
	...
}

第二个 catch 块不再可达,需要移动到 catch-all AmqpExceptioncatch 块上方。

RabbitMQ 3.4 兼容性

Spring AMQP 现在与 RabbitMQ 3.4 兼容,包括直接回复。有关更多信息,请参阅兼容性RabbitMQ 直接回复

ContentTypeDelegatingMessageConverter添加

ContentTypeDelegatingMessageConverter已引入以根据 中的属性选择要使用MessageConverter的。有关详细信息,请参阅消息转换器contentTypeMessageProperties

A.2.10。自 1.2 以来 1.3 的变化

侦听器并发

侦听器容器现在支持根据工作负载动态扩展消费者数量,或者您可以在不停止容器的情况下以编程方式更改并发性。请参阅侦听器并发

侦听器队列

侦听器容器现在允许在运行时修改其侦听的队列。此外,如果至少有一个已配置的队列可供使用,则容器现在将启动。请参阅侦听器容器队列

此侦听器容器现在在启动期间重新声明所有自动删除队列。请参阅auto-delete队列

消费者优先

监听器容器现在支持消费者参数,让x-priority参数被设置。见消费者优先

独家消费者

您现在可以配置SimpleMessageListenerContainer单个exclusive消费者,防止其他消费者收听队列。请参阅独占消费者

兔子管理员

您现在可以让代理生成队列名称,而不管durableautoDeleteexclusive设置。请参阅配置代理

直接交换绑定

以前,从配置元素中省略key属性会导致队列或交换与空字符串绑定作为路由键。现在它与提供的名称绑定或。如果您希望使用空字符串路由键绑定,则需要指定.bindingdirect-exchangeQueueExchangekey=""

AmqpTemplate变化

现在AmqpTemplate提供了几种同步receiveAndReply方法。这些是由RabbitTemplate. 有关详细信息,请参阅接收消息

现在RabbitTemplate支持配置RetryTemplate在代理不可用时尝试重试(使用可选的退避策略)。有关详细信息,请参阅添加重试功能

缓存连接工厂

您现在可以将缓存连接工厂配置为缓存Connection实例及其Channel实例,而不是使用单个连接并仅缓存Channel实例。请参阅连接和资源管理

绑定参数

现在支持解析<binding>子元素。您现在可以使用属性对(匹配单个标题)或子元素(允许匹配多个标题)配置 。这些选项是相互排斥的。请参阅标头交换<exchange><binding-arguments><binding><headers-exchange>key/value<binding-arguments>

路由连接工厂

一个新SimpleRoutingConnectionFactory的被介绍了。它允许配置ConnectionFactories映射,以确定ConnectionFactory在运行时使用的目标。请参阅路由连接工厂

MessageBuilderMessagePropertiesBuilder

现在提供了用于构建消息或消息属性的“Fluent API”。请参阅消息生成器 API

RetryInterceptorBuilder改变

现在提供了用于构建侦听器容器重试拦截器的“Fluent API”。请参阅同步操作中的失败和重试选项

RepublishMessageRecoverer添加

提供此新MessageRecoverer功能是为了允许在重试用尽时将失败的消息发布到另一个队列(包括标头中的堆栈跟踪信息)。请参阅消息侦听器和异步案例

默认错误处理程序(自 1.3.2 起)

默认值ConditionalRejectingErrorHandler已添加到侦听器容器中。这个错误处理程序检测到致命的消息转换问题,并指示容器拒绝该消息,以防止代理不断地重新传递不可转换的消息。请参阅异常处理

侦听器容器“missingQueuesFatal”属性(自 1.3.5 起)

现在SimpleMessageListenerContainer有一个名为missingQueuesFatal(默认值:)的属性true。以前,缺少队列总是致命的。请参阅消息侦听器容器配置

A.2.11。自 1.1 起对 1.2 的更改

RabbitMQ 版本

Spring AMQP 现在默认使用 RabbitMQ 3.1.x(但保留与早期版本的兼容性)。为 RabbitMQ 3.1.x 不再支持的功能添加了某些弃用 - 联合交换和immediate.RabbitTemplate

兔子管理员

RabbitAdmin现在提供了一个选项,让交换、排队和绑定声明在声明失败时继续。以前,所有声明都因失败而停止。通过设置ignore-declaration-exceptions,此类异常会被记录(在WARN级别上),但会继续进行进一步的声明。这可能有用的一个示例是当队列声明由于稍微不同的ttl设置而失败时,通常会阻止其他声明继续进行。

RabbitAdmin现在提供了一个额外的方法,叫做getQueueProperties(). 您可以使用它来确定代理上是否存在队列(返回null不存在的队列)。此外,它返回队列中的当前消息数以及当前消费者数。

兔子模板

以前,当这些…​sendAndReceive()方法与固定回复队列一起使用时,两个自定义标头用于关联数据以及保留和恢复回复队列信息。在此版本中,correlationId默认使用标准消息属性 ( ),但您可以指定要使用的自定义属性。此外,嵌套replyTo信息现在保留在模板内部,而不是使用自定义标题。

immediate属性已弃用。使用 RabbitMQ 3.0.x 或更高版本时不得设置此属性。

JSON 消息转换器

现在提供了Jackson 2.x MessageConverter,以及使用 Jackson 1.x 的现有转换器。

队列和其他项目的自动声明

以前,在声明队列、交换和绑定时,您无法定义用于声明的连接工厂。每个RabbitAdmin都使用其连接声明所有组件。

从此版本开始,您现在可以将声明限制为特定RabbitAdmin实例。请参阅条件声明

AMQP 远程处理

现在提供了使用 Spring 远程处理技术的设施,使用 AMQP 作为 RPC 调用的传输。有关更多信息,请参阅Spring Remoting with AMQP

请求的心跳

一些用户要求requestedHeartBeats在 Spring AMQP 上公开底层客户端连接工厂的属性CachingConnectionFactory。现在可以使用了。以前,需要将 AMQP 客户端工厂配置为单独的 bean,并在CachingConnectionFactory.

A.2.12。自 1.0 以来对 1.1 的更改

一般的

Spring-AMQP 现在是用 Gradle 构建的。

添加对发布者确认和返回的支持。

添加对 HA 队列和代理故障转移的支持。

添加对死信交换和死信队列的支持。

AMQP Log4j 附加程序

添加一个选项以支持将消息 ID 添加到记录的消息中。

添加一个选项以允许Charset在转换Stringbyte[].


1. see XML Configuration