无法将来自RabbitMQ的消息对象转换为Java类

4
我创建了一个RabbitListener来从RabbitMQ队列中获取消息。
我的RabbitMQ消息:
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"}}}

我的监听器方法:
@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 public ObjectMapper objectMapper() { return new ObjectMapper(); }
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
    return new Jackson2JsonMessageConverter(objectMapper());
}

转移类:

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;

}

很遗憾,在将消息转换为传输对象时出现了异常。
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])

完整的消息记录: https://pastebin.com/raw/47Lq7dYD

能否提供更详细的日志(堆栈跟踪)? - NiVeR
我认为这是一个问题,因为Transfer类和包与__TypeId__不同,但我不知道解决方案。 - Hunteerq
您需要在接收 jackson2JsonMessageConverter 的类型映射器中设置类型映射以映射到不同的类。或者,如果您将转换器连接到侦听器容器工厂,则框架将从参数中推断类型,例如使用 public void listen(Transfer transfer)。如果是 Boot 应用程序,这将自动发生。 - Gary Russell
3个回答

3
您需要在接收的Jackson2JsonMessageConverter的类型映射器中设置类型映射,以将其映射到不同的类。通常情况下,发送方会将其类映射为一个标记,例如transfer,而接收者会将其映射为其版本的Transfer。
另外,如果您使用public void listen(Transfer transfer)并将转换器连接到监听器容器工厂,则框架将从参数推断出类型。如果它是Spring Boot应用程序,则该连接将自动发生。

1
它有一些帮助,但是现在我遇到了与LocalDateTime相关的错误:Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.LocalDateTime (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) - Hunteerq
您需要将自定义的Jackson反序列化器应用于ObjectMapper - 这里有一个关于它的问题,其中包含答案:https://dev59.com/910a5IYBdhLWcg3wxrKM - Gary Russell

0

您需要为生产者和消费者都设置消息转换器。这样,当生产者发送对象时,它会自动将对象转换为Json格式;当消费者接收到您的Json消息时,它会自动将Json消息转换回原始对象。

   // 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>

完整示例: gs-messaging-rabbitmq


0

我曾经遇到这个问题,当我尝试使用自定义消息转换器时,有些会在尝试解析Spring生产者标头中指定的类时抛出错误。最终我使用ObjectMapper类将对象类转换为JSON字符串。

需要注意的一点是,这样做会将内容作为字符串化的JSON发送到Rabbit MQ。因此,您可以将其作为字符串接收到消费者类中,然后使用objectMapper.read(stringType, mappedToClass)将其映射到另一个自定义类。

我得到的另一个优势是,我可以直接在其他用pythonnodejs编写的消费者中读取它。

总之,在Springboot发送时,
rabbitTemplate.convertAndSend(exchange, key, objectMapper.writeValueAsString())

在Springboot消费者中读取不必匹配消费者对象的自定义对象时,
CustomObj obj = objectMapper.read(incomingString, CustomObj.class)

需要注意的一点是,在自定义对象类中确保ignoreUnknown字段。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接