在spring-kafka中为多个KafkaListener实现共享逻辑

时间:2018-05-14 08:13:41

标签: java spring spring-boot apache-kafka spring-kafka

My Spring Boot应用程序包含多个@KafkaListeners,每个侦听器在实际处理有效负载之前和之后执行相同的步骤:验证有效负载,检查事件是否已经处理,检查它是否为墓碑(null )消息,决定是否应该在发生故障时重试处理,发出指标等等。

这些步骤目前在基类中实现,但由于传递给@KafkaListener的主题在运行时必须是常量,因此使用@KafkaListener注释的方法在子类中定义,除了将其参数传递给基类中的方法。

这很好用,但我想知道是否有更优雅的解决方案。我假设我的基类必须以编程方式创建一个监听器容器,但在快速查看KafkaListenerAnnotationBeanPostProcessor后,它似乎非常复杂。

有没有人有任何建议?

3 个答案:

答案 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 软件包。