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 1:

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