8

I store thread-local rabbit message data in an MDC. I would like to clear the old and add new context data for an incoming rabbit message, like reading certain values from the headers or reading the rabbit message payload as a byte[]. Unfortunately I often see exceptions happening prior to the message hitting my @RabbitHandler annotated methods. Is there an earlier entry-point that I can hook into to establish this context? I don't know what happens before deserialization occurs, but ideally I'd like access to the message before attempting to deserialize it. Perhaps there's an onMessageReceived(byte[] message, Map headers) method hook somewhere. The earlier in the call stack the better.

kinbiko
  • 2,066
  • 1
  • 28
  • 40

2 Answers2

2

The @RabbitHandler is populated by the AbstractRabbitListenerContainerFactory which can be supplied with the custom MessageConverter: https://docs.spring.io/spring-amqp/docs/2.0.1.RELEASE/reference/html/_reference.html#message-converters. Its fromMessage() is called from the MessagingMessageListenerAdapter.toMessagingMessage(). And that is done in the MessagingMessageListenerAdapter.onMessage(). That's indeed very early place you can hook. And you really there still have a raw org.springframework.amqp.core.Message object without any conversion and with all available headers and properties.

Well, you also can inject:

/**
 * @param afterReceivePostProcessors the post processors.
 * @see AbstractMessageListenerContainer#setAfterReceivePostProcessors(MessagePostProcessor...)
 */
public void setAfterReceivePostProcessors(MessagePostProcessor... afterReceivePostProcessors) {

With similar reason you are requesting.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Looks like I'll have to do it in the converters after all. Thanks, I'll give this a try. Any idea if the thread that runs `MessagingMessageListenerAdapter.onMessage()` is the same that would execute the `@RabbitHandler` call? – kinbiko Jan 18 '18 at 14:41
  • 1
    Indeed it is definitely the same. Just follow the call stack I mentioned. – Artem Bilan Jan 18 '18 at 14:42
  • I would suggest the the `setAfterReceivePostProcessors()` is the easiest route. – Gary Russell Jan 18 '18 at 15:11
0

A solution is to use a SimpleMessageListenerContainer instead of @RabbitHandler annotations, and to use a custom message listener adapter.

Example:

@Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
                                         MessageListenerAdapter listenerAdapter) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames(queueName);
    container.setMessageListener(listenerAdapter); // custom listener
    container.setMessageConverter(null); // disable default conversion
    return container;
}

@Bean
MessageListenerAdapter listenerAdapter() {
    RawMessageDelegate delegate = new RawMessageDelegate();
    return new MessageListenerAdapter(delegate);
}

public class RawMessageDelegate {

    void handleMessage(Message message) {
        byte[] body = message.getBody();
        MessageProperties properties = message.getMessageProperties();
        Map<String, Object> headers = properties.getHeaders();
        // handle raw data
    }

}
ESala
  • 6,878
  • 4
  • 34
  • 55