weidox
weidox

Reputation: 31

@RabbitListener strange behavior with containerFactory

RabbitMQ messages are arriving with type information:

headers:    
__ContentTypeId__:  java.lang.Object
__TypeId__: java.util.ArrayList

I have these 2 container factory beans in code - default one uses converter, another not:

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    final Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
    factory.setMessageConverter(messageConverter);
    return factory;
}

@Bean
public SimpleRabbitListenerContainerFactory rawContainerFactory(ConnectionFactory connectionFactory) {
    final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    return factory;
}

Scenario 1. A listener.

@RabbitListener(queues = "#{@myQueueBean}")
public void persistMessage(Message message) {
    .....
}

As I check what arrives into listener, message.payload is ArrayList, with HashMaps inside. This looks ok, seems to be using rabbitListenerContainerFactory as a default.

Scenario 2. I change to use containerFactory without converter:

@RabbitListener(queues = "#{@myQueueBean}", containerFactory = "rawContainerFactory")
public void persistMessage(Message message) {
    .....
}

As I check what arrives, message.payload is now byte[] with contents of message. Again looks ok.

Scenario 3. I change to explicitly use the default containerFactory:

@RabbitListener(queues = "#{@myQueueBean}", containerFactory = "rabbitListenerContainerFactory")
public void persistMessage(Message message) {
    .....
}

As I check what arrives, message.payload is again byte[] with contents of message. This sounds unexpected, I hoped for ArrayList as in Scenario 1.

Now I comment out messageConverter in first bean:

@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
//  Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
//  factory.setMessageConverter(messageConverter);
    return factory;
}

@Bean
public SimpleRabbitListenerContainerFactory rawContainerFactory(ConnectionFactory connectionFactory) {
    final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    return factory;
}

Scenario 4. Listener again

@RabbitListener(queues = "#{@myQueueBean}")
public void persistMessage(Message message) {
    .....
}

As I check what arrives, I expect message.payload as byte[] (default containerfactory to be used, without converter like Scenario 2), but actually I'm getting a String of comma separated numbers. This sounds wrong.

Scenario 5. I change to explicitly use the default containerFactory without converter:

@RabbitListener(queues = "#{@myQueueBean}", containerFactory = "rabbitListenerContainerFactory")
public void persistMessage(Message message) {
    .....
}

As I check what arrives, message.payload is now byte[] with contents of message. This is like Scenario 2 and looks ok.

Scenario 6. Use "rawContainerFactory", result is as now logically expected a byte[] array.

Scenario 7. Use "rawContainerFactory" and add Jackson converter to it. Result is ArrayList. Quite unexpected after Scenario 3 results.

Results above look strongly inconsistent. There is some magic going with the "rabbitListenerContainerFactory" which prompts me to say - forget it and use own custom factories in explicit configuration. Unless someone manages to explain this magic.

Update: I cannot reproduce the issue anymore, and that is yet another magic why it's gone. Both Scenario 3 and Scenario 4 issues are gone, and I literally did nothing except adding one more testcase to send message to queue using RabbitTemplate (but still was using real big jsons from queue for testing). And before and now I was testing with both Eclipse and Idea on the same physical project code on the disk. One additional note, all my listeners in my samples are using non-generic Message type parameter. I did that explicitly because adding any specific type to some of the scenarios (that could be Scenario 1 or 4 if I remember correctly) - even the < ? > was changing it's behavior to the point where message would not even reach my listener and there would be an error in logs saying something "cannot convert START_OBJECT to some-type".

Upvotes: 0

Views: 2848

Answers (1)

Gary Russell
Gary Russell

Reputation: 174729

I can't reproduce the behavior you cite. Everything works as expected for me.

@SpringBootApplication
public class So47393130Application {

    public static void main(String[] args) {
        SpringApplication.run(So47393130Application.class, args).close();
    }

    @Bean
    public ApplicationRunner runner(RabbitTemplate rabbitTemplate) {
        return args -> {
            rabbitTemplate.convertAndSend("foo", "", new ArrayList<>(Arrays.asList("foo", "bar")));
            Thread.sleep(5_000);
        };
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        final Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter();
        factory.setMessageConverter(messageConverter);
        return factory;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rawContainerFactory(ConnectionFactory connectionFactory) {
        final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        return factory;
    }

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

    @Bean
    public Queue foo() {
        return new Queue("foo");
    }

    @Bean
    public Queue bar() {
        return new Queue("bar");
    }

    @Bean
    public Queue baz() {
        return new Queue("baz");
    }

    @Bean
    public FanoutExchange exchange() {
        return new FanoutExchange("foo");
    }

    @Bean
    public Binding fooBinding() {
        return BindingBuilder.bind(foo()).to(exchange());
    }

    @Bean
    public Binding barBinding() {
        return BindingBuilder.bind(bar()).to(exchange());
    }

    @Bean
    public Binding bazBinding() {
        return BindingBuilder.bind(baz()).to(exchange());
    }

    @RabbitListener(queues = "foo")
    public void foo(Message<?> in) {
        System.out.println("foo:" + in.getPayload());
    }

    @RabbitListener(queues = "bar", containerFactory = "rawContainerFactory")
    public void bar(Message<?> in) {
        System.out.println("bar:" + in.getPayload());
    }

    @RabbitListener(queues = "baz", containerFactory = "rabbitListenerContainerFactory")
    public void baz(Message<?> in) {
        System.out.println("baz:" + in.getPayload());
    }

}

Perhaps you can set a breakpoint in your listener and look down the stack to see exactly which converter is wired into the listener adapter.

Upvotes: 1

Related Questions