web-dev-qa-db-fra.com

Spring Kafka La classe n'est pas dans les packages approuvés

Dans mon application Spring Boot/Kafka avant la mise à jour de la bibliothèque, j'ai utilisé la classe suivante org.telegram.telegrambots.api.objects.Update afin de publier des messages sur le sujet Kafka. Pour le moment, j'utilise le org.telegram.telegrambots.meta.api.objects.Update. Comme vous pouvez le voir - ils ont des packages différents.

Après le redémarrage de l'application, j'ai rencontré le problème suivant:

[org.springframework.kafka.KafkaListenerEndpointContainer#1-0-C-1] o.s.kafka.listener.LoggingErrorHandler : Error while processing: null

org.Apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition telegram.fenix.bot.update-0 at offset 4223. If needed, please seek past the record to continue consumption.
Caused by: Java.lang.IllegalArgumentException: The class 'org.telegram.telegrambots.api.objects.Update' is not in the trusted packages: [Java.util, Java.lang, org.telegram.telegrambots.meta.api.objects]. If you believe this class is safe to deserialize, please provide its name. If the serialization is only done by a trusted source, you can also enable trust all (*).
at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.Java:139) ~[spring-kafka-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
at org.springframework.kafka.support.converter.DefaultJackson2JavaTypeMapper.toJavaType(DefaultJackson2JavaTypeMapper.Java:113) ~[spring-kafka-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
at org.springframework.kafka.support.serializer.JsonDeserializer.deserialize(JsonDeserializer.Java:221) ~[spring-kafka-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
at org.Apache.kafka.clients.consumer.internals.Fetcher.parseRecord(Fetcher.Java:967) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.internals.Fetcher.access$3300(Fetcher.Java:93) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.fetchRecords(Fetcher.Java:1144) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.internals.Fetcher$PartitionRecords.access$1400(Fetcher.Java:993) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.internals.Fetcher.fetchRecords(Fetcher.Java:527) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.internals.Fetcher.fetchedRecords(Fetcher.Java:488) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.KafkaConsumer.pollOnce(KafkaConsumer.Java:1155) ~[kafka-clients-1.1.0.jar!/:na]
at org.Apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.Java:1115) ~[kafka-clients-1.1.0.jar!/:na]
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.Java:699) ~[spring-kafka-2.1.8.RELEASE.jar!/:2.1.8.RELEASE]
at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:511) [na:1.8.0_171]
at Java.util.concurrent.FutureTask.run(FutureTask.Java:266) [na:1.8.0_171]
at Java.lang.Thread.run(Thread.Java:748) [na:1.8.0_171]

Voici ma config:

@EnableAsync
@Configuration
public class ApplicationConfig {

    @Bean
    public StringJsonMessageConverter jsonConverter() {
        return new StringJsonMessageConverter();
    }

}

@Configuration
public class KafkaProducerConfig {

    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;

    @Bean
    public Map<String, Object> producerConfigs() {

        Map<String, Object> props = new HashMap<>();

        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        props.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, 15000000);

        return props;
    }

    @Bean
    public ProducerFactory<String, Update> updateProducerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<String, Update> updateKafkaTemplate() {
        return new KafkaTemplate<>(updateProducerFactory());
    }

}

@Configuration
public class KafkaConsumerConfig {

    @Value("${kafka.consumer.max.poll.interval.ms}")
    private String kafkaConsumerMaxPollIntervalMs;

    @Value("${kafka.consumer.max.poll.records}")
    private String kafkaConsumerMaxPollRecords;

    @Value("${kafka.topic.telegram.fenix.bot.update.consumer.concurrency}")
    private Integer updateConsumerConcurrency;

    @Bean
    public ConsumerFactory<String, String> consumerFactory(KafkaProperties kafkaProperties) {
        return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties(), new StringDeserializer(), new JsonDeserializer<>(String.class));
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(KafkaProperties kafkaProperties) {

        kafkaProperties.getProperties().put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, kafkaConsumerMaxPollIntervalMs);
        kafkaProperties.getProperties().put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, kafkaConsumerMaxPollRecords);

        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
        factory.setConsumerFactory(consumerFactory(kafkaProperties));

        return factory;
    }

    @Bean
    public ConsumerFactory<String, Update> updateConsumerFactory(KafkaProperties kafkaProperties) {
        return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties(), new StringDeserializer(), new JsonDeserializer<>(Update.class));
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, Update> updateKafkaListenerContainerFactory(KafkaProperties kafkaProperties) {

        kafkaProperties.getProperties().put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, kafkaConsumerMaxPollIntervalMs);
        kafkaProperties.getProperties().put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, kafkaConsumerMaxPollRecords);

        ConcurrentKafkaListenerContainerFactory<String, Update> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE);
        factory.setConsumerFactory(updateConsumerFactory(kafkaProperties));
        factory.setConcurrency(updateConsumerConcurrency);

        return factory;
    }

}

application.properties

spring.kafka.bootstrap-servers=${kafka.Host}:${kafka.port}
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.group-id=postfenix
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer

Comment résoudre ce problème et laisser Kafka désérialiser les anciens messages dans les nouveaux?

MIS À JOUR

Ceci est mon auditeur

@Component
public class UpdateConsumer {

    @KafkaListener(topics = "${kafka.topic.update}", containerFactory = "updateKafkaListenerContainerFactory")
    public void onUpdateReceived(ConsumerRecord<String, Update> consumerRecord, Acknowledgment ack) {

        //do some logic here

        ack.acknowledge();
    }

}
7
alexanoid

Voir la documentation .

