Search code examples
javaspringrabbitmqspring-amqpspring-rabbit

How to change message type detection strategy?


I’m using a custom RabbitMQ client in application A and Spring Amqp Library in application B. I’ve faced with a problem: How to decide witch @RabbitListener to use for different message types?

Problem: I send custom messages from app A to app B. In a message I set custom header “type” - it’s not a default property and not a default Spring Amqp Header (which is “_ _ TypeId _ _”) - just a new custom header.

In application B (spring amqp) I have to decide which listener to use. As I understood, Spring Amqp uses “_ _ TypeId _ _” as a default mechanism of “message type detection strategy” (I don’t know how to call it properly), but I wanna use my own “strategy”.

I’ve found the next trick, but it seems quite strange and not obvious:

private void determineMessageType(Message message) {
    MessageProperties props = message.getMessageProperties();
    Map<String, Object> headers = props.getHeaders();
    final String type = String.valueOf(headers.get("type"));
    if ("popularity-report".equals(type)) {
        props.getHeaders().put("__TypeId__",
                PopularityReportCommand.class.getName());
    } 
}

Can I use custom type detection strategy for Spring Amqp application somehow? Or how to solve these the problem properly in Spring Amqp?


Solution

  • The mentioned _ _ TypeId _ _ is used only for JSON message converters. So, if that really a case for you in the part what you really send from that producer, then you can take a look into the AbstractJackson2MessageConverter.setJavaTypeMapper(Jackson2JavaTypeMapper). The default one DefaultJackson2JavaTypeMapper has a property like this:

    public String getClassIdFieldName() {
        return DEFAULT_CLASSID_FIELD_NAME;
    }
    

    Which really is that mentioned above name:

    public static final String DEFAULT_CLASSID_FIELD_NAME = "__TypeId__";
    

    so, if you are able to extend this DefaultJackson2JavaTypeMapper and override that getter for your custom header mapper, then that Jackson2JsonMessageConverter can convert properly incoming JSON data into a desired type presented by your custom header. Then @RabbitListener would accept the value.

    But you still need to be sure that typePrecedence on that mapper is set to TYPE_ID:

    /**
     * Set the precedence for evaluating type information in message properties.
     * When using {@code @RabbitListener} at the method level, the framework attempts
     * to determine the target type for payload conversion from the method signature.
     * If so, this type is provided in the
     * {@link MessageProperties#getInferredArgumentType() inferredArgumentType}
     * message property.
     * <p>
     * By default, if the type is concrete (not abstract, not an interface), this will
     * be used ahead of type information provided in the {@code __TypeId__} and
     * associated headers provided by the sender.
     * <p>
     * If you wish to force the use of the  {@code __TypeId__} and associated headers
     * (such as when the actual type is a subclass of the method argument type),
     * set the precedence to {@link Jackson2JavaTypeMapper.TypePrecedence#TYPE_ID}.
     *
     * @param typePrecedence the precedence.
     * @since 1.6
     */
    public void setTypePrecedence(TypePrecedence typePrecedence) {
    

    Otherwise it consults the @RabbitListener method signature.

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