Preface
The problem encountered in the previous development process is that the time in the message body is 8 hours less than the current time when the rabbitmq template is used to send messages. This is the time zone problem.
Let's talk about why it appears.
The previous configuration is as follows:
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMessageConverter(new Jackson2JsonMessageConverter()); template.setMandatory(true); ... return template; }
The message vo to be sent is as follows:
@Data public class TestVO { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date testDate; }
Then, the problem is that in the message body, the time is eight hours less than the current time.
{"testDate":"2019-12-27 05:45:26"}
Reason
This is how we use rabbitmq template:
@Autowired private RabbitTemplate rabbitTemplate; @Autowired private RedisRepository redisRepository; /** * send message * @param exchange Switch name * @param routingKey Routing key * @param msgMbject Message body, no need to serialize, will automatically serialize to json */ public void send(String exchange, String routingKey, final Object msgMbject) { CorrelationData correlationData = new CorrelationData(GUID.generate()); CachedMqMessageForConfirm cachedMqMessageForConfirm = new CachedMqMessageForConfirm(exchange, routingKey, msgMbject); redisRepository.saveCacheMessageForConfirms(correlationData,cachedMqMessageForConfirm); //Core code: here, the msgoobject sent out is actually a vo or dto. rabbitmqTemplate will automatically help us turn it into json rabbitTemplate.convertAndSend(exchange,routingKey,msgMbject,correlationData); }
As I explained in the note, rabbitmq will automatically make the transformation, and the transformation uses jackson.
Follow up the source code to find out:
org.springframework.amqp.rabbit.core.RabbitTemplate#convertAndSend @Override public void convertAndSend(String exchange, String routingKey, final Object object, @Nullable CorrelationData correlationData) throws AmqpException { // The convertMessageIfNecessary(object) is called here send(exchange, routingKey, convertMessageIfNecessary(object), correlationData); } //convertMessageIfNessary called: protected Message convertMessageIfNecessary(final Object object) { if (object instanceof Message) { return (Message) object; } // Get message converter return getRequiredMessageConverter().toMessage(object, new MessageProperties()); }
The code to get the message converter is as follows:
private MessageConverter getRequiredMessageConverter() throws IllegalStateException { MessageConverter converter = getMessageConverter(); if (converter == null) { throw new AmqpIllegalStateException( "No 'messageConverter' specified. Check configuration of RabbitTemplate."); } return converter; }
getMessageConverter is to get a field in rabbitmqTemplate class.
public MessageConverter getMessageConverter() { return this.messageConverter; } We just need to see where to assign it. Then I remember that the assignment in our business code: @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); //Here's the assignment... Almost forgotten template.setMessageConverter(new Jackson2JsonMessageConverter()); template.setMandatory(true); return template; }
Anyway, in general, rabbitmqTemplate will use our custom messageConverter to convert and send messages.
Time zone problem, good recurrence, source code in:
https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/jackson-demo
@Data public class TestVO { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date testDate; }
Test code:
@org.junit.Test public void normal() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); TestVO vo = new TestVO(); vo.setTestDate(new Date()); String value = mapper.writeValueAsString(vo); System.out.println(value); }
Output:
{"testDate":"2019-12-27 05:45:26"}
Solution
Specify default time zone configuration
@org.junit.Test public void specifyDefaultTimezone() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); SerializationConfig oldSerializationConfig = mapper.getSerializationConfig(); /** * New serialization configuration, to configure time zone */ String timeZone = "GMT+8"; SerializationConfig newSerializationConfig = oldSerializationConfig.with(TimeZone.getTimeZone(timeZone)); mapper.setConfig(newSerializationConfig); TestVO vo = new TestVO(); vo.setTestDate(new Date()); String value = mapper.writeValueAsString(vo); System.out.println(value); }
Annotate field
@Data public class TestVoWithTimeZone { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date testDate; } //Here, we add timezone and manually specify the time zone configuration. //Test code: @org.junit.Test public void specifyTimezoneOnField() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); TestVoWithTimeZone vo = new TestVoWithTimeZone(); vo.setTestDate(new Date()); String value = mapper.writeValueAsString(vo); System.out.println(value); }
Both of the above outputs are correct.
There is no analysis of the source code. To put it simply, when serializing, there will be a serialization configuration. This configuration consists of two parts: default configuration + customized configuration of this class. Custom configuration overrides the default configuration.
Our second method is to modify the default configuration; the third method is to use the custom configuration to override the default configuration.
jackson is also very important, especially for spring cloud, feign, restTemplate, and interested students of httpmessageConverter in Spring MVC. Go to the following place.
If you are interested in the processing of JsonFormat, you can see the following:
Com.fasterxml.jackson.annotation.jsonformat.value × value (com.fasterxml.jackson.annotation.jsonformat) (make a breakpoint here, and then run a test to get there)
summary
I almost forgot that our solution to the problem of rabbitmq template is:
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); ObjectMapper mapper = new ObjectMapper(); SerializationConfig oldSerializationConfig = mapper.getSerializationConfig(); /** * New serialization configuration, to configure time zone */ String timeZone = environment.getProperty(CadModuleConstants.SPRING_JACKSON_TIME_ZONE); SerializationConfig newSerializationConfig = oldSerializationConfig.with(TimeZone.getTimeZone(timeZone)); mapper.setConfig(newSerializationConfig); Jackson2JsonMessageConverter messageConverter = new Jackson2JsonMessageConverter(mapper); template.setMessageConverter(messageConverter); template.setMandatory(true); ...Set up callback What? return template; }