Hunteerq
Hunteerq

Reputation: 280

Can't convert Message object from RabbitMQ to java class

I created my RabbitListener to get messages from RabbitMQ queue.

My rabbitMQ message:

Properties  
priority:   0
delivery_mode:  2
headers:    
__TypeId__: com.kmb.bank.models.Transfer
content_encoding:   UTF-8
content_type:   application/json
Payload
356 bytes
Encoding: string


{"userAccountNumber":"1111444422221111","title":"123","recipientName":"123","recipientAccountNumber":"1234123412341234","amount":123.0,"localDateTime":{"nano":526106200,"year":2018,"monthValue":11,"dayOfMonth":29,"hour":20,"minute":43,"second":0,"month":"NOVEMBER","dayOfWeek":"THURSDAY","dayOfYear":333,"chronology":{"id":"ISO","calendarType":"iso8601"}}}

My Listener method:

@Autowired
private Jackson2JsonMessageConverter jackson2JsonMessageConverter;

@RabbitListener(queues = "kolejka")
public void listen(Message message) {
    try {
        Transfer transfer = (Transfer) jackson2JsonMessageConverter.fromMessage(message);
        log.info(transfer);
    } catch (Exception e) {
        log.debug("Error thrown while listening + " + e.getMessage());
    }

}

Bean config: @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); }

@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
    return new Jackson2JsonMessageConverter(objectMapper());
}

And Transfer class:

package com.kmb.transactionlogger.models;

@AllArgsConstructor
public class Transfer {
    @Getter @Setter
    private String userAccountNumber;
    @Getter @Setter
    private String title;
    @Getter @Setter
    private String recipientName;
    @Getter @Setter
    private String recipientAccountNumber;
    @Getter @Setter
    private double amount;
    @Getter @Setter
    private LocalDateTime localDateTime;

}

Unfortunately the exception is being thrown while converting from Message to Transfer transfer object.

Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [com.kmb.bank.models.Transfer]
2018-11-29 20:47:01.615  WARN 13688 --- [cTaskExecutor-1] ingErrorHandler$DefaultExceptionStrategy : Fatal message conversion error; message rejected; it will be dropped or routed to a dead letter exchange, if so configured: (Body:'{"userAccountNumber":"1111444422221111","title":"123","recipientName":"123","recipientAccountNumber":"1234123412341234","amount":123.0,"localDateTime":{"nano":599669800,"year":2018,"monthValue":11,"dayOfMonth":29,"hour":20,"minute":47,"second":1,"month":"NOVEMBER","dayOfWeek":"THURSDAY","dayOfYear":333,"chronology":{"id":"ISO","calendarType":"iso8601"}}}' MessageProperties [headers={__TypeId__=com.kmb.bank.models.Transfer}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=bank, receivedRoutingKey=, deliveryTag=2, consumerTag=amq.ctag-sLNqW-_WhDWLWJk6MCQcjg, consumerQueue=kolejka])

Whole message log: https://pastebin.com/raw/47Lq7dYD

Upvotes: 4

Views: 11930

Answers (3)

Thang Le
Thang Le

Reputation: 1449

You need to set message converter for both producer and consumer. So when producer sends your object, it will auto convert the object to Json and when consumer receives your Json message, it will auto convert the Json message to original object.

   // 1. producer converter config

   @Bean("Jackson2JsonMessageConverter")
      Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
         return new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory, Jackson2JsonMessageConverter jackson2JsonMessageConverter) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
        return rabbitTemplate;
    }

// 2. consumer converter config

@RabbitListener(queues = RabbitMQConfig.queueName, messageConverter = "Jackson2JsonMessageConverter")
public void receiveMessage(Object message) {
    if (message instanceof Object) {
        LOG.info("receiveMessage" + message.toString());
    }
}


// 3. send message which is an Java Object

rabbitTemplate.convertAndSend("topicExchangeName", "routing.key.test", Object);

POM:

  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
    </dependency>

Full example: gs-messaging-rabbitmq

Upvotes: 0

Andrew Mititi
Andrew Mititi

Reputation: 1143

I had this issue and when i tried using custom message converters, some would throw errors while trying to resolve to the class specified on the header by the spring producer. I ended up converting the object class to a json string using ObjectMapper class.

One thing i would like to point is that, doing that will send the content to rabbit MQ as a stringified JSON. So you can receive it on the consumer class as a string, map it to another custom class using objectMapper.read(stringType, mappedToClass).

Another advantage i have gotten is that i can directly read it in other consumers written in python or nodejs

In summary In springboot when sending,
rabbitTemplate.convertAndSend(exchange, key, objectMapper.writeValueAsString()).

In springboot consumer when reading to a custom object that does not have to match the consumer object.
CustomObj obj = objectMapper.read(incomingString, CustomObj.class).

A point to note is that, make sure to ignoreUnknown fields in the custom object class,

Upvotes: 0

Gary Russell
Gary Russell

Reputation: 174484

You have to set up the type mapping in the receiving Jackson2JsonMessageConverter's type mapper to map to a different class.

Generally the sender would map its class to a token, e.g. transfer and the receiver will map that to its version of Transfer.

Alternatively, the framework will infer the type from the parameter if you use public void listen(Transfer transfer), and if you wire the converter into the listener container factory.

If it's a Spring Boot application, that wiring will happen automatically.

Upvotes: 3

Related Questions