java - 如何使用 Apache Kafka 在 Spring Boot 中使用和保存自定义类型列表?

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

我正在编写一个 Kafka 生产者和消费者来发送我的自定义数据并在消费后保存。我完成了所有必需的 Kafka 配置,但出现了强制转换异常。

这些是代码:

消费者:

public class Consumer {
    private final Logger logger = LoggerFactory.getLogger(Consumer.class);

    @Autowired
    private RecordService service;

    @KafkaListener(topics = "data", groupId = "group_id", containerFactory = "kafkaListenerContainerFactory")
    public void consume(List<Record> message) {
        logger.info(String.format("$$ -> Consumed Message -> %s", "message"));

        System.out.println(message);
        service.saveAll(message);
    }
}

制作人:

public class Producer {
    private static final Logger logger = LoggerFactory.getLogger(Producer.class);
    private static final String TOPIC = "data";
    @Autowired
    private KafkaTemplate<String, List<Record>> kafkaTemplate;

    public void sendMessage(List<Record> list) {
        logger.info(String.format("$$ -> Producing message --> %s", "message"));
        // Message<List<Record>> message =
        // MessageBuilder.withPayload(list).setHeader(KafkaHeaders.TOPIC,
        // TOPIC).build();

        this.kafkaTemplate.send(TOPIC, list);
    }
}

消费者的KafkaConfig

@Configuration
public class KafkaConfiguration {

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "json");
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        return props;
    }

    @Bean
    public ConsumerFactory<String, List<Record>> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs(), new StringDeserializer(),
                new JsonDeserializer<>(List.class));
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, List<Record>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, List<Record>> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }
}

生产者的 KafkaConfig:

public class KafkaConfiguration {

    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return props;
    }

    @Bean
    public ProducerFactory<String, List<Record>> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<String, List<Record>> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

消息已成功发送并打印到控制台,但类型不是我所期望的。我发送一个列表,但它作为 LinkedHashMap 的列表被接收。存在堆栈跟踪错误:

2019-08-28 17:41:52.612  INFO 22236 --- [ntainer#0-0-C-1] com.consumerApp.models.Consumer          : $$ -> Consumed Message -> message
[{region=Central America and the Caribbean, country=Haiti, itemType=Office Supplies, salesChannel=Online, orderPriority=C, orderDate=1293739200000, orderId=485070693, shipDate=1296417600000, unitsSold=2052, unitPrice=651.21, unitCost=524.96, totalRevenue=1336282.92, totalCost=1077217.92, totalProfit=259065.0}, {region=Central America and the Caribbean, country=Nicaragua, itemType=Household, salesChannel=Online, orderPriority=C, orderDate=1445976000000, orderId=573998582, shipDate=1449432000000, unitsSold=7791, unitPrice=668.27, unitCost=502.54, totalRevenue=5206491.57, totalCost=3915289.14, totalProfit=1291202.43}]
2019-08-28 17:41:52.613 ERROR 22236 --- [ntainer#0-0-C-1] o.s.kafka.listener.LoggingErrorHandler   : Error while processing: ConsumerRecord(topic = data, partition = 0, offset = 16, CreateTime = 1566999712608, serialized key size = -1, serialized value size = 675, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = [{region=Central America and the Caribbean, country=Haiti, itemType=Office Supplies, salesChannel=Online, orderPriority=C, orderDate=1293739200000, orderId=485070693, shipDate=1296417600000, unitsSold=2052, unitPrice=651.21, unitCost=524.96, totalRevenue=1336282.92, totalCost=1077217.92, totalProfit=259065.0}, {region=Central America and the Caribbean, country=Nicaragua, itemType=Household, salesChannel=Online, orderPriority=C, orderDate=1445976000000, orderId=573998582, shipDate=1449432000000, unitsSold=7791, unitPrice=668.27, unitCost=502.54, totalRevenue=5206491.57, totalCost=3915289.14, totalProfit=1291202.43}])

org.springframework.kafka.listener.ListenerExecutionFailedException: Listener method 'public void com.consumerApp.models.Consumer.consume(java.util.List<com.consumerApp.models.Record>)' threw exception; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.consumerApp.models.Record; nested exception is java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.consumerApp.models.Record
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.decorateException(KafkaMessageListenerContainer.java:1272) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeErrorHandler(KafkaMessageListenerContainer.java:1261) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1188) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:1159) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:1099) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:934) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:750) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:699) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_151]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_151]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]
Caused by: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.consumerApp.models.Record
    at com.consumerApp.models.RecordService.saveAll(RecordService.java:15) ~[classes/:na]
    at com.consumerApp.models.Consumer.consume(Consumer.java:23) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_151]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_151]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_151]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_151]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:170) ~[spring-messaging-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.1.5.RELEASE.jar:5.1.5.RELEASE]
    at org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:48) ~[spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:283) ~[spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:79) ~[spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:50) ~[spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:1224) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:1217) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1178) [spring-kafka-2.2.4.RELEASE.jar:2.2.4.RELEASE]
    ... 8 common frames omitted

为什么它没有作为列表被接收?有任何想法吗?谢谢。

最佳答案

您得到的类型是List<LinkedHashMap> ,因为你的解串器是 JsonDeserializer<>(List.class) ,其实际目标类型为Lis<Object> ,最终 Jackson 将其转换为 List<LinkedHashMap> .

String str = "[{\"region\": \"Central America and the Caribbean\", \"country\":\"Haiti\", " +
                "\"itemType\":\"Office Supplies\"}]";
List list = new ObjectMapper().readValue(str, List.class);
System.out.println(list.get(0).getClass());

打印class java.util.LinkedHashMap .

据我所知,您应该使用自定义反序列化器。

static class CustomDeserializer extends JsonDeserializer<List<MyRecord>> {

        @Override
        public List<MyRecord> deserialize(String topic, Headers headers, byte[] data) {
            return deserialize(topic, data);
        }

        @Override
        public List<MyRecord> deserialize(String topic, byte[] data) {
            if (data == null) {
                return null;
            }
            try {
                return objectMapper.readValue(data, new TypeReference<List<MyRecord>>() {
                });
            } catch (IOException e) {
                throw new SerializationException("Can't deserialize data [" + Arrays.toString(data) +
                        "] from topic [" + topic + "]", e);
            }
        }
    }

关于java - 如何使用 Apache Kafka 在 Spring Boot 中使用和保存自定义类型列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57694010/

相关文章:

java - spring boot css 在渲染时有问题

java - Spring 启动: Testing a service with Constructor Dependency Injection

java - JPA 命名查询 findAllBy 与 Long 和 String

ssl - 启用 SSL 加密和身份验证后,Kafka Avro 控制台消费者无法正常工作

java - 作为kafka消费者同时消费多个主题

apache-kafka - Kafka 生产者弹性配置 : Fail but never block

java - 你如何调用另一个类的开关盒?

java - 特殊字符问题

java - "HashMap$Entry cannot be cast to X"尝试使用循环从 Map 中提取值时

java - Java 的 Groovy 超集