【问题标题】:spring-rabbit JSON deserialization for ArrayList contentsArrayList 内容的 spring-rabbit JSON 反序列化
【发布时间】:2018-09-09 00:20:26
【问题描述】:

我正在使用带有 JSON 消息序列化的 rabbitmq 的 Spring-boot。使用 Direct-Reply-to 功能的回复无法反序列化 java.util.List 容器中的类。

Jackson2JsonMessageConverter.fromMessage() 中使用我的调试器,MessageProperties 表明__TypeID__ 正确设置为java.util.ArrayList。但是 __ContentTypeId__java.lang.Object 是不正确的,因为我期待 FooDto (我假设......)。 异常消息是: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to FooDto

请注意,我使用的是 spring-rabbit 1.7.3 而不是 v2.0,因此不能将 ParameterizedTypeReference 用于 rabbitTemplate.convertSendAndReceiveAsType() 方法。

我尝试使用DefaultClassMapperDefaultJackson2JavaTypeMapper(TypePrecedence 在TYPE_IDINFERRED 下测试)但没有成功:

private DefaultJackson2JavaTypeMapper classMapper() {
    final DefaultJackson2JavaTypeMapper classMapper = new DefaultJackson2JavaTypeMapper();
    final Map<String, Class<?>> idClassMapping = new HashMap<>();
    idClassMapping.put(FooDto.class.getSimpleName(), FooDto.class);
    classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}

现在的例外是: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to FooDto

到目前为止,我的解决方法是使用原始类型的数组,即FooDto[]

库版本: - 弹簧启动 1.5.6 - 兔MQ:3.7.4 - 春兔 1.7.3

Maven pom.xml:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
    <relativePath />
</parent>

<dependencies>
    <dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    </dependency>    
</dependencies>

有效的 Pom:

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-amqp</artifactId>
    <version>1.7.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>1.7.3.RELEASE</version>
</dependency>

RabbitMQ 配置:

@Configuration
@EnableRabbit
public class MessagingConfiguration implements ShutdownListener {

    // FIXME: List<FooDto> in the direct-reply response contains  ArrayList<Object> due to __ContentTypeID__ == SimpleObject/Object .  __TypeID__ is correctly ArrayList

    @Bean
    public List<Declarable> bindings() {
    final List<Declarable> declarations = new ArrayList<>();
        final FanoutExchange exchange = new FanoutExchange("fx", true, false);
        final Queue queue = QueueBuilder.durable("orders").build();
        declarations.add(exchange);
        declarations.add(queue);
        declarations.add(BindingBuilder.bind(queue).to(exchange));
        return declarations;
    }

    // @Bean
    // public DefaultClassMapper classMapper() {
    // DefaultClassMapper classMapper = new DefaultClassMapper();
    // Map<String, Class<?>> idClassMapping = new HashMap<>();
    // idClassMapping.put("FooDto", FooDto.class);
    // java.util.List<FooDto>
    // classMapper.setIdClassMapping(idClassMapping);
    // return classMapper;
    // }
    //
    // @Bean
    // public DefaultClassMapper classMapper() {
    // final DefaultClassMapper typeMapper = new DefaultClassMapper();
    // // typeMapper.setDefaultType(new ArrayList<FooDto>().getClass());
    // typeMapper.setDefaultType(FooDto[].class);
    // return typeMapper;
    // }

    @Bean
    public Jackson2JsonMessageConverter jsonConverter() {
        // https://stackoverflow.com/questions/40491628/jackson-configuration-to-consume-list-of-records-in-rabbitmq
        // https://github.com/FasterXML/jackson-core/issues/295
        final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
        converter.setTypePrecedence(TypePrecedence.TYPE_ID);
        // converter.setClassMapper(classMapper());
        return converter;
    }

    @ConditionalOnProperty(name = "consumer", havingValue = "true")
    @Bean
    public ConsumerListener listenerConsumer() {
        return new ConsumerListener();
    }

    @ConditionalOnProperty(name = "producer", havingValue = "true")
    @Bean
    public ProducerListener listenerProducer() {
        return new ProducerListener();
    }

    @Bean
    public RabbitAdmin rabbitAdmin(final CachingConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            final ConnectionFactory connectionFactory) {
        // Setting the annotation @RabbitListener to use Jackson2JsonMessageConverter
        final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(jsonConverter());
        factory.setConcurrentConsumers(5);
        factory.setMaxConcurrentConsumers(5);
        return factory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jsonConverter()); // convert all sent messages to JSON
        rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
        rabbitTemplate.setReceiveTimeout(TimeUnit.SECONDS.toMillis(3));
        return rabbitTemplate;
    }

    @Override
    public void shutdownCompleted(final ShutdownSignalException arg0) {
    }
}

