problem
Recently in use RabbitMq There was a problem when , It is clearly converted into json Send to mq Data in , What the consumer receives is a string of numbers, which is byte Array , But use mq It's normal for visual pages to view data , This situation has never been encountered before in the process of use , The situations encountered are as follows :
The code for the producer to send the message is as follows :
public void sendJsonStrMsg(String jsonStr){ rabbitTemplate.convertAndSend(JSON_QUEUE, jsonStr);}
The consumer code is shown below :
@RabbitHandler@RabbitListener(queuesToDeclare = {@Queue(name=ProducerService.JSON_QUEUE, durable = "true")},containerFactory = "prefetchTenRabbitListenerContainerFactory")public void listenJsonMsg(String msg, Channel channel, Message message){ log.debug("json String type message >>>>{}",msg);}
Introduced containerFactory As shown below :
@Beanpublic RabbitListenerContainerFactory<SimpleMessageListenerContainer> prefetchTenRabbitListenerContainerFactory() { SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); MessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); //<x> factory.setConnectionFactory(connectionFactory); factory.setMessageConverter(jackson2JsonMessageConverter); return factory;}
Notice that the code says <x>
The place of , Here's the key to solving the problem .
Solution
Let's start with the solution , And the reason , The solution is simple , With the above code unchanged , Just inject the following bean that will do :
@Beanpublic MessageConverter jackson2JsonMessageConverter(){ return new Jackson2JsonMessageConverter("*");}
The solution is that simple , You just need to inject Jackson2JsonMessageConverter That's all right. , But what's the principle ? And look back .
Principle analysis
About the explanation of the principle, we from the source level , After all, there is no secret in front of the source code .
Producer source code analysis
First, let's see how we send messages to mq Methods rabbitTemplate.convertAndSend(JSON_QUEUE, jsonStr)
, After going in this way , After the overload method, we finally arrive at the method shown below :
@Overridepublic void convertAndSend(String exchange, String routingKey, final Object object, @Nullable CorrelationData correlationData) throws AmqpException { send(exchange, routingKey, convertMessageIfNecessary(object), correlationData);}
Focus on convertMessageIfNecessary
Method , The name of the method has already told us quite frankly , Transform the message if necessary , Let's take a look at this method :
protected Message convertMessageIfNecessary(final Object object) { if (object instanceof Message) { //<1> return (Message) object; } return getRequiredMessageConverter().toMessage(object, new MessageProperties()); //<2>}
<1>
If you want to send it to mq The object of is Message Example , So it's directly converted to Message Type return , Otherwise, you get MessageConverter
After the call toMessage()
Method returns Message object .
Let's take a look first RabbitTemplate#getRequiredMessageConverter()
, As shown below :
private MessageConverter getRequiredMessageConverter() throws IllegalStateException { MessageConverter converter = getMessageConverter(); if (converter == null) { throw new AmqpIllegalStateException( "No 'messageConverter' specified. Check configuration of RabbitTemplate."); } return converter;}public MessageConverter getMessageConverter() { return this.messageConverter; //<1>}
<1>
The code at indicates the need for a messageConverter
object , I am here RabbitTemplate
Source code found the corresponding set Method , Because we didn't call set Method to get the settings messageConverter Value , Then you need to find the default value , The default settings are shown in the following code :
/** * Convenient constructor for use with setter injection. Don't forget to set the connection factory. */public RabbitTemplate() { initDefaultStrategies(); // NOSONAR - intentionally overridable; other assertions will check}/** * Set up the default strategies. Subclasses can override if necessary. Set default policy , Subclasses can override when necessary */protected void initDefaultStrategies() { setMessageConverter(new SimpleMessageConverter());}public void setMessageConverter(MessageConverter messageConverter) { this.messageConverter = messageConverter;}
Let's go in SimpleMessageConverter#toMessage()
Method to see how to put a java Object conversion to Message Object's , It's a pity that SimpleMessageConverter No toMessage Method , Let's take a look at SimpleMessageConverter Succession , The class diagram is as follows :
After removing some useless interfaces and classes , The rest of the class diagram is shown below , Go up the class diagram , stay AbstractMessageConverter
Found in the toMessage Method :
@Overridepublic final Message toMessage(Object object, @Nullable MessageProperties messagePropertiesArg, @Nullable Type genericType) throws MessageConversionException { MessageProperties messageProperties = messagePropertiesArg; if (messageProperties == null) { messageProperties = new MessageProperties(); } Message message = createMessage(object, messageProperties, genericType); //<1> messageProperties = message.getMessageProperties(); if (this.createMessageIds && messageProperties.getMessageId() == null) { messageProperties.setMessageId(UUID.randomUUID().toString()); } return message;}
There is nothing in this method that we need , Continue to look at <1>
The way to do it , The method needs to return to SimpleMessageConverter
in :
@Overrideprotected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { byte[] bytes = null; if (object instanceof byte[]) { //<1> bytes = (byte[]) object; messageProperties.setContentType(MessageProperties.CONTENT_TYPE_BYTES); //<1.x> } else if (object instanceof String) { //<2> try { bytes = ((String) object).getBytes(this.defaultCharset); } catch (UnsupportedEncodingException e) { throw new MessageConversionException( "failed to convert to Message content", e); } messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);//<2.x> messageProperties.setContentEncoding(this.defaultCharset); } else if (object instanceof Serializable) { //<3> try { bytes = SerializationUtils.serialize(object); } catch (IllegalArgumentException e) { throw new MessageConversionException( "failed to convert to serialized Message content", e); } messageProperties.setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT);//<3.x> } if (bytes != null) { messageProperties.setContentLength(bytes.length); return new Message(bytes, messageProperties); } throw new IllegalArgumentException(getClass().getSimpleName() + " only supports String, byte[] and Serializable payloads, received: " + object.getClass().getName()); //<4>}
This method is more interesting , stay <1>
、<2>
、<3>
Three places respectively determine whether the message sent is byte[]
、String
、Serializable
, And after judgment, the message will be .........