Sune
Sune

Reputation: 41

Spring AMQP detect similar payload types to use in @RabbitHandler

I used to have a @RabbitListener that works fine like this:

@Component
public class AppointmentMessageListener {

    @RabbitListener(queues = "${rabbitmq.immediateQueueName}")
    public void receiveImmediateAppointmentMessage(AppointmentMessage appointmentMessage) {
        // Do something
    }
}

Now I want to have a different type of message on the same queue, and I tried it like the docs said:

@Component
@RabbitListener(queues = "${rabbitmq.immediateQueueName}")
public class AppointmentMessageListener {

    @RabbitHandler
    public void receiveImmediateAppointmentMessage(AppointmentMessage appointmentMessage) {
        // Do something
    }

    @RabbitHandler
    public void receiveImmediateAppointmentMessage(AppointmentDeleteMessage appointmentDeleteMessage) {
        // Do something else
    }
}

This doesn't work, and I get org.springframework.amqp.AmqpException: No method found for class java.util.LinkedHashMap.

The JSON looks like below, and the difference between the 2 types is only in the object structure. I don't control the structure of the message. Is there any way I can detect the correct type to use in my multimethod listener?

{
  "key":"event-message-resource.immediate",
  "creationTimestamp":1643804135376,
  "object": {
    // here is the object I am interested in
  }
}

I use the following configuration besides the exchange, queue and routing key declarables.:

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
        return new Jackson2JsonMessageConverter(objectMapper);
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                                               ConnectionFactory connectionFactory,
                                                                               AuthenticationRabbitListenerAdvice rabbitListenerAdvice) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        factory.setAdviceChain(combineAdvice(factory.getAdviceChain(), rabbitListenerAdvice));

        return factory;
    }

Upvotes: 0

Views: 1369

Answers (2)

Sune
Sune

Reputation: 41

Based on the answer from the revered Gary Russell and his answer on his post here, I managed to get it to work by subclassing the ClassMapper and deciding on the type based on the routing key.

@Component
@RequiredArgsConstructor
public class CustomClassMapper extends DefaultJackson2JavaTypeMapper {

    @Value("${rabbitmq.deleteRoutingKey}")
    private final String deleteRoutingKey;

    @NonNull
    @Override
    public Class<?> toClass(MessageProperties properties) {
        String routingKey = properties.getReceivedRoutingKey();
        if (deleteRoutingKey.equals(routingKey)) {
            return AppointmentDeleteMessage.class;
        }

        return AppointmentMessage.class;
    }
}

Then I set the classMapper in the messageConverter.

    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter(CustomClassMapper customClassMapper) {
        ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
        Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(objectMapper);
        messageConverter.setClassMapper(customClassMapper);

        return messageConverter;
    }

Upvotes: 1

Gary Russell
Gary Russell

Reputation: 174749

The framework can't infer the target type when using @RabbitHandler because it uses the type to select the method. The JSON has to be converted before the method selection is performed.

See https://docs.spring.io/spring-amqp/docs/current/reference/html/#Jackson2JsonMessageConverter-from-message

You need to use something in the message to determine which type to create.

Upvotes: 2

Related Questions