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'
非常非常快
本节提供最快的介绍。
首先,添加以下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 Queue
,Exchange
并Binding
代表用户向代理声明它们。因此,您无需在简单的 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. 参考
4.1。使用 Spring AMQP
本章探讨了使用 Spring AMQP 开发应用程序的基本组件的接口和类。
4.1.1。AMQP 抽象
Spring AMQP 由两个模块组成(每个模块在发行版中由一个 JAR 表示):spring-amqp
和spring-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.7 、1.6.11 、1.7.4 和开始2.0.0 ,如果消息体是序列化的java 对象,则在执行操作时(例如在日志消息中)Serializable 不再反序列化(默认情况下)。toString() 这是为了防止不安全的反序列化。默认情况下,只有java.util 和java.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
。基本类型是:direct
、topic
、fanout
和headers
。在核心包中,您可以找到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-pool2
jar 必须在类路径上才能使用这个工厂。
@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
这个工厂管理一个连接和两个ThreadLocal
s,一个用于事务通道,另一个用于非事务通道。这个工厂确保同一个线程上的所有操作都使用同一个通道(只要它保持打开状态)。这有助于严格的消息排序,而不需要Scoped Operations。为避免内存泄漏,如果您的应用程序使用许多短期线程,您必须调用工厂closeThreadChannel()
来释放通道资源。从版本 2.3.7 开始,线程可以将其通道传输到另一个线程。有关详细信息,请参阅多线程环境中的严格消息排序。
CachingConnectionFactory
提供的第三个实现是CachingConnectionFactory
,默认情况下,它建立一个可由应用程序共享的单一连接代理。共享连接是可能的,因为与 AMQP 进行消息传递的“工作单元”实际上是一个“通道”(在某些方面,这类似于 JMS 中连接和会话之间的关系)。连接实例提供了一个createChannel
方法。该CachingConnectionFactory
实现支持这些通道的缓存,并且它根据它们是否是事务性的为通道维护单独的缓存。创建 的实例时CachingConnectionFactory
,您可以通过构造函数提供“主机名”。您还应该提供“用户名”和“密码”属性。要配置通道缓存的大小(默认为25),可以调用该
setChannelCacheSize()
方法。
从 1.3 版开始,您可以配置CachingConnectionFactory
缓存连接以及仅通道。在这种情况下,每次调用createConnection()
都会创建一个新连接(或从缓存中检索一个空闲连接)。关闭连接会将其返回到缓存(如果尚未达到缓存大小)。在此类连接上创建的通道也会被缓存。在某些环境中使用单独的连接可能很有用,例如从 HA 集群消费,与负载均衡器结合使用,以连接到不同的集群成员等。要缓存连接,请将 设置cacheMode
为CacheMode.CONNECTION
。
这不限制连接数。相反,它指定允许多少空闲打开的连接。 |
从版本 1.5.5 开始,connectionLimit
提供了一个名为的新属性。设置此属性时,它会限制允许的连接总数。设置后,如果达到限制,channelCheckoutTimeLimit
则用于等待连接变为空闲。如果超过时间,AmqpTimeoutException
则抛出 an。
当缓存模式为 此外,在撰写本文时,该 |
重要的是要了解缓存大小(默认情况下)不是限制,而仅仅是可以缓存的通道数。如果缓存大小为 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>
命名连接
从版本 1.7 开始,ConnectionNameStrategy
提供了用于注入AbstractionConnectionFactory
. 生成的名称用于目标 RabbitMQ 连接的特定于应用程序的标识。如果 RabbitMQ 服务器支持,连接名称会显示在管理 UI 中。此值不必是唯一的,也不能用作连接标识符——例如,在 HTTP API 请求中。该值应该是人类可读的,并且是键ClientProperties
下的一部分connection_name
。您可以使用简单的 Lambda,如下所示:
connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION");
该ConnectionFactory
参数可用于通过某种逻辑区分目标连接名称。默认情况下,使用beanName
的AbstractConnectionFactory
、表示对象的十六进制字符串和内部计数器来生成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
和。这些使您可以提供应用程序逻辑以对代理上的问题做出适当的反应,并(例如)采取一些纠正措施。ConnectionUnblockedEvent
BlockedListener
当应用程序配置了单个CachingConnectionFactory 时,默认情况下使用 Spring Boot 自动配置,当连接被 Broker 阻止时,应用程序将停止工作。当它被 Broker 阻止时,它的任何客户端都会停止工作。如果我们在同一个应用程序中有生产者和消费者,当生产者阻塞连接(因为 Broker 上没有资源了)并且消费者无法释放它们(因为连接被阻塞)时,我们可能最终会出现死锁。CachingConnectionFactory 为了缓解这个问题,我们建议再创建一个具有相同选项的单独实例——一个用于生产者,一个用于消费者。对于在消费者线程上执行的事务生产者来说,单独CachingConnectionFactory 的是不可能的,因为他们应该重用Channel 与消费者交易相关联。
|
从版本 2.0.2 开始,RabbitTemplate
有一个配置选项可以自动使用第二个连接工厂,除非正在使用事务。有关详细信息,请参阅使用单独的连接。发布者连接的ConnectionNameStrategy
与主策略相同,.publisher
附加到调用方法的结果。
从 1.7.7 版本开始,提供 an ,当无法创建 aAmqpResourceNotAvailableException
时抛出(例如,因为达到限制并且缓存中没有可用通道)。您可以在一些退避后使用此异常来恢复操作。SimpleConnection.createChannel()
Channel
channelMax
RetryPolicy
配置底层客户端连接工厂
使用 Rabbit 客户端的CachingConnectionFactory
一个实例ConnectionFactory
。在. _ host
_ port
_ userName
_ password
_ requestedHeartBeat
_ 要设置其他属性(例如 ),您可以定义 Rabbit 工厂的实例并使用. 使用命名空间时(如前所述),您需要在属性中提供对已配置工厂的引用。为方便起见,提供了一个工厂 bean 来帮助在 Spring 应用程序上下文中配置连接工厂,如下一节所述。connectionTimeout
CachingConnectionFactory
clientProperties
CachingConnectionFactory
connection-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 文档。省略keyStore
andtrustStore
配置以通过 SSL 连接而不进行证书验证。下一个示例展示了如何提供密钥和信任库配置。
该属性是一个指向包含以下键的属性文件sslPropertiesLocation
的 Spring :Resource
keyStore=file:/secret/keycert.p12
trustStore=file:/secret/trustStore
keyStore.passPhrase=secret
trustStore.passPhrase=secret
和是 SpringkeyStore
指向商店。通常,此属性文件由具有读取访问权限的应用程序的操作系统保护。truststore
Resources
从 Spring AMQP 1.5 版开始,您可以直接在工厂 bean 上设置这些属性。如果同时sslPropertiesLocation
提供了离散属性 和 ,则后者中的属性将覆盖离散值。
从 2.0 版开始,默认情况下会验证服务器证书,因为它更安全。如果您出于某种原因希望跳过此验证,请将工厂 bean 的skipServerCertificateValidation 属性设置为true . 从 2.1 版开始,RabbitConnectionFactoryBean now 默认调用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:ConnectionFactory
lookupKey
SimpleRoutingConnectionFactory
lookupKey
SimpleResourceHolder
SimpleRoutingConnectionFactory
<bean id="connectionFactory"
class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
<property name="targetConnectionFactories">
<map>
<entry key="#{connectionFactory1.virtualHost}" ref="connectionFactory1"/>
<entry key="#{connectionFactory2.virtualHost}" ref="connectionFactory2"/>
</map>
</property>
</bean>
<rabbit:template id="template" connection-factory="connectionFactory" />
public class MyService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void service(String vHost, String payload) {
SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);
rabbitTemplate.convertAndSend(payload);
SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());
}
}
使用后解绑资源很重要。有关更多信息,请参阅.AbstractRoutingConnectionFactory
从 1.4 版开始,RabbitTemplate
支持 SpELsendConnectionFactorySelectorExpression
和receiveConnectionFactorySelectorExpression
属性,它们在每个 AMQP 协议交互操作(、、、或)上进行评估send
,解析sendAndReceive
为提供的值。您可以使用 bean 引用,例如在表达式中。对于操作,要发送的消息是根评估对象。对于操作,是根评估对象。receive
receiveAndReply
lookupKey
AbstractRoutingConnectionFactory
@vHostResolver.getVHost(#root)
send
receive
queueName
路由算法如下:如果选择器表达式是null
or 被评估为null
或提供ConnectionFactory
的不是 的实例AbstractRoutingConnectionFactory
,则一切都像以前一样工作,依赖于提供的ConnectionFactory
实现。如果评估结果不是 ,也会发生同样的情况null
,但没有目标ConnectionFactory
,lookupKey
并且AbstractRoutingConnectionFactory
配置了lenientFallback = true
。在 的情况下AbstractRoutingConnectionFactory
,它确实回退到routing
基于 的实现determineCurrentLookupKey()
。但是,如果lenientFallback = false
,IllegalStateException
则抛出 an。
命名空间支持还提供了组件的send-connection-factory-selector-expression
和receive-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
的一般值。confirms
returns
如果您遇到您要检查的某些消息确认/返回而其他您不知道的情况,这可能会很有用。例如:
@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
它使用队列名称作为查找键。RoutingConnectionFactory
SimpleMessageListenerContainer
由于这个原因(使用队列名称进行查找),LocalizedQueueConnectionFactory 只能在容器配置为侦听单个队列时使用。
|
必须在每个节点上启用 RabbitMQ 管理插件。 |
此连接工厂适用于长期连接,例如SimpleMessageListenerContainer . 它不适用于短连接使用,例如,RabbitTemplate 因为在建立连接之前调用 REST API 的开销。还有,对于发布操作来说,队列是未知的,反正消息是发布给所有集群成员的,所以查找节点的逻辑意义不大。
|
以下示例配置显示了如何配置工厂:
@Autowired
private ConfigurationProperties props;
@Bean
public CachingConnectionFactory defaultConnectionFactory() {
CachingConnectionFactory cf = new CachingConnectionFactory();
cf.setAddresses(this.props.getAddresses());
cf.setUsername(this.props.getUsername());
cf.setPassword(this.props.getPassword());
cf.setVirtualHost(this.props.getVirtualHost());
return cf;
}
@Bean
public LocalizedQueueConnectionFactory queueAffinityCF(
@Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) {
return new LocalizedQueueConnectionFactory(defaultCF,
StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()),
StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()),
StringUtils.commaDelimitedListToStringArray(this.props.getNodes()),
this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(),
false, null);
}
请注意,前三个参数是addresses
、adminUris
和的数组nodes
。这些是定位的,当容器尝试连接到队列时,它使用管理 API 来确定哪个节点是队列的引导节点,并连接到与该节点相同的数组位置中的地址。
出版商确认并退货
CachingConnectionFactory
通过将属性设置publisherConfirmType
为ConfirmType.CORRELATED
并将publisherReturns
属性设置为“真”来支持确认(具有相关性)和返回的消息。
设置这些选项后,Channel
工厂创建的实例被包装在一个PublisherCallbackChannel
中,用于方便回调。当获得这样的通道时,客户端可以PublisherCallbackChannel.Listener
向Channel
. 该PublisherCallbackChannel
实现包含将确认或返回路由到适当侦听器的逻辑。这些功能将在以下部分中进一步解释。
另请参阅Scoped OperationssimplePublisherConfirms
。
有关更多背景信息,请参阅 RabbitMQ 团队的博客文章,标题为Introducing Publisher Confirms。 |
连接和通道监听器
连接工厂支持注册ConnectionListener
和ChannelListener
实现。这允许您接收连接和通道相关事件的通知。(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
注入
自定义。CachingConnectionFactory
closeExceptionLogger
另请参阅消费者事件。
运行时缓存属性
从 1.6 版本开始,CachingConnectionFactory
现在通过该getCacheProperties()
方法提供缓存统计信息。这些统计信息可用于调整缓存以在生产中对其进行优化。例如,高水位标记可用于确定是否应增加缓存大小。如果它等于缓存大小,您可能需要考虑进一步增加。下表描述了这些CacheMode.CHANNEL
属性:
财产 | 意义 |
---|---|
连接名称 |
生成的连接的名称 |
通道缓存大小 |
当前配置的允许空闲的最大通道数。 |
本地端口 |
连接的本地端口(如果可用)。这可用于关联 RabbitMQ Admin UI 上的连接和通道。 |
idleChannelsTx |
当前空闲(缓存)的事务通道数。 |
idleChannelsNotTx |
当前空闲(缓存)的非事务性通道数。 |
idleChannelsTxHighWater |
并发空闲(缓存)的最大事务通道数。 |
idleChannelsNotTxHighWater |
非事务性通道的最大数量同时空闲(缓存)。 |
下表描述了这些CacheMode.CONNECTION
属性:
财产 | 意义 |
---|---|
连接名称:<本地端口> |
生成的连接的名称 |
开放连接 |
表示与代理的连接的连接对象的数量。 |
通道缓存大小 |
当前配置的允许空闲的最大通道数。 |
连接缓存大小 |
当前配置的允许空闲的最大连接数。 |
空闲连接 |
当前空闲的连接数。 |
idleConnectionsHighWater |
并发空闲的最大连接数。 |
idleChannelsTx:<本地端口> |
此连接当前空闲(缓存)的事务通道数。您可以使用 |
idleChannelsNotTx:<localPort> |
此连接当前空闲(缓存)的非事务通道数。属性名称的 |
idleChannelsTxHighWater:<localPort> |
并发空闲(缓存)的最大事务通道数。属性名称的 localPort 部分可用于与 RabbitMQ Admin UI 上的连接和通道相关联。 |
idleChannelsNotTxHighWater:<localPort> |
最大数量的非事务性通道同时空闲(缓存)。您可以使用 |
cacheMode
属性 (或CHANNEL
)CONNECTION
也包括在内。
RabbitMQ 自动连接/拓扑恢复
从 Spring AMQP 的第一个版本开始,该框架提供了自己的连接和通道恢复,以防代理失败。此外,正如配置代理中所讨论的,当重新建立连接时,RabbitAdmin
重新声明任何基础设施 bean(队列和其他)。因此,它不依赖于库现在提供的自动恢复amqp-client
。Spring AMQP 现在使用 的4.0.x
版本amqp-client
,默认启用自动恢复。如果您愿意,Spring AMQP 仍然可以使用自己的恢复机制,在客户端禁用它,(通过将automaticRecoveryEnabled
底层的属性设置RabbitMQ connectionFactory
为false
)。但是,该框架与启用的自动恢复完全兼容。这意味着您在代码中创建的任何消费者(可能通过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
)。
另请参阅异步兔子模板。
添加重试功能
从 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>
以下示例使用@Configuration
Java 中的注解:
@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 丢弃。为了成功发布,您可以收到异步确认,如相关发布者确认和返回中所述。考虑两种失败情况:
-
发布到交换,但没有匹配的目标队列。
-
发布到不存在的交易所。
第一种情况由发布者返回涵盖,如相关发布者确认和返回中所述。
对于第二种情况,消息被丢弃并且不生成返回。底层通道异常关闭。默认情况下,会记录此异常,但您可以向 注册一个ChannelListener
以CachingConnectionFactory
获取此类事件的通知。以下示例显示了如何添加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
属性必须设置为true
或mandatory-expression
必须true
为特定消息计算。此功能需要将CachingConnectionFactory
其publisherReturns
属性设置为的 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
。另请参阅回复超时。
对于发布者确认(也称为发布者确认),模板需要将CachingConnectionFactory
其publisherConfirm
属性设置为 的ConfirmType.CORRELATED
。RabbitTemplate.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:ack
和reason
(对于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
来构造管理员。RabbitTemplate
invoke
如果模板操作已经在现有事务的范围内执行,那么前面的讨论是没有意义的——例如,当在事务侦听器容器线程上运行并在事务模板上执行操作时。在这种情况下,操作在该通道上执行并在线程返回容器时提交。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 对象(forack 和nack 实例)是 Rabbit 客户端回调,而不是模板回调。
|
以下示例日志ack
和nack
实例:
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
将线程的通道传输到另一个线程。第一个方法返回一个上下文,该上下文被传递给调用第二个方法的第二个线程。一个线程可以有一个非事务性通道或一个事务性通道(或其中一个)绑定到它;除非您使用两个连接工厂,否则您不能单独传输它们。一个例子如下:prepareContextSwitch
switchContext
@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 客户端库所需的格式之间进行转换。默认情况下,消息负载由提供的实例的消息转换器转换。或者,您可以注入自定义RabbitTemplate
org.springframework.messaging.Message
spring-messaging
Message<?>
Message<?>
Message
Message
RabbitTemplate
MessagingMessageConverter
使用其他一些有效负载转换器,如以下示例所示:
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));
如果模板上同时设置了exchange
和routingKey
属性,则可以使用只接受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 由MessageBuilder
and提供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
。这使发件人可以将确认(ack
或nack
)与发送的消息相关联。
从 1.6.7 版本开始,CorrelationAwareMessagePostProcessor
引入了接口,允许在消息转换后修改相关数据。以下示例显示了如何使用它:
Message postProcessMessage(Message message, Correlation correlation);
在 2.0 版中,此接口已弃用。该方法已移至MessagePostProcessor
默认实现,该实现委托给postProcessMessage(Message message)
.
同样从 1.6.7 版本开始,提供了一个新的回调接口CorrelationDataPostProcessor
。MessagePostProcessor
这在所有实例之后调用(在send()
方法中提供以及在 中提供的那些setBeforePublishPostProcessors()
)。实现可以更新或替换方法中提供的相关数据send()
(如果有)。和Message
original 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
. 有关更多信息,请参阅从Message
WithRabbitTemplate
转换。
与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,这应该可以让消费者在大多数常见场景中保持忙碌,从而提高吞吐量。 然而,在某些情况下预取值应该很低:
此外,对于低容量消息和多个消费者(包括单个侦听器容器实例中的并发),您可能希望减少预取以在消费者之间获得更均匀的消息分布。 请参阅消息侦听器容器配置。 有关预取的更多背景信息,请参阅这篇关于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()
以根据消息动态选择不同的方法。该方法有两个参数,originalMessage
和extractedMessage
,后者是任何转换的结果。默认情况下,aSimpleMessageConverter
已配置。SimpleMessageConverter
有关其他可用转换器的更多信息和信息,请参阅。
从版本 1.4.2 开始,原始消息具有consumerQueue
和consumerTag
属性,可用于确定从哪个队列接收消息。
从 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)
而是引入了新版本。新方法帮助侦听器获取Channel
和Message
参数做更多事情,例如channel.basicReject(long, boolean)
在手动确认模式下调用。以下清单显示了最基本的示例:
public class ExtendedListenerAdapter extends MessageListenerAdapter {
@Override
protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) {
return new Object[]{extractedMessage, channel, message};
}
}
现在您可以ExtendedListenerAdapter
像MessageListenerAdapter
需要接收“频道”和“消息”一样进行配置。listener 的参数应设置为buildListenerArguments(Object, Channel, Message)
返回,如下面的 listener 示例所示:
public void handleMessage(Object object, Channel channel, Message message) throws IOException {
...
}
容器
现在您已经看到了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" />
在这种情况下,队列和交换由 声明containerAdmin
,auto-startup="false"
因此在上下文初始化期间不声明元素。此外,出于同样的原因,容器没有启动。当容器稍后启动时,它使用它的引用containerAdmin
来声明元素。
批量消息
批处理消息(由生产者创建)由侦听器容器自动取消批处理(使用springBatchFormat
消息头)。拒绝批次中的任何消息会导致整个批次被拒绝。有关批处理的更多信息,请参阅批处理。
从 2.2 版开始,SimpleMessageListenerContainer
可用于在消费者端(生产者发送离散消息)创建批处理。
设置容器属性consumerBatchEnabled
以启用此功能。
deBatchingEnabled
也必须为真,以便容器负责处理这两种类型的批次。实施BatchMessageListener
或ChannelAwareBatchMessageListener
何时consumerBatchEnabled
为真。从版本 2.2.7 开始,SimpleMessageListenerContainer
和DirectMessageListenerContainer
can debatch producer 创建的批次为List<Message>
. 有关将此功能与@RabbitListener
.
消费者活动
每当侦听器(消费者)遇到某种故障时,容器就会发布应用程序事件。该事件ListenerContainerConsumerFailedEvent
具有以下属性:
-
container
:消费者遇到问题的侦听器容器。 -
reason
: 失败的文字原因。 -
fatal
:一个布尔值,指示失败是否是致命的。对于非致命异常,容器会尝试根据recoveryInterval
orrecoveryBackoff
(for theSimpleMessageListenerContainer
) 或 themonitorInterval
(for theDirectMessageListenerContainer
) 重启消费者。 -
throwable
:Throwable
被抓了。
这些事件可以通过实现来使用ApplicationListener<ListenerContainerConsumerFailedEvent>
。
concurrentConsumers 当大于 1
时,系统范围的事件(例如连接失败)由所有消费者发布。 |
如果消费者失败,因为如果它的队列被独占使用,默认情况下,以及发布事件,WARN
都会发出日志。要更改此日志记录行为,ConditionalExceptionLogger
请在SimpleMessageListenerContainer
实例的exclusiveConsumerExceptionLogger
属性中提供自定义。另请参阅记录通道关闭事件。
致命错误始终记录在该ERROR
级别。这是它不可修改的。
在容器生命周期的不同阶段发布了其他几个事件:
-
AsyncConsumerStartedEvent
: 当消费者启动时。 -
AsyncConsumerRestartedEvent
:SimpleMessageListenerContainer
仅当消费者在失败后重新启动时。 -
AsyncConsumerTerminatedEvent
: 当消费者正常停止时。 -
AsyncConsumerStoppedEvent
:SimpleMessageListenerContainer
仅当消费者停止时。 -
ConsumeOkEvent
:consumeOk
从代理接收到 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
为. String
for 的表达式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 开始,您可以将RetryTemplate
and添加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
,其中m
是concurrentConsumers
属性,n
是maxConcurrentConsumers
属性。
在任何一种情况下,此设置都会覆盖出厂设置。以前,如果您有需要不同并发的侦听器,则必须定义不同的容器工厂。
注释还允许通过和(自 2.2 起)注释属性覆盖工厂autoStartup
和属性。为每个执行程序使用不同的执行程序可能有助于在日志和线程转储中识别与每个侦听器关联的线程。taskExecutor
autoStartup
executor
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.Message
AMQP 消息转换而来。 -
@Header
-带注释的方法参数以提取特定的标头值,包括标准 AMQP 标头。 -
@Headers
-annotated 参数,也必须分配给以java.util.Map
访问所有标题。 -
转换后的有效载荷
不是受支持类型之一的未注释元素(即
Message
、MessageProperties
和)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
和对,其中可以省略其中一个部分。有效值如下:routingKey
exchange/routingKey
-
thing1/thing2
:replyTo
交易所和routingKey
.thing1/
:replyTo
交换和默认(空)routingKey
。thing2
or/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时,这些属性 (replyContentType
和converterWinsContentType
) 不适用。在第一种情况下,不涉及转换;只需设置消息属性。在第二种情况下,行为是使用消息头控制的:Message
Message<?>
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
方法。您应该了解,系统必须能够根据负载类型识别唯一的方法。检查类型是否可分配给没有注释或使用注释进行注释的单个参数。请注意,应用相同的方法签名,如方法级别中所述(如前所述)。Thing2
Cat
Hat
@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
注解有两个新属性:errorHandler
和returnExceptions
.
默认情况下未配置这些。
您可以使用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
工厂创建的容器中的容器属性(除非consumerBatchEnabled
是true
- 见下文)。实际上,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
参数,通常在使用MANUAL
ack 模式时使用。这对于第三个示例不是很有用,因为您无权访问该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 ) 方法,从而异步发送回复。@RabbitHandler
ListenableFuture<?>
Mono<?>
必须配置监听容器工厂AcknowledgeMode.MANUAL ,使消费者线程不会确认消息;相反,异步完成将在异步操作完成时确认或确认消息。当异步结果完成并出现错误时,消息是否重新排队取决于抛出的异常类型、容器配置和容器错误处理程序。默认情况下,消息将被重新排队,除非容器的defaultRequeueRejected 属性设置为false (true 默认情况下)。如果异步结果以 完成AmqpRejectAndDontRequeueException ,则消息将不会重新排队。如果容器的defaultRequeueRejected 属性是false ,您可以通过将未来的异常设置为ImmediateRequeueException 并且消息将被重新排队。如果在侦听器方法中发生阻止创建异步结果对象的异常,则必须捕获该异常并返回适当的返回对象,该对象将导致消息被确认或重新排队。
|
从版本 2.2.21、2.3.13、2.4.1 开始,AcknowledgeMode
将自动设置MANUAL
检测到异步返回类型的时间。此外,具有致命异常的传入消息将被单独否定确认,以前任何先前未确认的消息也被否定确认。
线程和异步消费者
异步消费者涉及许多不同的线程。
TaskExecutor
中配置的线程用于在传递新消息时SimpleMessageListenerContainer
调用。如果未配置,则使用 a。如果使用池执行器,则需要确保池大小足以处理配置的并发。使用,直接在线程上调用。在这种情况下,用于监视消费者的任务。MessageListener
RabbitMQ Client
SimpleAsyncTaskExecutor
DirectMessageListenerContainer
MessageListener
RabbitMQ Client
taskExecutor
使用默认值SimpleAsyncTaskExecutor 时,对于调用侦听器的线程,侦听器容器beanName 在threadNamePrefix . 这对于日志分析很有用。我们通常建议始终在日志附加程序配置中包含线程名称。当 aTaskExecutor 通过容器上的属性专门提供时taskExecutor ,它按原样使用,无需修改。建议您使用类似的技术来命名由自定义TaskExecutor bean 定义创建的线程,以帮助在日志消息中识别线程。
|
中的Executor
配置在创建连接时CachingConnectionFactory
传入RabbitMQ Client
,它的线程用于向监听容器传递新消息。如果未配置,则客户端使用内部线程池执行器,(在撰写本文时)Runtime.getRuntime().availableProcessors() * 2
每个连接的池大小为 。
如果您有大量工厂或正在使用CacheMode.CONNECTION
,您可能希望考虑使用ThreadPoolTaskExecutor
具有足够线程的共享来满足您的工作量。
使用DirectMessageListenerContainer ,您需要确保为连接工厂配置了一个任务执行器,该任务执行器具有足够的线程来支持您在使用该工厂的所有侦听器容器中所需的并发性。默认池大小(在撰写本文时)为Runtime.getRuntime().availableProcessors() * 2 .
|
使用RabbitMQ client
aThreadFactory
为低级 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
——一个普通的监听器,或者一个缩小到只接收这个特定事件的监听器。您也可以使用@EventListener
Spring Framework 4.2 中引入的 ,。
下面的示例将@RabbitListener
and组合@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
:(简单侦听器的队列名称或配置的队列名称列表何时consumerBatchEnabled
是true
- 因为批处理可能包含来自多个队列的消息) -
result
:success
或failure
-
exception
:none
或ListenerExecutionFailedException
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 序列化的情况下传递丰富领域对象内容的替代方案。
对于所有其他内容类型,直接SimpleMessageConverter
将Message
正文内容作为字节数组返回。
有关重要信息,请参阅Java 反序列化。
SerializerMessageConverter
这个转换器类似于,SimpleMessageConverter
除了它可以配置其他 Spring Framework
Serializer
和Deserializer
实现进行application/x-java-serialized-object
转换。
有关重要信息,请参阅Java 反序列化。
Jackson2JsonMessageConverter
本节介绍使用Jackson2JsonMessageConverter
与Message
. 它有以下几个部分:
转换为Message
上一节提到过,一般不推荐依赖Java序列化。JSON(JavaScript Object Notation)是一种相当常见的替代方案,它更灵活且可跨不同的语言和平台移植。转换器可以在任何RabbitTemplate
实例上配置以覆盖其SimpleMessageConverter
默认使用。使用2.x 库Jackson2JsonMessageConverter
。com.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
消息属性;这也用于编码。添加了一个新方法:supportedMediaType
charset
setSupportedMediaType
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
. 这让转换器转换为目标方法的参数类型。这仅适用于有一个没有注释的参数或一个带有注释的参数@Payload
。Message
在分析过程中忽略类型参数。
默认情况下,推断的类型信息将覆盖TypeId 发送系统创建的入站和相关标头。这使接收系统可以自动转换为不同的域对象。这仅适用于参数类型是具体的(不是抽象的或接口)或来自java.util
包的情况。在所有其他情况下,使用TypeId 和 相关的标头。在某些情况下,您可能希望覆盖默认行为并始终使用这些TypeId 信息。例如,假设您有一个@RabbitListener 带Thing1 参数的 a,但消息包含 aThing2 的子类Thing1 (它是具体的)。推断的类型将不正确。要处理这种情况,请将TypePrecedence 属性设置Jackson2JsonMessageConverter 为TYPE_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[] Jackson2JsonMessageConverter contentType application/json text/x-json Jackson2JsonMessageConverter |
@RabbitListener
public void thing1(Thing1 thing1) {...}
@RabbitListener
public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<Foo> message) {...}
@RabbitListener
public void thing1(Thing1 thing1, String bar) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<?> message) {...}
在上述清单的前四种情况下,转换器尝试转换为Thing1
类型。第五个示例无效,因为我们无法确定哪个参数应该接收消息负载。对于第六个示例,由于泛型类型是 a ,因此应用了 Jackson 默认值WildcardType
。
但是,您可以创建自定义转换器并使用targetMethod
message 属性来决定将 JSON 转换为哪种类型。
@RabbitListener 只有在方法级别声明注解
时,才能实现这种类型推断。使用 class-level @RabbitListener ,转换后的类型用于选择@RabbitHandler 要调用的方法。出于这个原因,基础设施提供了targetObject message 属性,您可以在自定义转换器中使用它来确定类型。
|
从版本 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 版开始,Jackson2JsonMessageConverter
implementsSmartMessageConverter
允许它与带有参数的新RabbitTemplate
方法一起使用ParameterizedTypeReference
。这允许转换复杂的泛型类型,如下例所示:
Thing1<Thing2<Cat, Hat>> thing1 =
rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
从 2.1 版开始,AbstractJsonMessageConverter 该类已被删除。它不再是Jackson2JsonMessageConverter . 它已被AbstractJackson2MessageConverter .
|
MarshallingMessageConverter
另一个选择是MarshallingMessageConverter
. 它委托给 Spring OXM 库的实现Marshaller
和Unmarshaller
策略接口。您可以在此处阅读有关该库的更多信息。在配置方面,最常见的是只提供构造函数参数,因为大多数实现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 库Jackson2XmlMessageConverter
。com.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 对象时可能存在漏洞。 如果您接受来自不受信任来源的带有 默认情况下,允许列表为空,这意味着所有类都被反序列化。 您可以设置模式列表,例如、或。 按顺序检查模式,直到找到匹配项。如果没有匹配, 您可以使用 |
消息属性转换器
MessagePropertiesConverter
策略接口用于 Rabbit ClientBasicProperties
和 Spring AMQP之间的转换MessageProperties
。默认实现 ( DefaultMessagePropertiesConverter
) 通常足以满足大多数目的,但如果需要,您可以实现自己的。当大小不大于字节时,默认属性转换器将BasicProperties
类型的元素转换LongString
为实例。较大的实例不会被转换(参见下一段)。可以使用构造函数参数覆盖此限制。String
1024
LongString
从版本 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
. 以前,在BasicProperties
RabbitMQ 客户端使用的转换和转换时,执行了不必要的byte[] <→ String
转换,因为MessageProperties.correlationId
is 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
,ZipPostProcessor
并DeflaterPostProcessor
在发送前压缩消息, 和GUnzipPostProcessor
,UnzipPostProcessor
并InflaterPostProcessor
解压缩收到的消息。
从版本 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
. 有关更多信息,请参阅从Message
WithRabbitTemplate
转换。
从 2.1 版开始,您可以配置RabbitTemplate
选项noLocalReplyConsumer
来控制noLocal
回复消费者的标志。这是false
默认设置。
回复超时
默认情况下,发送和接收方法在五秒后超时并返回 null。replyTimeout
您可以通过设置属性来修改此行为。从版本 1.5 开始,如果将mandatory
属性设置为true
(或特定消息的mandatory-expression
计算结果true
为),如果消息无法传递到队列,AmqpMessageReturnedException
则会抛出 an。此异常具有returnedMessage
、replyCode
和replyText
属性,以及用于发送的exchange
和。routingKey
此功能使用发布商退货。您可以通过设置publisherReturns 为true on来启用它CachingConnectionFactory (请参阅Publisher Confirms and Returns)。此外,您一定没有ReturnCallback 在RabbitTemplate .
|
从版本 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
代替,让消费者被重用。模板仍然负责关联回复,因此不存在延迟回复发送给其他发件人的危险。如果要恢复到以前的行为,请将useDirectReplyToContainer
(direct-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-factory
和message-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-address and reply-queue (或上的queues 属性<listener-container> )必须在逻辑上引用相同的队列。
|
使用此配置,aSimpleListenerContainer
用于接收回复,RabbitTemplate
而MessageListener
. 在使用命名空间元素定义模板时<rabbit:template/>
,如前面的示例所示,解析器将模板中的容器和连线定义为侦听器。
当模板不使用固定的replyQueue (或使用直接回复 - 请参阅RabbitMQ 直接回复)时,不需要侦听器容器。使用 RabbitMQ 3.4.0 或更高版本时,直接reply-to 是首选机制。
|
如果您定义 your RabbitTemplate
as a<bean/>
或使用@Configuration
类将其定义为 an @Bean
or 当您以编程方式创建模板时,您需要自己定义和连接回复侦听器容器。如果您不这样做,模板将永远不会收到回复并最终超时并返回 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
. 有关更多信息,请参阅从Message
WithRabbitTemplate
转换。
使用 AMQP 进行 Spring 远程处理
此功能已弃用,将在 3.0 中删除。它已被设置为 true的处理异常并在发送端配置 a取代了很长时间。有关详细信息,请参阅处理异常。
returnExceptions RemoteInvocationAwareMessageConverterAdapter |
Spring 框架具有通用的远程处理能力,允许使用各种传输的远程过程调用 (RPC) 。Spring-AMQP 支持类似的机制,AmqpProxyFactoryBean
在客户端和AmqpInvokerServiceExporter
服务器上都有 a。这提供了基于 AMQP 的 RPC。在客户端,如前所述RabbitTemplate
使用a 。在服务器端,调用者(配置为 a )接收消息,调用配置的服务,并使用入站消息的信息返回回复。MessageListener
replyTo
您可以将客户端工厂 bean 注入任何 bean(通过使用它的serviceInterface
)。然后,客户端可以调用代理上的方法,从而通过 AMQP 远程执行。
对于默认MessageConverter 实例,方法参数和返回值必须是Serializable .
|
在服务器端,AmqpInvokerServiceExporter
两者都有AmqpTemplate
和MessageConverter
属性。MessageConverter
目前,未使用模板。如果您需要提供自定义消息转换器,则应通过设置messageConverter
属性来提供。在客户端,您可以将自定义消息转换器添加到,使用其属性AmqpTemplate
提供给。AmqpProxyFactoryBean
amqpTemplate
以下清单显示了示例客户端和服务器配置:
<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_NAME
、
QUEUE_MESSAGE_COUNT
和QUEUE_CONSUMER_COUNT
) 中的常量。RabbitMQ REST API在QueueInfo
对象中提供了更多信息。
no-argdeclareQueue()
方法在代理上定义了一个队列,其名称是自动生成的。此自动生成队列的附加属性是exclusive=true
、autoDelete=true
和durable=false
。
该declareQueue(Queue queue)
方法接受一个Queue
对象并返回声明的队列的名称。如果name
提供的属性为Queue
空String
,则代理使用生成的名称声明队列。该名称将返回给调用者。该名称也被添加actualName
到Queue
. 您只能通过RabbitAdmin
直接调用以编程方式使用此功能。当管理员在应用程序上下文中以声明方式定义队列时使用自动声明时,您可以将 name 属性设置为""
(空字符串)。然后代理创建名称。从 2.1 版开始,监听器容器可以使用这种类型的队列。有关更多信息,请参阅容器和代理命名队列。
这与AnonymousQueue
框架生成唯一的 ( UUID
) 名称并设置durable
为
false
and exclusive
, autoDelete
to的情况形成对比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"/>
您可以同时提供id 和name 属性。这使您可以通过独立于队列名称的 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()
Queue
client-local
RabbitMQ 代理不允许声明参数不匹配的队列。例如,如果 aqueue 已经存在且没有time to live 参数,并且您尝试使用 (for example) 声明它,key="x-message-ttl" value="100" 则会引发异常。
|
默认情况下,RabbitAdmin
当发生任何异常时,立即停止处理所有声明。这可能会导致下游问题,例如侦听器容器无法初始化,因为未声明另一个队列(在错误队列之后定义)。
可以通过将ignore-declaration-exceptions
属性设置true
为RabbitAdmin
实例来修改此行为。此选项指示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 (如果应用程序上下文中存在)在Exchange
Broker 上正确配置了这样的标志。RabbitAdmin
如果internal
标志true
用于交换,RabbitMQ 不会让客户端使用交换。这对于死信交换或交换到交换绑定很有用,您不希望发布者直接使用交换。
要了解如何使用 Java 配置 AMQP 基础架构,请查看 Stock 示例应用程序,其中有@Configuration
类AbstractStockRabbitConfiguration
,而类又具有
RabbitClientConfiguration
子RabbitServerConfiguration
类。以下清单显示了 的代码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
类继承链的结束。最终结果是,TopicExchange
并Queue
在应用程序启动时向代理声明。在服务器配置中没有绑定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();
}
有关更多信息,org.springframework.amqp.core.QueueBuilder
请参阅 Javadoc 。org.springframework.amqp.core.ExchangeBuilder
从 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
您可以将对象集合(Queue
、Exchange
和Binding
)包装在Declarables
对象中。检测应用程序上下文中的RabbitAdmin
此类 bean(以及离散Declarable
bean),并在建立连接时(最初和连接失败后)在代理上声明包含的对象。以下示例显示了如何执行此操作:
@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
有一个新属性explicitDeclarationsOnly
(false
默认情况下);当设置为 时true
,管理员将仅声明明确配置为由该管理员声明的 bean。
从 1.2 版本开始,您可以有条件地声明这些元素。当应用程序连接到多个代理并需要指定应使用哪些代理声明特定元素时,这特别有用。 |
代表这些元素的类实现Declarable
了 ,它有两个方法:shouldDeclare()
和getDeclaringAdmins()
。使用RabbitAdmin
这些方法来确定特定实例是否应该实际处理其Connection
.
这些属性可用作命名空间中的属性,如以下示例所示:
<rabbit:admin id="admin1" connection-factory="CF1" />
<rabbit:admin id="admin2" connection-factory="CF2" />
<rabbit:admin id="admin3" connection-factory="CF3" explicit-declarations-only="true" />
<rabbit:queue id="declaredByAdmin1AndAdmin2Implicitly" />
<rabbit:queue id="declaredByAdmin1AndAdmin2" declared-by="admin1, admin2" />
<rabbit:queue id="declaredByAdmin1Only" declared-by="admin1" />
<rabbit:queue id="notDeclaredByAllExceptAdmin3" auto-declare="false" />
<rabbit:direct-exchange name="direct" declared-by="admin1, admin2">
<rabbit:bindings>
<rabbit:binding key="foo" queue="bar"/>
</rabbit:bindings>
</rabbit:direct-exchange>
默认情况下,auto-declare 属性是true and,如果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;
}
关于id
和name
属性的注释
和元素上的name
属性反映了代理中实体的名称。对于队列,如果省略 ,则会创建一个匿名队列(请参阅 参考资料)。<rabbit:queue/>
<rabbit:exchange/>
name
AnonymousQueue
在 2.0 之前的版本中,name
也注册为 bean 名称别名(类似于name
on<bean/>
元素)。
这导致了两个问题:
-
它阻止了同名队列和交换的声明。
-
如果别名包含 SpEL 表达式 (
#{…}
),则不会解析该别名。
从 2.0 版开始,如果您使用 anid
和aname
属性声明这些元素之一,则该名称不再声明为 bean 名称别名。如果你想声明一个队列并与之交换name
,你必须提供一个id
.
如果元素只有一个name
属性,则没有变化。bean 仍然可以被引用name
——例如,在绑定声明中。但是,如果名称中包含 SpEL,您仍然不能引用它——您必须提供一个id
以供参考。
AnonymousQueue
通常,当您需要一个唯一命名的、排他的、自动删除的队列时,我们建议您使用AnonymousQueue
代替代理定义的队列名称(使用""
作为Queue
名称会导致代理生成队列名称)。
这是因为:
-
队列实际上是在建立与代理的连接时声明的。这是在创建 bean 并将其连接在一起很久之后。使用队列的 Bean 需要知道它的名称。事实上,当应用程序启动时,代理甚至可能没有运行。
-
如果与代理的连接由于某种原因丢失,管理员会重新声明
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
交换类型(Direct
、Fanout
等)来设置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
下可能会抛出实例。、RabbitTemplate
和SimpleMessageListenerContainer
其他 Spring AMQP 组件捕获这些异常并将它们转换为AmqpException
层次结构中的异常之一。这些在“org.springframework.amqp”包中定义,AmqpException
是层次结构的基础。
当监听器抛出异常时,它被包裹在一个ListenerExecutionFailedException
. 通常消息被代理拒绝并重新排队。设置defaultRequeueRejected
为false
会导致消息被丢弃(或路由到死信交换)。正如消息侦听器和异步案例中所讨论的,侦听器可以抛出一个AmqpRejectAndDontRequeueException
(或ImmediateRequeueAmqpException
)来有条件地控制此行为。
但是,存在一类错误,侦听器无法控制行为。当遇到无法转换的消息(例如,无效的content_encoding
标头)时,会在消息到达用户代码之前引发一些异常。defaultRequeueRejected
设置为true
(默认)(或抛出),此类消息将ImmediateRequeueAmqpException
一遍又一遍地重新传递。在 1.3.2 版本之前,用户需要编写自定义ErrorHandler
,如异常处理中所述,以避免这种情况。
从版本 1.3.2 开始,默认ErrorHandler
值现在是ConditionalRejectingErrorHandler
拒绝(并且不重新排队)因不可恢复错误而失败的消息。具体来说,它拒绝失败并出现以下错误的消息:
-
o.s.amqp…MessageConversionException
: 可以在使用 . 转换传入消息有效负载时抛出MessageConverter
。 -
o.s.messaging…MessageConversionException
@RabbitListener
:如果在映射到方法时需要额外的转换,则可以由转换服务抛出。 -
o.s.messaging…MethodArgumentNotValidException
@Valid
: 如果在侦听器中使用验证(例如,)并且验证失败,则可以抛出。 -
o.s.messaging…MethodArgumentTypeMismatchException
:如果入站消息被转换为对目标方法不正确的类型,则可以抛出。例如,参数被声明为Message<Foo>
但Message<Bar>
被接收。 -
java.lang.NoSuchMethodException
:在 1.6.3 版本中添加。 -
java.lang.ClassCastException
:在 1.6.3 版本中添加。
您可以使用 a 配置此错误处理程序的实例,FatalExceptionStrategy
以便用户可以为有条件的消息拒绝提供自己的规则 - 例如,对BinaryExceptionClassifier
Spring 重试(消息侦听器和异步案例)的委托实现。此外,ListenerExecutionFailedException
now 有一个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
属性来恢复到以前的行为。ConditionalRejectingErrorHandler
false
从版本 2.1.9 开始,具有这些致命异常的消息将被拒绝并且默认情况下不会重新排队,即使容器确认模式为手动也是如此。这些异常通常发生在调用侦听器之前,因此侦听器没有机会确认或确认消息,因此它以未确认状态保留在队列中。要恢复到以前的行为,请将 上的rejectManual 属性设置ConditionalRejectingErrorHandler 为false 。
|
4.1.16。交易
Spring Rabbit 框架支持同步和异步用例中的自动事务管理,具有许多可以声明式选择的不同语义,正如 Spring 事务的现有用户所熟悉的那样。这使得许多(如果不是最常见的)消息传递模式易于实现。
有两种方法可以向框架发送所需的事务语义信号。在RabbitTemplate
andSimpleMessageListenerContainer
中,都有一个标志channelTransacted
if 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 提供时不一致。在前一种情况下,应用了正常的重新排队逻辑(AmqpRejectAndDontRequeueException 或defaultRequeueRejected=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
指定一个SMLC
或DMLC
。命名空间未公开某些属性。这些由N/A
属性指示。
属性(属性) | 描述 | SMLC | DMLC | 标准液相色谱法 | ||||||
---|---|---|---|---|---|---|---|---|---|---|
|
设置时 |
|||||||||
|
|
|||||||||
|
应用于侦听器执行的 AOP Advice 数组。这可用于应用额外的横切关注点,例如在代理死亡的情况下自动重试。 |
|||||||||
|
||||||||||
设置为 |
||||||||||
|
当设置为
|
|||||||||
|
标志指示容器应该在执行时启动 |
|||||||||
|
当与 |
|||||||||
|
分批消息时使用的策略。默认 |
|||||||||
|
布尔标志,表示应在事务中确认所有消息(手动或自动)。 |
|||||||||
|
|
|||||||||
|
最初为每个侦听器启动的并发使用者数量。请参阅侦听器并发。 |
|||||||||
|
对 |
|||||||||
|
在考虑启动新的消费者时,消费者接收到的连续消息的最小数量,没有发生接收超时。也受“batchSize”的影响。请参阅侦听器并发。默认值:10。 |
|||||||||
|
在考虑停止消费者之前,消费者必须经历的最小接收超时次数。也受“batchSize”的影响。请参阅侦听器并发。默认值:10。 |
|||||||||
|
如果 |
|||||||||
|
|
|||||||||
|
等待消费者线程启动的时间(以毫秒为单位)。如果此时间过去,则会写入错误日志。可能发生这种情况的一个例子是,如果配置 请参阅线程和异步消费者。默认值:60000(一分钟)。 |
|||||||||
|
设置ConsumerTagStrategy的实现,为每个消费者创建一个(唯一的)标签。 |
|||||||||
|
为每个配置的队列创建的消费者数量。请参阅侦听器并发。 |
|||||||||
|
将RabbitMQ Sharding Plugin与 一起使用时 |
|||||||||
|
当为 true 时,侦听器容器将分批批处理消息并使用批处理中的每条消息调用侦听器。从版本 2.2.7 开始,如果侦听器是 a或,生产者创建的批次将作为 a分批。否则,来自批次的消息将一次显示一个。默认为真。请参阅Batching和@RabbitListener with Batching。 |
|||||||||
|
被动队列声明失败时的重试次数。被动队列声明发生在消费者启动时,或者当从多个队列消费时,当初始化期间并非所有队列都可用时。当重试次数用尽后(出于任何原因)无法被动声明任何已配置的队列时,容器行为由前面描述的“missingQueuesFatal”属性控制。默认值:重试 3 次(总共尝试 4 次)。 |
|||||||||
|
确定是否应重新排队因侦听器引发异常而被拒绝的消息。默认值: |
|||||||||
|
|
|||||||||
|
确定此容器中的单个使用者是否具有对队列的独占访问权限。当 this 为 时,容器的并发度必须为 1 |
|||||||||
当独占消费者无法访问队列时使用的异常记录器。默认情况下,这是在 |
||||||||||
|
被动队列声明重试尝试之间的间隔。被动队列声明发生在消费者启动时,或者当从多个队列消费时,当初始化期间并非所有队列都可用时。默认值:5000(五秒)。 |
|||||||||
|
如果消费者在 内没有响应关闭 |
|||||||||
|
如果为真,则将 |
|||||||||
(团体) |
这仅在使用命名空间时可用。指定后,将 |
|||||||||
|
请参阅检测空闲异步消费者。 |
|||||||||
|
当 |
|||||||||
|
如果需要,按需启动的最大并发消费者数。必须大于或等于“concurrentConsumers”。请参阅侦听器并发。 |
|||||||||
|
确认之间接收的消息数。使用它来减少发送到代理的确认数量(以增加重新传递消息的可能性为代价)。通常,您应该仅在大容量侦听器容器上设置此属性。如果设置了此项并且消息被拒绝(抛出异常),则待处理的确认被确认并且失败的消息被拒绝。不允许使用已交易的渠道。如果 |
|||||||||
|
容器启动时,如果此属性为 如果在恢复过程中检测到问题(例如,在失去连接之后),容器将停止。 在应用程序上下文中必须有一个
|
|||||||||
|
设置为 这在以前的版本中是不可配置的。 当设置为 时 您还可以使用属性 bean 为所有容器全局设置属性,如下所示:
此全局属性不适用于任何具有显式 可以通过设置以下属性来覆盖默认重试属性(以五秒为间隔重试三次)。
|
|||||||||
|
使用 DMLC,任务计划在此时间间隔运行,以监视使用者的状态并恢复任何失败的。 |
|||||||||
|
设置为 |
|||||||||
|
|
|||||||||
|
当设置为 从2.0 版开始。 DirectMessageListenerContainer 当设置为 SimpleMessageListenerContainer 当设置为 时 您还可以使用属性 bean 为所有容器全局设置属性,如下所示:
此全局属性不会应用于任何具有显式 默认重试属性(以 5 秒为间隔重试 3 次)可以使用此属性之后的属性进行覆盖。 |
|||||||||
|
每个消费者可以未确认的未确认消息的数量。此值越高,消息传递的速度越快,但非顺序处理的风险越高。
另请参阅 |
|||||||||
|
当侦听器容器至少侦听一个自动删除队列并且在启动过程中发现它丢失时,容器使用 a |
|||||||||
|
等待每条消息的最长时间。如果 |
|||||||||
|
如果由于非致命原因无法启动消费者,则指定 |
|||||||||
|
如果由于非致命原因无法启动消费者,则确定尝试启动消费者之间的时间(以毫秒为单位)。默认值:5000。与 互斥 |
|||||||||
|
如果在消费者初始化期间配置的队列的子集可用,则消费者开始从这些队列中消费。消费者尝试使用此时间间隔被动声明丢失的队列。当此间隔过去时,将再次使用“declarationRetries”和“failedDeclarationRetryInterval”。如果仍然缺少队列,消费者将再次等待此时间间隔,然后再重试。这个过程无限期地继续,直到所有队列都可用。默认值:60000(一分钟)。 |
|||||||||
|
当一个容器关闭时(例如,如果它的封闭 |
|||||||||
|
在每个新消费者按需启动之前必须经过的时间(以毫秒为单位)。请参阅侦听器并发。默认值:10000(10 秒)。 |
|||||||||
|
使用有状态重试建议时,如果收到缺少 |
|||||||||
|
自检测到空闲消费者时停止最后一个消费者以来,消费者停止之前必须经过的时间(以毫秒为单位)。请参阅侦听器并发。默认值:60000(一分钟)。 |
|||||||||
|
A |
|||||||||
|
对用于执行侦听器调用程序的 Spring |
|||||||||
|
使用 DMLC,调度程序用于在“monitorInterval”运行监控任务。 |
|||||||||
|
外部事务管理器用于监听器的操作。还补充了 |
4.1.18。侦听器并发
SimpleMessageListenerContainer
默认情况下,侦听器容器启动一个从队列接收消息的消费者。
在检查上一节中的表格时,您可以看到许多控制并发性的属性和属性。最简单的是concurrentConsumers
,它创建了(固定)数量的并发处理消息的消费者。
在 1.3.0 版本之前,这是唯一可用的设置,必须停止并重新启动容器才能更改设置。
从 1.3.0 版本开始,您现在可以动态调整concurrentConsumers
属性。如果在容器运行时更改它,则根据需要添加或删除消费者以适应新设置。
此外,maxConcurrentConsumers
还添加了一个名为的新属性,容器根据工作负载动态调整并发。这与四个附加属性结合使用:consecutiveActiveTrigger
、startConsumerMinInterval
、consecutiveIdleTrigger
和stopConsumerMinInterval
。在默认设置下,增加消费者的算法如下:
如果maxConcurrentConsumers
尚未达到并且现有消费者连续十个周期处于活动状态并且自最后一个消费者启动以来已经过去了至少 10 秒,则启动一个新消费者。batchSize
如果消费者在*receiveTimeout
毫秒内收到至少一条消息,则认为该消费者处于活动状态。
在默认设置下,减少消费者的算法工作如下:
如果有多个concurrentConsumers
正在运行并且消费者检测到十个连续超时(空闲)并且最后一个消费者至少在 60 秒前停止,则消费者将被停止。超时取决于receiveTimeout
和batchSize
属性。batchSize
如果消费者在*receiveTimeout
毫秒内没有收到任何消息,则认为它是空闲的。因此,使用默认超时(1 秒)和 abatchSize
为 4,在 40 秒的空闲时间(四个超时对应于一个空闲检测)后考虑停止消费者。
实际上,只有在整个容器空闲一段时间后才能停止消费者。这是因为经纪人在所有活跃的消费者之间共享其工作。 |
无论配置的队列数量如何,每个消费者都使用单个通道。
从 2.0 版开始,可以使用属性设置 和concurrentConsumers
属性,例如.maxConcurrentConsumers
concurrency
2-4
4.1.19。独家消费者
从版本 1.3 开始,您可以使用单个独占消费者配置侦听器容器。这可以防止其他容器从队列中消费,直到当前消费者被取消。这种容器的并发性必须是1
.
使用独占消费者时,其他容器根据recoveryInterval
属性尝试从队列中消费,WARN
如果尝试失败,则记录消息。
4.1.20。侦听器容器队列
1.3 版引入了许多改进,用于在侦听器容器中处理多个队列。
容器必须配置为至少侦听一个队列。以前也是如此,但现在可以在运行时添加和删除队列。当处理任何预取的消息时,容器会回收(取消和重新创建)消费者。请参阅Javadoc了解addQueues
、addQueueNames
和removeQueues
方法removeQueueNames
。移除队列时,必须至少保留一个队列。
如果有任何队列可用,则消费者现在开始。以前,如果任何队列不可用,容器将停止。现在,只有在没有可用队列的情况下才会出现这种情况。如果并非所有队列都可用,则容器每 60 秒尝试被动声明(并从中消耗)丢失的队列。
此外,如果消费者从代理收到取消消息(例如,如果队列被删除),消费者会尝试恢复,并且恢复的消费者会继续处理来自任何其他已配置队列的消息。以前,取消一个队列会取消整个消费者,最终,容器将由于缺少队列而停止。
如果您希望永久删除队列,则应在删除队列之前或之后更新容器,以避免将来尝试使用它。
4.1.21。弹性:从错误和代理故障中恢复
Spring AMQP 提供的一些关键(也是最流行的)高级特性与协议错误或代理失败时的恢复和自动重新连接有关。我们已经在本指南中看到了所有相关组件,但将它们全部放在一起并单独调用功能和恢复方案应该会有所帮助。
主要的重新连接功能由CachingConnectionFactory
自身启用。RabbitAdmin
使用自动声明功能通常也是有益的。此外,如果您关心保证交付,您可能channelTransacted
还RabbitTemplate
需要在.SimpleMessageListenerContainer
AcknowledgeMode.AUTO
SimpleMessageListenerContainer
交换、队列和绑定的自动声明
该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;
};
}
它在不提供对Declarable
bean 定义的直接访问的项目中也很有用。
另请参阅RabbitMQ 自动连接/拓扑恢复。
同步操作失败和重试选项
如果在使用RabbitTemplate
(例如)时以同步顺序失去与代理的连接,Spring AMQP 会抛出一个AmqpException
(通常,但不总是,AmqpIOException
)。我们不会试图隐藏存在问题的事实,因此您必须能够捕获并响应异常。如果您怀疑连接丢失(这不是您的错),最简单的方法是再次尝试该操作。您可以手动执行此操作,或者您可以查看使用 Spring Retry 来处理重试(命令式或声明式)。
Spring Retry 提供了几个 AOP 拦截器和很大的灵活性来指定重试的参数(尝试次数、异常类型、退避算法等)。Spring AMQP 还提供了一些方便的工厂 bean,用于以方便的形式为 AMQP 用例创建 Spring Retry 拦截器,并具有可用于实现自定义恢复逻辑的强类型回调接口。请参阅和的 Javadoc 和StatefulRetryOperationsInterceptor
属性StatelessRetryOperationsInterceptor
了解更多详情。如果没有事务或事务在重试回调中启动,则无状态重试是合适的。请注意,无状态重试比有状态重试更易于配置和分析,但如果有一个正在进行的事务必须回滚或肯定要回滚,则通常不合适。在事务中间断开的连接应该与回滚具有相同的效果。因此,对于在堆栈更高层开始事务的重新连接,有状态重试通常是最佳选择。有状态重试需要一种机制来唯一标识一条消息。最简单的方法是让发送者在MessageId
消息属性中放置一个唯一值。提供的消息转换器提供了一个选项来执行此操作:您可以设置createMessageIds
为true
. 否则,您可以将MessageKeyGenerator
实现注入拦截器。密钥生成器必须为每条消息返回一个唯一的密钥。在 2.0 之前的版本MissingMessageIdAdvice
中,提供了 a。它使没有messageId
属性的消息只重试一次(忽略重试设置)。不再提供此建议,因为与spring-retry
1.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 在消费者端使用时,接收到的消息deliveryMode 在receivedDeliveryMode message 属性中。在这种情况下deliveryMode 是null 。这意味着NON_PERSISTENT 经纪人的交付模式。从 2.0 版开始,您可以将设置RepublishMessageRecoverer 为deliveryMode 要重新发布的消息(如果它是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
并将Exception
此traverseCauses
策略注入RetryTemplate
.
4.1.22。多代理(或集群)支持
2.3 版在单个应用程序与多个代理或代理集群之间进行通信时增加了更多便利。在消费者方面,主要好处是基础设施可以自动将自动声明的队列与适当的代理相关联。
最好用一个例子来说明这一点:
@SpringBootApplication(exclude = RabbitAutoConfiguration.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CachingConnectionFactory cf1() {
return new CachingConnectionFactory("localhost");
}
@Bean
CachingConnectionFactory cf2() {
return new CachingConnectionFactory("otherHost");
}
@Bean
CachingConnectionFactory cf3() {
return new CachingConnectionFactory("thirdHost");
}
@Bean
SimpleRoutingConnectionFactory rcf(CachingConnectionFactory cf1,
CachingConnectionFactory cf2, CachingConnectionFactory cf3) {
SimpleRoutingConnectionFactory rcf = new SimpleRoutingConnectionFactory();
rcf.setDefaultTargetConnectionFactory(cf1);
rcf.setTargetConnectionFactories(Map.of("one", cf1, "two", cf2, "three", cf3));
return rcf;
}
@Bean("factory1-admin")
RabbitAdmin admin1(CachingConnectionFactory cf1) {
return new RabbitAdmin(cf1);
}
@Bean("factory2-admin")
RabbitAdmin admin2(CachingConnectionFactory cf2) {
return new RabbitAdmin(cf2);
}
@Bean("factory3-admin")
RabbitAdmin admin3(CachingConnectionFactory cf3) {
return new RabbitAdmin(cf3);
}
@Bean
public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
return new RabbitListenerEndpointRegistry();
}
@Bean
public RabbitListenerAnnotationBeanPostProcessor postProcessor(RabbitListenerEndpointRegistry registry) {
MultiRabbitListenerAnnotationBeanPostProcessor postProcessor
= new MultiRabbitListenerAnnotationBeanPostProcessor();
postProcessor.setEndpointRegistry(registry);
postProcessor.setContainerFactoryBeanName("defaultContainerFactory");
return postProcessor;
}
@Bean
public SimpleRabbitListenerContainerFactory factory1(CachingConnectionFactory cf1) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cf1);
return factory;
}
@Bean
public SimpleRabbitListenerContainerFactory factory2(CachingConnectionFactory cf2) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cf2);
return factory;
}
@Bean
public SimpleRabbitListenerContainerFactory factory3(CachingConnectionFactory cf3) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cf3);
return factory;
}
@Bean
RabbitTemplate template(SimpleRoutingConnectionFactory rcf) {
return new RabbitTemplate(rcf);
}
@Bean
ConnectionFactoryContextWrapper wrapper(SimpleRoutingConnectionFactory rcf) {
return new ConnectionFactoryContextWrapper(rcf);
}
}
@Component
class Listeners {
@RabbitListener(queuesToDeclare = @Queue("q1"), containerFactory = "factory1")
public void listen1(String in) {
}
@RabbitListener(queuesToDeclare = @Queue("q2"), containerFactory = "factory2")
public void listen2(String in) {
}
@RabbitListener(queuesToDeclare = @Queue("q3"), containerFactory = "factory3")
public void listen3(String in) {
}
}
如您所见,我们已经声明了 3 套基础设施(连接工厂、管理员、容器工厂)。如前所述,@RabbitListener
可以定义使用哪个容器工厂;在这种情况下,他们还使用queuesToDeclare
which 导致在代理上声明队列(如果它不存在)。通过使用约定命名RabbitAdmin
bean <container-factory-name>-admin
,基础设施能够确定哪个管理员应该声明队列。这也将与bindings = @QueueBinding(…)
声明交换和绑定一起使用。它不适用于queues
,因为它期望队列已经存在。
在生产者方面,ConnectionFactoryContextWrapper
提供了一个方便的类,以使使用RoutingConnectionFactory
(请参阅路由连接工厂)更简单。
正如您在上面看到的,添加了一个带有路由键的SimpleRoutingConnectionFactory
bean和. 还有一个使用那个工厂。这是一个使用该模板与包装器一起路由到其中一个代理集群的示例。one
two
three
RabbitTemplate
@Bean
public ApplicationRunner runner(RabbitTemplate template, ConnectionFactoryContextWrapper wrapper) {
return args -> {
wrapper.run("one", () -> template.convertAndSend("q1", "toCluster1"));
wrapper.run("two", () -> template.convertAndSend("q2", "toCluster2"));
wrapper.run("three", () -> template.convertAndSend("q3", "toCluster3"));
};
}
4.2. 使用 RabbitMQ 流插件
-
RabbitStreamTemplate
-
StreamListenerContainer
4.2.1。发送消息
RabbitStreamTemplate
提供了RabbitTemplate
(AMQP) 功能的一个子集。
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
实现具有以下构造函数和属性:
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 的方法convertAndSend
中Message
。
StreamMessageConverter
用于从 Spring AMQP 转换为Message
原生流Message
。
也可以Message
直接发送原生流;使用提供对' 的消息构建器的messageBuilder()
访问权限的方法。Producer
提供ProducerCustomizer
了一种机制来在构建之前自定义生产者。
请参阅Java 客户端文档以了解自定义Environment
和Producer
.
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 客户端文档以了解自定义Environment
和Consumer
.
使用时@RabbitListener
,配置一个StreamRabbitListenerContainerFactory
;此时,大多数@RabbitListener
属性(concurrency
等)被忽略。仅支持id
、queues
和。此外,只能包含一个流名称。autoStartup
containerFactory
queues
4.2.3。例子
@Bean
RabbitStreamTemplate streamTemplate(Environment env) {
RabbitStreamTemplate template = new RabbitStreamTemplate(env, "test.stream.queue1");
template.setProducerCustomizer((name, builder) -> builder.name("test"));
return template;
}
@Bean
RabbitListenerContainerFactory<StreamListenerContainer> rabbitListenerContainerFactory(Environment env) {
return new StreamRabbitListenerContainerFactory(env);
}
@RabbitListener(queues = "test.stream.queue1")
void listen(String in) {
...
}
@Bean
RabbitListenerContainerFactory<StreamListenerContainer> nativeFactory(Environment env) {
StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(env);
factory.setNativeListener(true);
factory.setConsumerCustomizer((id, builder) -> {
builder.name("myConsumer")
.offset(OffsetSpecification.first())
.manualTrackingStrategy();
});
return factory;
}
@RabbitListener(id = "test", queues = "test.stream.queue2", containerFactory = "nativeFactory")
void nativeMsg(Message in, Context context) {
...
context.storeOffset();
}
版本 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。共同属性
以下属性可用于所有附加程序:
财产 | 默认 | 描述 |
---|---|---|
交换名称 |
日志 |
要将日志事件发布到的交易所的名称。 |
交换类型 |
话题 |
向其发布日志事件的交换类型——仅当附加程序声明交换时才需要。见 |
路由键模式 |
%c.%p |
用于生成路由密钥的日志记录子系统模式格式。 |
应用程序 ID |
应用程序 ID — 如果模式包含 .,则添加到路由键中 |
|
发件人池大小 |
2 |
用于发布日志事件的线程数。 |
maxSenderRetries |
30 |
如果代理不可用或出现其他错误,重试发送消息的次数。重试延迟如下: |
地址 |
以逗号分隔的代理地址列表,格式如下: |
|
主持人 |
本地主机 |
要连接的 RabbitMQ 主机。 |
港口 |
5672 |
要连接的 RabbitMQ 端口。 |
虚拟主机 |
/ |
要连接的 RabbitMQ 虚拟主机。 |
用户名 |
来宾 |
连接时使用的 RabbitMQ 用户。 |
密码 |
来宾 |
此用户的 RabbitMQ 密码。 |
使用SSL |
错误的 |
是否为 RabbitMQ 连接使用 SSL。查看 |
验证主机名 |
真的 |
为 TLS 连接启用服务器主机名验证。查看 |
ssl算法 |
无效的 |
要使用的 SSL 算法。 |
sslProperties位置 |
无效的 |
SSL 属性文件的位置。 |
密钥库 |
无效的 |
密钥库的位置。 |
密钥存储密码 |
无效的 |
密钥库的密码。 |
密钥存储类型 |
JKS |
密钥库类型。 |
信任商店 |
无效的 |
信任库的位置。 |
信任商店密码 |
无效的 |
信任库的密码。 |
信任存储类型 |
JKS |
信任库类型。 |
saslConfig |
null(RabbitMQ 客户端默认适用) |
-有关有效值, |
内容类型 |
文本/纯文本 |
|
内容编码 |
|
|
申报交易所 |
错误的 |
此附加程序启动时是否声明配置的交换。另见 |
耐用的 |
真的 |
当 |
自动删除 |
错误的 |
当 |
字符集 |
无效的 |
转换 |
交付方式 |
执着的 |
|
生成Id |
错误的 |
用于确定 |
客户端连接属性 |
无效的 |
|
addMdcAsHeaders |
真的 |
在引入此属性之前,始终将 MDC 属性添加到 RabbitMQ 消息头中。它可能会导致大型 MDC 出现问题,因为 RabbitMQ 对所有标头的缓冲区大小有限,而且这个缓冲区非常小。引入此属性是为了避免在大型 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 默认情况下不创建线程安全事件。如果代理关闭, |
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 编码器encoder
。encoder
和layout
选项是互斥的。
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>
<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. 提供自定义队列实现
使用AmqpAppenders
aBlockingQueue
将日志事件异步发布到 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
.
以下清单显示了helloWorldQueue
bean 定义:
@Bean
public Queue helloWorldQueue() {
return new Queue(this.helloWorldQueueName);
}
回顾rabbitTemplate
bean 配置,您可以看到它的属性(用于接收消息)和属性(用于发送消息)都有helloWorldQueue
set的名称。queue
routingKey
现在我们已经探索了配置,我们可以查看实际使用这些组件的代码。Producer
首先,从同一个包中打开类。它包含一个创建main()
Spring 的方法ApplicationContext
。
以下清单显示了该main
方法:
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(RabbitConfiguration.class);
AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
amqpTemplate.convertAndSend("Hello World");
System.out.println("Sent: Hello World");
}
在前面的示例中,AmqpTemplate
bean 被检索并用于发送一个Message
. 由于客户端代码应尽可能依赖接口,因此类型是AmqpTemplate
而不是RabbitTemplate
. 即使在 中创建的 beanHelloWorldConfiguration
是 的实例RabbitTemplate
,依赖接口也意味着此代码更具可移植性(您可以独立于代码更改配置)。由于convertAndSend()
方法被调用,模板委托给它的MessageConverter
实例。在这种情况下,它使用默认值SimpleMessageConverter
,但可以为rabbitTemplate
bean 提供不同的实现,如HelloWorldConfiguration
.
现在开课Consumer
。它实际上共享相同的配置基类,这意味着它共享rabbitTemplate
bean。这就是我们为该模板配置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
和一个rabbitTemplate
bean。这一次,由于配置专用于消息发送端,我们甚至不需要任何队列定义,并且RabbitTemplate
只设置了'routingKey'属性。回想一下,消息被发送到交换而不是直接发送到队列。AMQP 默认交换是没有名称的直接交换。所有队列都绑定到该默认交换,并以其名称作为路由键。这就是为什么我们只需要在这里提供路由键。
以下清单显示了rabbitTemplate
定义:
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.helloWorldQueueName);
return template;
}
由于此示例演示了异步消息接收,因此生产端被设计为连续发送消息(如果它是像同步版本那样的每次执行消息模型,那么它实际上是一个消息就不会那么明显了——驱动型消费者)。负责连续发送消息的组件被定义为ProducerConfiguration
. 它被配置为每三秒运行一次。
以下清单显示了该组件:
static class ScheduledProducer {
@Autowired
private volatile RabbitTemplate rabbitTemplate;
private final AtomicInteger counter = new AtomicInteger();
@Scheduled(fixedRate = 3000)
public void sendMessage() {
rabbitTemplate.convertAndSend("Hello World " + counter.incrementAndGet());
}
}
您不需要了解所有细节,因为真正的重点应该放在接收方(我们接下来会介绍)。但是,如果您还不熟悉 Spring 任务调度支持,您可以在此处了解更多信息。简短的故事是postProcessor
bean 向ProducerConfiguration
调度程序注册任务。
现在我们可以转向接收方。为了强调消息驱动的 POJO 行为,我们从响应消息的组件开始。该类被调用HelloWorldHandler
并显示在以下清单中:
public class HelloWorldHandler {
public void handleMessage(String text) {
System.out.println("Received: " + text);
}
}
该类是 POJO。它不扩展任何基类,不实现任何接口,甚至不包含任何导入。MessageListener
Spring 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
也会自动启动。您可以按任何顺序启动Producer
and 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 。要求')。在该通用配置文件中,您还可以看到Jackson2JsonMessageConverter
在RabbitTemplate
.
服务器特定的配置由两部分组成。首先,它在 上配置市场数据交换,RabbitTemplate
因此它不需要在每次调用时都提供该交换名称来发送Message
. 它在基本配置类中定义的抽象回调方法中执行此操作。以下清单显示了该方法:
public void configureRabbitTemplate(RabbitTemplate rabbitTemplate) {
rabbitTemplate.setExchange(MARKET_DATA_EXCHANGE_NAME);
}
其次,声明库存请求队列。在这种情况下,它不需要任何显式绑定,因为它以自己的名称作为路由键绑定到默认的无名称交换。如前所述,AMQP 规范定义了该行为。以下清单显示了stockRequestQueue
bean 的定义:
@Bean
public Queue stockRequestQueue() {
return new Queue(STOCK_REQUEST_QUEUE_NAME);
}
现在您已经看到了服务器的 AMQP 资源的配置,导航到目录org.springframework.amqp.rabbit.stocks
下的包src/test/java
。Server
在那里,您可以看到提供方法的实际类main()
。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
,并且该对象是使用BindingBuilder
fluent API 生成的。以下清单显示了Binding
:
@Value("${stocks.quote.pattern}")
private String marketDataRoutingKey;
@Bean
public Binding marketDataBinding() {
return BindingBuilder.bind(
marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}
请注意,实际值已在属性文件(client.properties
下src/main/resources
)中外部化,并且我们使用 Spring 的@Value
注释来注入该值。这通常是一个好主意。否则,该值将被硬编码在一个类中,并且无需重新编译就无法修改。在这种情况下,在更改用于绑定的路由模式时运行客户端的多个版本要容易得多。我们现在可以试试。
首先运行org.springframework.amqp.rabbit.stocks.Server
,然后org.springframework.amqp.rabbit.stocks.Client
。您应该会看到股票的虚拟报价NASDAQ
,因为与 client.properties 中的 'stocks.quote.pattern' 键关联的当前值为 'app.stock.quotes.nasdaq。'。现在,在保持现有Server
和Client
运行的同时,将该属性值更改为 '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.5. 测试支持
为异步应用程序编写集成必然比测试更简单的应用程序更复杂。当诸如@RabbitListener
注释之类的抽象出现时,这会变得更加复杂。问题是如何验证在发送消息后,侦听器是否按预期收到了消息。
框架本身有许多单元和集成测试。一些使用模拟,而另一些则使用实时 RabbitMQ 代理进行集成测试。您可以查阅这些测试以了解测试场景的一些想法。
Spring AMQP 1.6 版引入了spring-rabbit-test
jar,它为测试其中一些更复杂的场景提供了支持。预计该项目将随着时间的推移而扩展,但我们需要社区反馈来为帮助测试所需的功能提出建议。请使用JIRA或GitHub 问题提供此类反馈。
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。
@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();
}
}
以下类测试Thing
POJO:
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()
获取对它们的引用。
当与@RabbitListenerTest
和RabbitListenerTestHarness
一起使用harness.getLambdaAnswerFor("listenerId", true, …)
时,可以为听众获得正确构建的答案。
4.5.3. @RabbitListenerTest
和RabbitListenerTestHarness
用注释你的一个@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 调用数据的属性中可用。 |
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
更改主机名会影响management
REST API 连接(除非明确设置了管理 uri)。
BrokerRunning
还提供了一个static
名为的方法setEnvironmentVariableOverrides
,让您可以传入包含这些变量的映射。它们覆盖系统环境变量。如果您希望对多个测试套件中的测试使用不同的配置,这可能会很有用。重要提示:必须在调用任何isRunning()
创建规则实例的静态方法之前调用该方法。变量值应用于此调用后创建的所有实例。调用clearEnvironmentVariableOverrides()
以重置规则以使用默认值(包括任何实际环境变量)。
在您的测试用例中,您可以brokerRunning
在创建连接工厂时使用;getConnectionFactory()
返回规则的 RabbitMQ ConnectionFactory
。以下示例显示了如何执行此操作:
@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
return new CachingConnectionFactory(brokerRunning.getConnectionFactory());
}
4.5.6。JUnit5 条件
2.0.2 版引入了对 JUnit5 的支持。
使用@RabbitAvailable
注解
注释具有三个属性:
-
queues
:在每个测试之前声明(并清除)并在所有测试完成后删除的队列数组。 -
management
:true
如果您的测试还需要在代理上安装管理插件,请将此设置为。 -
purgeAfterEach
:(自 2.2 版以来)当true
(默认)时,queues
将在测试之间清除。
它用于检查代理是否可用,如果不可用则跳过测试。正如配置规则中所讨论的,如果没有代理,名为RABBITMQ_SERVER_REQUIRED
if的环境变量会导致测试快速失败。您可以使用配置规则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
注解
与LongRunningIntegrationTest
JUnit4类似@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"/>
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。
消息转换器更改
如果配置了自定义反序列化器,则Jackson2JMessageConverter
s 现在可以反序列化抽象类(包括接口) 。ObjectMapper
有关详细信息,请参阅反序列化抽象类。
测试更改
提供了一个新注释@SpringRabbitTest
来自动配置一些基础设施 bean,以便在您不使用SpringBootTest
. 有关更多信息,请参阅@SpringRabbitTest。
RabbitTemplate 更改
模板ReturnCallback
已被重构,ReturnsCallback
以便在 lambda 表达式中更简单地使用。有关详细信息,请参阅相关发布者确认和退货。
当使用返回和相关确认时,CorrelationData
现在需要一个独特的id
属性。有关详细信息,请参阅相关发布者确认和退货。
使用直接回复时,您现在可以配置模板,以便服务器不需要返回相关数据和回复。有关更多信息,请参阅RabbitMQ 直接回复。
侦听器容器更改
consumeDelay
现在可以使用新的侦听器容器属性;在使用RabbitMQ Sharding Plugin时很有帮助。
现在默认JavaLangErrorHandler
调用System.exit(99)
. 要恢复到以前的行为(什么都不做),请添加一个无操作处理程序。
容器现在支持globalQos
属性prefetchCount
为通道而不是通道上的每个消费者应用全局。
有关详细信息,请参阅消息侦听器容器配置。
消息后处理器更改
compressing MessagePostProcessor
s 现在使用逗号而不是冒号来分隔多个内容编码。解压缩器可以处理这两种格式,但是,如果您使用此版本生成的消息被 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
方法现在可以在一次调用中接收一整批消息,而不是一次获取一个。
当一次接收一个批处理消息时,最后一条消息的isLastInBatch
message 属性设置为 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
现在支持verifyHostname
SSL 选项。
此外,这些附加程序现在可以配置为不将 MDC 条目添加为标头。引入了addMdcAsHeaders
布尔选项来配置这种行为。
appenders 现在支持该SaslConfig
属性。
有关更多信息,请参阅记录子系统 AMQP 附加程序。
MessageListenerAdapter 更改
现在MessageListenerAdapter
提供了一种新buildListenerArguments(Object, Channel, Message)
方法来构建要传递给目标侦听器的参数数组,并且不推荐使用旧方法。有关MessageListenerAdapter
更多信息,请参阅。
交换/队列声明更改
用于创建和声明对象的 fluent APIExchangeBuilder
现在支持“众所周知的”参数。有关更多信息,请参阅队列和交换的 Builder API 。QueueBuilder
Exchange
Queue
RabbitAdmin
有一个新的RabbitAdmin
财产explicitDeclarationsOnly
。有关详细信息,请参阅条件声明。
连接工厂更改
有一个新的CachingConnectionFactory
财产shuffleAddresses
。在提供代理节点地址列表时,该列表将在创建连接之前被打乱,以便尝试连接的顺序是随机的。有关详细信息,请参阅连接到集群。
当使用 Publisher 确认并返回时,回调现在在连接工厂的executor
. amqp-clients
如果您从回调中执行兔子操作,这可以避免库中可能出现的死锁。有关详细信息,请参阅相关发布者确认和退货。
此外,现在使用ConfirmType
枚举而不是两个互斥的 setter 方法指定发布者确认类型。
现在RabbitConnectionFactoryBean
启用 SSL 时默认使用 TLS 1.2。有关详细信息,请参阅RabbitConnectionFactoryBean
和配置 SSL。
新的 MessagePostProcessor 类
当消息内容编码设置为 时,分别添加了类DeflaterPostProcessor
和以支持压缩和解压缩。InflaterPostProcessor
deflate
其他变化
该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-client
Spring AMQP 现在使用RabbitMQ 团队提供的 5.4.x 版本的库。此客户端默认配置了自动恢复。请参阅RabbitMQ 自动连接/拓扑恢复。
从 4.0 版开始,客户端默认启用自动恢复。Spring AMQP 在兼容这个特性的同时,有自己的恢复机制,一般不需要客户端恢复特性。我们建议禁用amqp-client 自动恢复,以避免AutoRecoverConnectionNotCurrentlyOpenException 在代理可用但连接尚未恢复时获取实例。从版本 1.7.1 开始,Spring AMQP 禁用它,除非您明确创建自己的 RabbitMQ 连接工厂并将其提供给CachingConnectionFactory . 默认情况下,由创建的RabbitMQConnectionFactory 实例RabbitConnectionFactoryBean 也禁用该选项。
|
包更改
某些类已移至不同的包。大多数是内部类,不会影响用户应用程序。两个例外是ChannelAwareMessageListener
和RabbitListenerErrorHandler
。这些接口现在在org.springframework.amqp.rabbit.listener.api
.
发布者确认更改
当有未完成的确认时,为发布者确认启用的通道不会返回到缓存中。有关详细信息,请参阅相关发布者确认和退货。
侦听器容器工厂改进
您现在可以使用侦听器容器工厂来创建任何侦听器容器,而不仅仅是那些用于@RabbitListener
注释或@RabbitListenerEndpointRegistry
. 有关更多信息,请参阅使用容器工厂。
ChannelAwareMessageListener
现在继承自MessageListener
.
代理事件监听器
引入ABrokerEventListener
将选定的代理事件作为ApplicationEvent
实例发布。有关更多信息,请参阅代理事件侦听器。
RabbitAdmin 更改
RabbitAdmin
发现 bean 类型Declarables
(它是Declarable
- Queue
、Exchange
和Binding
对象的容器)并在代理上声明包含的对象。不鼓励用户使用旧的声明机制<Collection<Queue>>
(和其他机制),而应该使用Declarables
bean。默认情况下,旧机制被禁用。有关详细信息,请参阅声明交换、队列和绑定的集合。
AnonymousQueue
现在声明实例时默认x-queue-master-locator
设置为client-local
,以确保在应用程序连接到的节点上创建队列。有关更多信息,请参阅配置代理。
RabbitTemplate 更改
您现在可以使用选项配置RabbitTemplate
以noLocalReplyConsumer
控制操作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 更改
默认情况下,RabbitConnectionFactoryBean
now 调用enableHostnameVerification()
. 要恢复到以前的行为,请将enableHostnameVerification
属性设置为false
。
连接工厂更改
现在CachingConnectionFactory
无条件地禁用底层 RabbitMQ 中的自动恢复ConnectionFactory
,即使在构造函数中提供了预配置的实例。尽管已采取措施使 Spring AMQP 与自动恢复兼容,但仍出现了某些问题仍然存在的极端情况。Spring AMQP 从 1.0.0 开始就有了自己的恢复机制,不需要使用客户端提供的恢复。虽然在构建之后仍然可以启用该功能(使用cachingConnectionFactory.getRabbitConnectionFactory()
.setAutomaticRecoveryEnabled()
) ,但我们强烈建议您不要这样做。如果您在直接使用客户端工厂(而不是使用 Spring AMQP 组件)时需要自动恢复连接,我们建议您使用单独的 RabbitMQ 。CachingConnectionFactory
ConnectionFactory
侦听器容器更改
如果存在标头,默认值ConditionalRejectingErrorHandler
现在完全丢弃导致致命错误的消息x-death
。有关详细信息,请参阅异常处理。
立即重新排队
引入了一个新ImmediateRequeueAmqpException
的来通知侦听器容器该消息必须重新排队。为了使用这个特性,ImmediateRequeueMessageRecoverer
添加了一个新的实现。
有关详细信息,请参阅消息侦听器和异步案例。
A.2.4。自 1.7 以来 2.0 的变化
使用CachingConnectionFactory
从版本 2.0.2 开始,您可以将 配置RabbitTemplate
为使用与侦听器容器使用的连接不同的连接。当生产者因任何原因被阻塞时,此更改避免了死锁的消费者。有关详细信息,请参阅使用单独的连接。
AMQP 客户端库
Spring AMQP 现在使用amqp-client
RabbitMQ 团队提供的新 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 Exchanges和Annotation-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转换。AsyncRabbitTemplate
receiveAndConvert
convertSendAndReceiveAsType
ParameterizedTypeReference<T>
SmartMessageConverter
Jackson2JsonMessageConverter
Message
RabbitTemplate
您现在可以使用 aRabbitTemplate
在专用通道上执行多项操作。有关详细信息,请参阅范围操作。
侦听器适配器
将FunctionalInterface
lambdas 与MessageListenerAdapter
. 有关MessageListenerAdapter
更多信息,请参阅。
侦听器容器更改
预取默认值
预取默认值曾经是 1,这可能导致高效消费者的利用不足。默认预取值现在是 250,这应该让消费者在最常见的场景中保持忙碌,从而提高吞吐量。
在某些情况下,预取值应该很低——例如,对于大消息,尤其是在处理速度很慢的情况下(消息可能会在客户端进程中增加大量内存),并且如果需要严格的消息排序(在这种情况下,预取值应设置回 1)。此外,对于低容量消息和多个消费者(包括单个侦听器容器实例中的并发),您可能希望减少预取以在消费者之间获得更均匀的消息分布。 |
有关预取的更多背景信息,请参阅这篇关于RabbitMQ 中消费者利用率的 文章和这篇关于排队理论的文章。
消息数
以前,为容器发出的消息MessageProperties.getMessageCount()
返回。0
此属性仅在您使用basicGet
(例如,来自RabbitTemplate.receive()
方法)时适用,并且现在已初始化为null
用于容器消息。
事务回滚行为
无论是否配置了事务管理器,事务回滚时的消息重新排队现在都是一致的。有关详细信息,请参阅有关回滚收到的消息的说明。
关机行为
如果容器线程在 内没有响应关闭shutdownTimeout
,则默认情况下会强制关闭通道。有关详细信息,请参阅消息侦听器容器配置。
连接工厂更改
连接和通道侦听器接口现在提供了一种机制来获取有关异常的信息。有关更多信息,请参阅连接和通道侦听器和发布是异步的 - 如何检测成功和失败。
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 解析器
在解析Queue
和Exchange
XML 组件时,如果存在属性,解析器不再将name
属性值注册为 bean 别名id
。有关详细信息,请参阅关于id
和name
属性的注释。
阻塞连接
您现在可以将 注入com.rabbitmq.client.BlockedListener
到org.springframework.amqp.rabbit.connection.Connection
对象中。此外,当连接被代理阻塞或解除阻塞时,ConnectionBlockedEvent
和ConnectionUnblockedEvent
事件会被发出。ConnectionFactory
有关详细信息,请参阅连接和资源管理。
A.2.5。自 1.6 以来 1.7 的变化
AMQP 客户端库
amqp-client
Spring 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
. 该框架不再与以前的版本兼容。
关机行为
您现在可以设置forceCloseChannel
为true
,如果容器线程在 内不响应关闭shutdownTimeout
,则通道将被强制关闭,从而导致任何未确认的消息重新排队。有关详细信息,请参阅消息侦听器容器配置。
JUnit@Rules
以前由框架内部使用的规则现在可以在一个名为spring-rabbit-junit
. 有关详细信息,请参阅JUnit4@Rules
。
连接命名策略
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
,如果在启动期间检测到问题,它会阻止容器(和上下文)启动。如果稍后检测到问题,例如从连接失败中恢复后,它也会停止容器。有关详细信息,请参阅消息侦听器容器配置。
默认错误处理程序
默认错误处理程序 ( ConditionalRejectingErrorHandler
) 现在将不可恢复@RabbitListener
的异常视为致命异常。有关详细信息,请参阅异常处理。
AutoDeclare
和RabbitAdmin
实例
有关在应用程序上下文中使用实例的选项语义的一些更改,请参阅消息侦听器容器配置( )。autoDeclare
RabbitAdmin
AmqpTemplate
: 超时接收
已经为
及其实现引入了许多新receive()
方法。有关更多信息,请参阅轮询消费者。timeout
AmqpTemplate
RabbitTemplate
使用AsyncRabbitTemplate
一个新AsyncRabbitTemplate
的被介绍了。该模板提供了多个发送和接收方法,返回值为 a ListenableFuture
,稍后可以使用这些方法同步或异步获取结果。有关详细信息,请参阅异步兔子模板。
RabbitTemplate
变化
1.4.1 引入了在代理支持时使用直接回复的能力。它比为每个回复使用临时队列更有效。useTemporaryReplyQueues
此版本允许您通过将属性设置为 来覆盖此默认行为并使用临时队列true
。有关更多信息,请参阅RabbitMQ 直接回复。
现在RabbitTemplate
支持user-id-expression
(userIdExpression
使用 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
更多信息,请参阅。
@SendTo
SpEL 表达式
@SendTo
用于路由没有replyTo
属性的回复现在可以是针对请求/回复评估的 SpEL 表达式。有关详细信息,请参阅回复管理。
@QueueBinding
改进
您现在可以在@QueueBinding
注释中为队列、交换和绑定指定参数。标头交换现在受@QueueBinding
. 有关详细信息,请参阅注释驱动的侦听器端点。
延迟消息交换
Spring AMQP 现在对 RabbitMQ 延迟消息交换插件具有一流的支持。有关详细信息,请参阅延迟消息交换。
交换内部标志
任何Exchange
定义现在都可以标记为internal
,并RabbitAdmin
在声明交换时将值传递给代理。有关更多信息,请参阅配置代理。
CachingConnectionFactory
变化
CachingConnectionFactory
缓存统计
现在CachingConnectionFactory
在运行时和通过 JMX 提供缓存属性。有关详细信息,请参阅运行时缓存属性。
访问底层 RabbitMQ 连接工厂
添加了一个新的 getter 以提供对底层工厂的访问。例如,您可以使用此 getter 添加自定义连接属性。有关详细信息,请参阅添加自定义客户端连接属性。
通道缓存
默认通道缓存大小已从 1 增加到 25。有关详细信息,请参阅连接和资源管理。
此外,SimpleMessageListenerContainer
不再将缓存大小调整为至少与数量一样大concurrentConsumers
——这是多余的,因为容器消费者通道永远不会被缓存。
Java反序列化
现在,您可以在使用 Java 反序列化时配置允许类的“允许列表”。如果您接受来自不受信任来源的带有序列化 java 对象的消息,您应该考虑创建一个允许列表。有关更多信息,请参阅Java 反序列化。
JSONMessageConverter
JSON 消息转换器的改进现在允许使用消息头中没有类型信息的消息。有关更多信息,请参阅注释方法的消息转换和Jackson2JsonMessageConverter。
A.2.8。自 1.4 以来 1.5 的变化
spring-erlang
不再支持
spring-erlang
jar 不再包含在分发中。请改用RabbitMQ REST API。
CachingConnectionFactory
变化
控制容器队列声明行为的属性
当侦听器容器消费者启动时,他们尝试被动地声明队列以确保它们在代理上可用。以前,如果这些声明失败(例如,因为队列不存在)或当 HA 队列被移动时,重试逻辑被固定为以五秒为间隔的三次重试尝试。如果队列仍然不存在,则行为由missingQueuesFatal
属性控制(默认值:true
)。此外,对于配置为从多个队列进行侦听的容器,如果只有一部分队列可用,则消费者会以 60 秒的固定间隔重试丢失的队列。
、declarationRetries
和属性现在是可配置的failedDeclarationRetryInterval
。retryDeclarationInterval
有关详细信息,请参阅消息侦听器容器配置。
DefaultMessagePropertiesConverter
变化
您现在可以配置以确定转换为 a而不是 aDefaultMessagePropertiesConverter
的 a 的最大长度。转换器有一个替代构造函数,该构造函数将值作为限制。以前,此限制以字节为单位进行硬编码。(在 1.4.4 中也可用)。LongString
String
DataInputStream
1024
@RabbitListener
改进
自动交换、队列和绑定声明
您现在可以声明定义这些实体集合的 bean,RabbitAdmin
并将内容添加到它在建立连接时声明的实体列表中。有关详细信息,请参阅声明交换、队列和绑定的集合。
RabbitTemplate
变化
reply-address
添加
该reply-address
属性已<rabbit-template>
作为替代添加到组件中reply-queue
。有关更多信息,请参阅请求/回复消息。(也可在 1.4.4 中作为 的设置器使用RabbitTemplate
)。
阻塞receive
方法
现在RabbitTemplate
支持阻塞receive
和convertAndReceive
方法。有关更多信息,请参阅轮询消费者。
sendAndReceive
方法强制
如果在使用and方法mandatory
时设置了标志,则调用线程会抛出一个如果请求消息无法传递。有关详细信息,请参阅回复超时。sendAndReceive
convertSendAndReceive
AmqpMessageReturnedException
RabbitManagementTemplate
添加
已RabbitManagementTemplate
引入通过使用其管理插件提供的 REST API 来监视和配置 RabbitMQ 代理。有关更多信息,请参阅RabbitMQ REST API。
侦听器容器 Bean 名称 (XML)
元素上的 应用正常的 Spring bean 名称覆盖。如果稍后使用与现有 bean 迁移到此版本时,如果您的元素有 |
但是,为了支持作为一个组启动和停止容器,group
添加了一个新属性。定义此属性后,由此元素创建的容器将添加到具有此名称的 bean 中,类型为Collection<SimpleMessageListenerContainer>
。您可以遍历该组来启动和停止容器。
班级级别@RabbitListener
现在@RabbitListener
可以在类级别应用注释。与新的@RabbitHandler
方法注释一起,这使您可以根据有效负载类型选择处理程序方法。有关详细信息,请参阅多方法侦听器。
SimpleMessageListenerContainer
: 退避支持
现在SimpleMessageListenerContainer
可以为启动恢复提供一个BackOff
实例。consumer
有关详细信息,请参阅消息侦听器容器配置。
通道关闭记录
引入了一种控制通道关闭日志级别的机制。请参阅记录通道关闭事件。
应用程序事件
当SimpleMessageListenerContainer
消费者失败时,现在会发出应用程序事件。有关更多信息,请参阅消费者事件。
消费者标签配置
以前,异步消费者的消费者标签是由代理生成的。在此版本中,现在可以为侦听器容器提供命名策略。请参阅消费者标签。
匿名队列命名
从版本 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
属性(PERSISTENT
或NON_PERSISTENT
,默认值:) PERSISTENT
。以前,所有 log4j 消息都是PERSISTENT
.
appender 还支持Message
在发送前修改 - 例如,允许添加自定义标头。子类应该覆盖postProcessMessageBeforeSend()
.
侦听器队列
现在,默认情况下,侦听器容器会在启动期间重新声明任何丢失的队列。添加了一个新auto-declare
属性<rabbit:listener-container>
以防止这些重新声明。请参阅auto-delete
队列。
RabbitTemplate
:mandatory
和connectionFactorySelector
表达式
、 和 SpEL 表达式的mandatoryExpression
属性sendConnectionFactorySelectorExpression
已receiveConnectionFactorySelectorExpression
添加到RabbitTemplate
. mandatoryExpression
用于在使用 a 时针对每个请求消息评估布尔mandatory
值ReturnCallback
。请参阅相关发布者确认和退货。sendConnectionFactorySelectorExpression
and在提供receiveConnectionFactorySelectorExpression
an 时使用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 AmqpException
catch 块上方。
RabbitMQ 3.4 兼容性
Spring AMQP 现在与 RabbitMQ 3.4 兼容,包括直接回复。有关更多信息,请参阅兼容性和RabbitMQ 直接回复。
ContentTypeDelegatingMessageConverter
添加
ContentTypeDelegatingMessageConverter
已引入以根据 中的属性选择要使用MessageConverter
的。有关详细信息,请参阅消息转换器。contentType
MessageProperties
A.2.10。自 1.2 以来 1.3 的变化
侦听器并发
侦听器容器现在支持根据工作负载动态扩展消费者数量,或者您可以在不停止容器的情况下以编程方式更改并发性。请参阅侦听器并发。
侦听器队列
侦听器容器现在允许在运行时修改其侦听的队列。此外,如果至少有一个已配置的队列可供使用,则容器现在将启动。请参阅侦听器容器队列
此侦听器容器现在在启动期间重新声明所有自动删除队列。请参阅auto-delete
队列。
消费者优先
监听器容器现在支持消费者参数,让x-priority
参数被设置。见消费者优先。
独家消费者
您现在可以配置SimpleMessageListenerContainer
单个exclusive
消费者,防止其他消费者收听队列。请参阅独占消费者。
兔子管理员
您现在可以让代理生成队列名称,而不管durable
、autoDelete
和exclusive
设置。请参阅配置代理。
直接交换绑定
以前,从配置元素中省略key
属性会导致队列或交换与空字符串绑定作为路由键。现在它与提供的名称绑定或。如果您希望使用空字符串路由键绑定,则需要指定.binding
direct-exchange
Queue
Exchange
key=""
AmqpTemplate
变化
现在AmqpTemplate
提供了几种同步receiveAndReply
方法。这些是由RabbitTemplate
. 有关详细信息,请参阅接收消息。
现在RabbitTemplate
支持配置RetryTemplate
在代理不可用时尝试重试(使用可选的退避策略)。有关详细信息,请参阅添加重试功能。
缓存连接工厂
您现在可以将缓存连接工厂配置为缓存Connection
实例及其Channel
实例,而不是使用单个连接并仅缓存Channel
实例。请参阅连接和资源管理。
绑定参数
现在支持解析<binding>
子元素。您现在可以使用属性对(匹配单个标题)或子元素(允许匹配多个标题)配置 。这些选项是相互排斥的。请参阅标头交换。<exchange>
<binding-arguments>
<binding>
<headers-exchange>
key/value
<binding-arguments>
路由连接工厂
一个新SimpleRoutingConnectionFactory
的被介绍了。它允许配置ConnectionFactories
映射,以确定ConnectionFactory
在运行时使用的目标。请参阅路由连接工厂。
MessageBuilder
和MessagePropertiesBuilder
现在提供了用于构建消息或消息属性的“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 或更高版本时不得设置此属性。
队列和其他项目的自动声明
以前,在声明队列、交换和绑定时,您无法定义用于声明的连接工厂。每个RabbitAdmin
都使用其连接声明所有组件。
从此版本开始,您现在可以将声明限制为特定RabbitAdmin
实例。请参阅条件声明。
AMQP 远程处理
现在提供了使用 Spring 远程处理技术的设施,使用 AMQP 作为 RPC 调用的传输。有关更多信息,请参阅Spring Remoting with AMQP