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