In the message sent by rabbitmq template, the Date type field is 8 hours later than the current time

Posted by lanjoky on Fri, 27 Dec 2019 12:43:39 +0100

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;
    }

Topics: Java RabbitMQ Junit less JSON