我正在创建一个消息处理器,它从一个kafka主题接收事件,对其进行处理并将结果转发到另一个主题。
我使用@KafkaListener
创建了一个@SendTo
方法,该方法在我希望控制外发消息的密钥生成之前一直有效。
documentation (2.2.4.RELEASE)建议通过子类KafkaTemplate
来创建一个bean,并覆盖它的send(String topic, String data)
方法。
不幸的是,这不起作用,因为在我的情况下未调用该方法。另一方面,send(Message<?> message)
被调用,但这没有帮助。经过简短的调试后,发现如果输入是MessagingMessageListenerAdapter
的实例且结果不是org.springframework.messaging.Message
的情况下,List
会调用此方法。不幸的是RecordMessagingMessageListenerAdapter
总是将 input 转换为Message
。
我是否将这种注释组合用于不是spring kafka作者的意图的东西,这是错误还是文档错误?
此外,只有在我不创建自己的KafkaTemplate
bean的情况下,spring boot自动配置才有效,这很令人讨厌。
如果我创建了该覆盖的模板,则必须自己创建KafkaListenerContainerFactory并设置答复模板以使@SendTo
再次起作用。
这是我的示例代码。尽可能简单。
@SpringBootApplication
@Slf4j
public class SpringKafkaExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringKafkaExampleApplication.class, args);
}
@KafkaListener(topics = "${example.topics.input}")
@SendTo("${example.topics.output}")
public String process(final byte[] payload) {
String message = new String(payload, StandardCharsets.UTF_8);
log.info(message);
return message;
}
/*
//To set my custom KafkaTemplate as replyTemplate
@Bean
public ConcurrentKafkaListenerContainerFactory<String, byte[]> kafkaListenerContainerFactory(KafkaTemplate<String, String> kafkaTemplate,
ConsumerFactory<String, byte[]> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, byte[]> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setReplyTemplate(kafkaTemplate);
return factory;
}
//My KafkaTemplate with overridden send(topic, data) method
@Bean
public KafkaTemplate<String, String> kafkaTempate(ProducerFactory<String, String> producerFactory) {
return new KafkaTemplate<String, String>(producerFactory) {
@Override
public ListenableFuture<SendResult<String, String>> send(String topic, String data) {
return super.send(topic, "some_generated_key", data);
}
};
}
*/
}
更新
send:215, KafkaTemplate (org.springframework.kafka.core)
sendReplyForMessageSource:449, MessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
sendSingleResult:416, MessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
sendResponse:402, MessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
handleResult:324, MessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
onMessage:81, RecordMessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
onMessage:50, RecordMessagingMessageListenerAdapter (org.springframework.kafka.listener.adapter)
此处将接收到的记录转换为Message对象。
@Override
public void onMessage(ConsumerRecord<K, V> record, Acknowledgment acknowledgment, Consumer<?, ?> consumer) {
Message<?> message = toMessagingMessage(record, acknowledgment, consumer);
if (logger.isDebugEnabled()) {
logger.debug("Processing [" + message + "]");
}
try {
Object result = invokeHandler(record, acknowledgment, message, consumer);
if (result != null) {
handleResult(result, record, message);
}
}
String
由KafkaListener方法返回,因此将调用sendSingleResult(result, topic, source)
。
protected void sendResponse(Object result, String topic, @Nullable Object source, boolean messageReturnType) {
if (!messageReturnType && topic == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No replyTopic to handle the reply: " + result);
}
}
else if (result instanceof Message) {
this.replyTemplate.send((Message<?>) result);
}
else {
if (result instanceof Collection) {
((Collection<V>) result).forEach(v -> {
if (v instanceof Message) {
this.replyTemplate.send((Message<?>) v);
}
else {
this.replyTemplate.send(topic, v);
}
});
}
else {
sendSingleResult(result, topic, source);
}
}
}
private void sendSingleResult(Object result, String topic, @Nullable Object source) {
byte[] correlationId = null;
boolean sourceIsMessage = source instanceof Message;
if (sourceIsMessage
&& ((Message<?>) source).getHeaders().get(KafkaHeaders.CORRELATION_ID) != null) {
correlationId = ((Message<?>) source).getHeaders().get(KafkaHeaders.CORRELATION_ID, byte[].class);
}
if (sourceIsMessage) {
sendReplyForMessageSource(result, topic, source, correlationId);
}
else {
this.replyTemplate.send(topic, result);
}
}
@SuppressWarnings("unchecked")
private void sendReplyForMessageSource(Object result, String topic, Object source, byte[] correlationId) {
MessageBuilder<Object> builder = MessageBuilder.withPayload(result)
.setHeader(KafkaHeaders.TOPIC, topic);
if (this.replyHeadersConfigurer != null) {
Map<String, Object> headersToCopy = ((Message<?>) source).getHeaders().entrySet().stream()
.filter(e -> {
String key = e.getKey();
return !key.equals(MessageHeaders.ID) && !key.equals(MessageHeaders.TIMESTAMP)
&& !key.equals(KafkaHeaders.CORRELATION_ID)
&& !key.startsWith(KafkaHeaders.RECEIVED);
})
.filter(e -> this.replyHeadersConfigurer.shouldCopy(e.getKey(), e.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (headersToCopy.size() > 0) {
builder.copyHeaders(headersToCopy);
}
headersToCopy = this.replyHeadersConfigurer.additionalHeaders();
if (!ObjectUtils.isEmpty(headersToCopy)) {
builder.copyHeaders(headersToCopy);
}
}
if (correlationId != null) {
builder.setHeader(KafkaHeaders.CORRELATION_ID, correlationId);
}
setPartition(builder, ((Message<?>) source));
this.replyTemplate.send(builder.build());
}
source
现在是一条消息-> sendReplyForMessageSource
将被呼叫。
答案 0 :(得分:0)
经过简短的调试,结果发现如果输入是
org.springframework.messaging.Message
的实例,则MessagingMessageListenerAdapter会调用此方法
那是不正确的;当监听器方法返回一个Message<?>
(或Collection<Message<?>>
)时调用。
代码:
protected void sendResponse(Object result, String topic, @Nullable Object source, boolean messageReturnType) {
if (!messageReturnType && topic == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No replyTopic to handle the reply: " + result);
}
}
else if (result instanceof Message) {
this.replyTemplate.send((Message<?>) result);
}
else {
if (result instanceof Collection) {
((Collection<V>) result).forEach(v -> {
if (v instanceof Message) {
this.replyTemplate.send((Message<?>) v);
}
else {
this.replyTemplate.send(topic, v);
}
});
}
else {
sendSingleResult(result, topic, source);
}
}
}
自定义出站键的最简单方法是将您的方法更改为返回Message<String>
。从该文档链接向下滚动到...
如果侦听器方法返回“消息”或“集合”,则侦听器方法负责为答复设置消息头。例如,当处理来自ReplyingKafkaTemplate的请求时,您可以执行以下操作:
@KafkaListener(id = "messageReturned", topics = "someTopic")
public Message<?> listen(String in, @Header(KafkaHeaders.REPLY_TOPIC) byte[] replyTo,
@Header(KafkaHeaders.CORRELATION_ID) byte[] correlation) {
return MessageBuilder.withPayload(in.toUpperCase())
.setHeader(KafkaHeaders.TOPIC, replyTo)
.setHeader(KafkaHeaders.MESSAGE_KEY, 42)
.setHeader(KafkaHeaders.CORRELATION_ID, correlation)
.setHeader("someOtherHeader", "someValue")
.build();
}
此外,只有在我不创建自己的KafkaTemplate bean的情况下,Spring Boot自动配置才能工作,这很令人讨厌。
为了引导引导程序插入答复模板,必须将其声明为KafkaTemplate<Object, Object>
。