侦听器在交换“fx”上使用包含来自队列“订单”的 MyQuery 对象的消息:

public class ConsumerListener {
    private static final Logger log = LoggerFactory.getLogger(ConsumerListener.class);

    @RabbitListener(queues = { "orders" })
    public FooDto[] receiveMessage(final MyQuery query) {
        log.info(query);
        List<FooDto> response = new ArrayList<>();
        response.add(new FooDto());
        response.add(new FooDto());
        response.add(new FooDto());
        return response;
    }
}

向 Exchange 发送消息时使用的 POJO: 类 MyQuery { 私有字符串内容=“测试”;

    public MyQuery();

    public String toString() {
        return content;
    }
}

用于响应的 POJO: 类FooDto { 私有字符串内容 = "foo";

    public FooDto();

    public String toString() {
        return content;
    }
}

【问题讨论】:

    标签: json spring-boot rabbitmq spring-rabbit


    【解决方案1】:

    你的听众有些奇怪;它的返回类型为void,但您返回一个列表。

    也就是说,我认为问题是由于类型擦除造成的。

    自定义 ClassMapper 不会有帮助,因为那只是用于顶级课程。

    但是,您应该能够构造自定义 Jackson2JavaTypeMapper 以创建更复杂的类型。如果没有类映射器,则查询类型映射器。 See here.

    我现在不在电脑前,但如果你不明白,我可以明天看看。

    编辑

    这是一个如何自定义转换器的示例...

    @SpringBootApplication
    public class So49566278Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So49566278Application.class, args);
        }
    
        @Bean
        public ApplicationRunner runner(RabbitTemplate template) {
            template.setReplyTimeout(60_000);
            return args -> {
                @SuppressWarnings("unchecked")
                List<Foo> reply = (List<Foo>) template.convertSendAndReceive("so49566278", "baz");
                System.out.println(reply);
                Foo foo = reply.get(0);
                System.out.println(foo);
            };
        }
    
        @RabbitListener(queues = "so49566278")
        public List<Foo> handle(String in) {
            return Collections.singletonList(new Foo(in));
        }
    
        @Bean
        public Queue queue() {
            return new Queue("so49566278", false, false, true);
        }
    
        @Bean
        public MessageConverter converter() {
            Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
            converter.setJavaTypeMapper(new DefaultJackson2JavaTypeMapper() {
    
                @Override
                public JavaType toJavaType(MessageProperties properties) {
                    JavaType javaType = super.toJavaType(properties);
                    if (javaType instanceof CollectionLikeType) {
                        return TypeFactory.defaultInstance()
                                .constructCollectionLikeType(List.class, Foo.class);
                    }
                    else {
                        return javaType;
                    }
                }
    
            });
            return converter;
        }
    
        public static class Foo {
    
            private String bar;
    
            public Foo() {
                super();
            }
    
            public Foo(String bar) {
                this.bar = bar;
            }
    
            public String getBar() {
                return this.bar;
            }
    
            public void setBar(String bar) {
                this.bar = bar;
            }
    
            @Override
            public String toString() {
                return "Foo [bar=" + this.bar + "]";
            }
    
        }
    
    }
    

    【讨论】:

    • 我用如何自定义类型映射器的示例编辑了我的答案。
    • 我打开了AMQP-807 来解决这个问题。
    • 我的 ConsumerListener 的返回类型不适合在此处发布。对困惑感到抱歉。此处的代码已更新为实际内容。非常感谢您的代码示例。有效!我期待 AMQP-807 的通用修复解决方案。
    • @GaryRussell 有没有办法通过 spring boot 属性设置转换器?
    • 不要在评论中提出新问题。它不能帮助人们找到答案。总是问一个新问题。不,它必须是一个 bean,并且 boot 会将它连接进去。
    【解决方案2】:

    这种方式适用于集合没有问题。我觉得很简单:https://stackoverflow.com/a/67130566/10746857

    【讨论】:

      猜你喜欢
      • 2014-09-05
      • 2019-05-02
      • 2015-07-11
      • 1970-01-01
      • 2013-05-07
      • 1970-01-01
      • 2023-01-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多