À partir de la version 2.1, les informations de type peuvent être transmises dans des en-têtes d'enregistrement, ce qui permet de gérer plusieurs types. De plus, le sérialiseur/désérialiseur peut être configuré à l'aide des propriétés Kafka.

JsonSerializer.ADD_TYPE_INFO_HEADERS (par défaut true); définie sur false pour désactiver cette fonctionnalité sur JsonSerializer (définit la propriété addTypeInfo).

JsonDeserializer.KEY_DEFAULT_TYPE; type de secours pour la désérialisation des clés si aucune information d'en-tête n'est présente.

JsonDeserializer.VALUE_DEFAULT_TYPE; type de secours pour la désérialisation des valeurs si aucune information d'en-tête n'est présente.

JsonDeserializer.TRUSTED_PACKAGES (par défaut Java.util, Java.lang); liste séparée par des virgules des modèles de package autorisés pour la désérialisation; * signifie désérialiser tout.

Par défaut, le sérialiseur ajoutera des informations de type aux en-têtes.

voir la documentation de démarrage .

De même, vous pouvez désactiver le comportement par défaut de JsonSerializer lors de l'envoi d'informations de type dans les en-têtes:

spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializerspring.kafka.producer.properties.spring.json.add.type.headers=false

Ou vous pouvez ajouter un mappage de type au convertisseur de messages entrants pour mapper le type source au type de destination.

[~ # ~] modifier [~ # ~]

Cela dit, quelle version utilisez-vous?

5
Gary Russell

Il convient de mentionner deux points clés.

  1. Il y a deux projets séparés pour le producteur et le consommateur.
  2. L'envoi de message (valeur) est alors un type d'objet plutôt qu'un type primitif.

Le problème est que l'objet de message producteur n'est pas disponible côté consommateur car ce sont deux projets distincts.

Deux ont surmonté ce problème, veuillez suivre les étapes de mention ci-dessous dans les applications Spring Boot Producer et Consumer.

---- App producteur -------------

** Classe de configuration du producteur **

import com.kafka.producer.models.Container;    
import org.Apache.kafka.clients.producer.ProducerConfig;
import org.Apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;
import Java.util.HashMap;
import Java.util.Map;

@Configuration
public class KafkaProducerConfig {

@Bean
public ProducerFactory<String, Container> producerFactory(){

    Map<String, Object> config = new HashMap<>();

config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);

    return new DefaultKafkaProducerFactory(config);
}

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

Remarque: le conteneur est l'objet personnalisé à publier dans une rubrique kafka.


** Classe de producteur **

import com.kafka.producer.models.Container;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class Producer {

private static final Logger LOGGER = LoggerFactory.getLogger(Producer.class);
private static final String TOPIC = "final-topic";

@Autowired
private KafkaTemplate<String, Container> kafkaTemplate;

public void sendUserMessage(Container msg) {
    LOGGER.info(String.format("\n ===== Producing message in JSON ===== \n"+msg));
    Message<Container> message = MessageBuilder
            .withPayload(msg)
            .setHeader(KafkaHeaders.TOPIC, TOPIC)
            .build();
    this.kafkaTemplate.send(message);
}
}

** Contrôleur de production **

import com.kafka.producer.models.Container;
import com.kafka.producer.services.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/message")
public class MessageController {

@Autowired
private Producer producer;

@PostMapping(value = "/publish")
public String sendMessageToKafkaTopic(@RequestBody Container containerMsg) {
    this.producer.sendUserMessage(containerMsg);
    return "Successfully Published !!";
}
}

Remarque: Le message de type Conteneur sera publié dans le kafka nom de rubrique: final-topic en tant que message JSON.

================================================== =============================

- Application grand public -

** Classe de configuration **

import com.kafka.consumer.models.Container;
import org.Apache.kafka.clients.consumer.ConsumerConfig;
import org.Apache.kafka.common.serialization.StringDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import Java.util.HashMap;
import Java.util.Map;

@Configuration
@EnableKafka
public class KafkaConsumerOneConfig {

@Bean
public ConsumerFactory<String, Container> consumerFactory(){
    JsonDeserializer<Container> deserializer = new JsonDeserializer<>(Container.class);
    deserializer.setRemoveTypeHeaders(false);
    deserializer.addTrustedPackages("*");
    deserializer.setUseTypeMapperForKey(true);

    Map<String, Object> config = new HashMap<>();

    config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
    config.put(ConsumerConfig.GROUP_ID_CONFIG, "group_one");
    config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
    config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
    config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
    config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, deserializer);

    return new DefaultKafkaConsumerFactory<>(config, new StringDeserializer(), deserializer);
}

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

}
}

Remarque: Ici, vous pouvez voir, au lieu d'utiliser JsonDeserializer () par défaut, nous devons utiliser JsonDeserializer personnalisé pour consommer les messages Json de type objet Conteneur de final-topic (nom du sujet).


** Service client **

import com.kafka.consumer.models.Container;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;
import Java.io.IOException;

@Service
public class ConsumerOne {

private final Logger LOGGER = LoggerFactory.getLogger(ConsumerOne.class);

@KafkaListener(topics = "final-topic", groupId = "group_one", containerFactory = "kafkaListenerContainerFactory")
public void consumeUserMessage(@Payload Container msg, @Headers MessageHeaders headers) throws IOException {
    System.out.println("received data in Consumer One ="+ msg.getMessageTypes());
}
}
2
Iroshan
jsonDeserializer.addTrustedPackages("*");

résolu mon problème pour spring-kafka-2.2.8.

0
jumping_monkey