使用 @SendTo
转发 Listener 结果
从 2.0 版本开始,如果也使用 @SendTo
注解注释 @KafkaListener
,并且方法调用返回结果,则该结果将转发到 @SendTo
指定的主题。
@SendTo
值可以有多种形式
-
@SendTo("someTopic")
路由到字面主题。 -
@SendTo("#{someExpression}")
路由到在应用程序上下文初始化期间评估表达式一次确定的主题。 -
@SendTo("!{someExpression}")
路由到在运行时评估表达式确定的主题。用于评估的#root
对象具有三个属性-
request
:传入的ConsumerRecord
(或批处理监听器的ConsumerRecords
对象)。 -
source
:从request
转换的org.springframework.messaging.Message<?>
。 -
result
:方法的返回结果。
-
-
@SendTo
(无属性):这被视为!{source.headers['kafka_replyTopic']}
(从 2.1.3 版本开始)。
从 2.1.11 和 2.2.1 版本开始,在 @SendTo
值中解析属性占位符。
表达式评估的结果必须是表示主题名称的 String
。以下示例显示了使用 @SendTo
的各种方法
@KafkaListener(topics = "annotated21")
@SendTo("!{request.value()}") // runtime SpEL
public String replyingListener(String in) {
...
}
@KafkaListener(topics = "${some.property:annotated22}")
@SendTo("#{myBean.replyTopic}") // config time SpEL
public Collection<String> replyingBatchListener(List<String> in) {
...
}
@KafkaListener(topics = "annotated23", errorHandler = "replyErrorHandler")
@SendTo("annotated23reply") // static reply topic definition
public String replyingListenerWithErrorHandler(String in) {
...
}
...
@KafkaListener(topics = "annotated25")
@SendTo("annotated25reply1")
public class MultiListenerSendTo {
@KafkaHandler
public String foo(String in) {
...
}
@KafkaHandler
@SendTo("!{'annotated25reply2'}")
public String bar(@Payload(required = false) KafkaNull nul,
@Header(KafkaHeaders.RECEIVED_KEY) int key) {
...
}
}
为了支持 @SendTo ,必须为监听器容器工厂提供 KafkaTemplate (在其 replyTemplate 属性中),该模板用于发送回复。这应该是一个 KafkaTemplate ,而不是 ReplyingKafkaTemplate ,后者在客户端用于请求/回复处理。使用 Spring Boot 时,它会自动将模板配置到工厂中;当配置自己的工厂时,必须按以下示例所示设置它。 |
从 2.2 版本开始,您可以向监听器容器工厂添加 ReplyHeadersConfigurer
。这将用于确定要在回复消息中设置哪些标头。以下示例显示了如何添加 ReplyHeadersConfigurer
@Bean
public ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(cf());
factory.setReplyTemplate(template());
factory.setReplyHeadersConfigurer((k, v) -> k.equals("cat"));
return factory;
}
如果需要,您还可以添加更多标头。以下示例显示了如何执行此操作
@Bean
public ConcurrentKafkaListenerContainerFactory<Integer, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(cf());
factory.setReplyTemplate(template());
factory.setReplyHeadersConfigurer(new ReplyHeadersConfigurer() {
@Override
public boolean shouldCopy(String headerName, Object headerValue) {
return false;
}
@Override
public Map<String, Object> additionalHeaders() {
return Collections.singletonMap("qux", "fiz");
}
});
return factory;
}
当您使用 @SendTo
时,必须使用其 replyTemplate
属性中的 KafkaTemplate
配置 ConcurrentKafkaListenerContainerFactory
以执行发送。Spring Boot 会自动连接其自动配置的模板(或者如果存在单个实例,则连接任何模板)。
除非您使用 请求/回复语义,否则只会使用简单的 send(topic, value) 方法,因此您可能希望创建一个子类来生成分区或键。以下示例显示了如何执行此操作 |
@Bean
public KafkaTemplate<String, String> myReplyingTemplate() {
return new KafkaTemplate<Integer, String>(producerFactory()) {
@Override
public CompletableFuture<SendResult<String, String>> send(String topic, String data) {
return super.send(topic, partitionForData(data), keyForData(data), data);
}
...
};
}
如果监听器方法返回
|
使用请求/回复语义时,发送方可以请求目标分区。
即使没有返回值,您也可以使用
有关详细信息,请参阅 处理异常。 |
如果监听器方法返回 Iterable ,则默认情况下,每个元素作为值的记录都会被发送。从 2.3.5 版本开始,将 @KafkaListener 上的 splitIterables 属性设置为 false ,并且整个结果将作为单个 ProducerRecord 的值发送。这需要回复模板的生产者配置中的合适序列化器。但是,如果回复是 Iterable<Message<?>> ,则会忽略该属性,并且会单独发送每条消息。 |