My Spring Boot应用程序包含多个@KafkaListener
s,每个侦听器在实际处理有效负载之前和之后执行相同的步骤:验证有效负载,检查事件是否已经处理,检查它是否为墓碑(null )消息,决定是否应该在发生故障时重试处理,发出指标等等。
这些步骤目前在基类中实现,但由于传递给@KafkaListener
的主题在运行时必须是常量,因此使用@KafkaListener注释的方法在子类中定义,除了将其参数传递给基类中的方法。
这很好用,但我想知道是否有更优雅的解决方案。我假设我的基类必须以编程方式创建一个监听器容器,但在快速查看KafkaListenerAnnotationBeanPostProcessor
后,它似乎非常复杂。
有没有人有任何建议?
答案 0 :(得分:1)
这个怎么样:
public abstract class BaseKafkaProcessingLogic {
@KafkaHandler
public void handle(Object payload) {
}
}
@KafkaListener(topics = "topic1")
public class Topic1Handler extends BaseKafkaProcessingLogic {
}
@KafkaListener(topics = "topic2")
public class Topic2Handler extends BaseKafkaProcessingLogic {
}
答案 1 :(得分:0)
在寻找类似的东西的同时偶然发现了这个问题,我首先开始使用Artem Bilan的answer。但是这不起作用,因为默认情况下的注释不会在子类中继承,除非它们自己使用@Inherited注释。尽管如此,可能还有一种方法可以使注释方法起作用,如果我开始工作,我会更新这个答案。值得庆幸的是,虽然我已经使用Kafka听众的程序注册实现了理想的行为。
我的代码如下:
接口:
public interface GenericKafkaListener {
String METHOD = "handleMessage";
void handleMessage(ConsumerRecord<String, String> record);
}
抽象类:
public abstract class AbstractGenericKafkaListener implements GenericKafkaListener {
private final String kafkaTopic;
public AbstractGenericKafkaListener(final String kafkaTopic) {
this.kafakTopic = kafkaTopic;
}
@Override
public void handleMessage(final ConsumerRecord<String, String> record) {
//do common logic here
specificLogic(record);
}
protected abstract specificLogic(ConsumerRecord<String, String> record);
public String getKafkaTopic() {
return kafkaTopic;
}
}
然后我们可以在KafkaListenerConfigurer中以编程方式注册所有类型为AbstractGenericKafkaListener的bean:
@Configuration
public class KafkaListenerConfigurataion implements KafkaListenerConfigurer {
@Autowired
private final List<AbstractGenericKafkaListener> listeners;
@Autowired
private final BeanFactory beanFactory;
@Autowired
private final MessageHandlerMethodFactory messageHandlerMethodFactory;
@Autowired
private final KafkaListenerContainerFactory kafkaListenerContainerFactory;
@Value("${your.kafka.consumer.group-id}")
private String consumerGroup;
@Value("${your.application.name}")
private String service;
@Override
public void configureKafkaListeners(
final KafkaListenerEndpointRegistrar registrar) {
final Method listenerMethod = lookUpMethod();
listeners.forEach(listener -> {
registerListenerEndpoint(listener, listenerMethod, registrar);
});
}
private void registerListenerEndpoint(final AbstractGenericKafkaListener listener,
final Method listenerMethod,
final KafkaListenerEndpointRegistrar registrar) {
log.info("Registering {} endpoint on topic {}", listener.getClass(),
listener.getKafkaTopic());
final MethodKafkaListenerEndpoint<String, String> endpoint =
createListenerEndpoint(listener, listenerMethod);
registrar.registerEndpoint(endpoint);
}
private MethodKafkaListenerEndpoint<String, String> createListenerEndpoint(
final AbstractGenericKafkaListener listener, final Method listenerMethod) {
final MethodKafkaListenerEndpoint<String, String> endpoint = new MethodKafkaListenerEndpoint<>();
endpoint.setBeanFactory(beanFactory);
endpoint.setBean(listener);
endpoint.setMethod(listenerMethod);
endpoint.setId(service + "-" + listener.getKafkaTopic());
endpoint.setGroup(consumerGroup);
endpoint.setTopics(listener.getKafkaTopic());
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
return endpoint;
}
private Method lookUpMethod() {
return Arrays.stream(GenericKafkaListener.class.getMethods())
.filter(m -> m.getName().equals(GenericKafkaListener.METHOD))
.findAny()
.orElseThrow(() ->
new IllegalStateException("Could not find method " + GenericKafkaListener.METHOD));
}
}
答案 2 :(得分:0)
我需要相同的功能,并提出了接近Artem Bilan answer的解决方案。是的,@KafkaHandler
注释不是子类继承的,而是在接口中定义的。解决方法如下:
interface AbstractKafkaListener<T> {
default Class<T> getCommandType() {
TypeToken<T> type = new TypeToken<>(getClass()) {};
return (Class<T>) type.getRawType();
}
@KafkaHandler
default void handle(String message) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
T value = objectMapper.readValue(message, getCommandType());
handle(value);
}
void handle(T message);
}
该类应仅实现handle方法:
@Component
@KafkaListener(topics = "my_topic")
public class KafkaListenerForMyCustomMessage implements AbstractKafkaListener<MyCustomMessage> {
@Override
public void handle(MyCustomMessage message) {
System.out.println(message);
}
}
接口中的两个已实现方法应该是私有的/受保护的,但是由于它们在接口中,因此无法完成。 default
方法始终是公共的。实际上,接口中定义的所有方法始终都是公共的。
我使用此解决方案动态地将消息从kafka(在 String 中接收)解析为自定义类。
getCommandType
方法返回T
通用参数的类。 TypeToken
来自Google Guava 软件包。