rabbitmq template發送的消息中,Date類型字段比當前時間晚8小時

前言

前一陣開發過程遇到的問題,用的 rabbitmq template 發送消息,消息body裡的時間是比當前時間少了8小時的,這種一看就是時區問題了。

就說說為什麼出現吧。

之前的配置是這樣的:

<code>@Bean    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {        RabbitTemplate template = new RabbitTemplate(connectionFactory);        template.setMessageConverter(new Jackson2JsonMessageConverter());        template.setMandatory(true);              ...        return template;    }/<code>

要發送出去的消息vo是這樣的:

<code>@Datapublic class TestVO {    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private Date testDate;}/<code>

然後,出現的問題就是,消息體裡,時間比當前時間少了8個小時。

<code>{"testDate":"2019-12-27 05:45:26"}/<code>

原因

我們是這麼使用rabbitmq template的:

<code>@Autowired    private RabbitTemplate rabbitTemplate;    @Autowired    private RedisRepository redisRepository;    /**     * 發送消息     * @param exchange 交換機名稱     * @param routingKey 路由鍵     * @param msgMbject 消息體,無需序列化,會自動序列化為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);        //核心代碼:這裡,發送出去的msgObject其實就是一個vo或者dto,rabbitmqTemplate會自動幫我們轉為json        rabbitTemplate.convertAndSend(exchange,routingKey,msgMbject,correlationData);    }/<code>

註釋裡我解釋了,rabbitmq會自動做轉換,轉換用的就是jackson。

跟進源碼也能一探究竟:

<code>org.springframework.amqp.rabbit.core.RabbitTemplate#convertAndSend      @Override    public void convertAndSend(String exchange, String routingKey, final Object object,            @Nullable CorrelationData correlationData) throws AmqpException {        // 這裡調用了convertMessageIfNecessary(object)        send(exchange, routingKey, convertMessageIfNecessary(object), correlationData);    }/<code>

調用了convertMessageIfNessary:

<code>protected Message convertMessageIfNecessary(final Object object) {        if (object instanceof Message) {            return (Message) object;        }        // 獲取消息轉換器        return getRequiredMessageConverter().toMessage(object, new MessageProperties());    }/<code>

獲取消息轉換器的代碼如下:

<code>private MessageConverter getRequiredMessageConverter() throws IllegalStateException {        MessageConverter converter = getMessageConverter();        if (converter == null) {            throw new AmqpIllegalStateException(                    "No 'messageConverter' specified. Check configuration of RabbitTemplate.");        }        return converter;    }/<code>

getMessageConverter就是獲取rabbitmqTemplate 類中的一個field。

<code>public MessageConverter getMessageConverter() {        return this.messageConverter;    }/<code>

我們只要看哪裡對它進行賦值即可。

然後我想起來,就是在我們業務代碼裡賦值的:

<code>@Bean    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {        RabbitTemplate template = new RabbitTemplate(connectionFactory);        // 下面這裡賦值了。。。差點搞忘了        template.setMessageConverter(new Jackson2JsonMessageConverter());        template.setMandatory(true);        return template;    }/<code>

反正呢,總體來說,就是rabbitmqTemplate 會使用我們自定義的messageConverter轉換message後再發送。

時區問題,很好重現,源碼在:

https://gitee.com/ckl111/all-simple-demo-in-work/tree/master/jackson-demo

<code>@Datapublic class TestVO {    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private Date testDate;}/<code>

測試代碼:

<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);    }/<code>

輸出:

<code>{"testDate":"2019-12-27 05:45:26"}/<code>

解決辦法

  1. 指定默認時區配置@org.junit.Test public void specifyDefaultTimezone() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); SerializationConfig oldSerializationConfig = mapper.getSerializationConfig(); /** * 新的序列化配置,要配置時區 */ 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); }
  2. 在field上加註解@Data public class TestVoWithTimeZone { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date testDate; }我們這裡,新增了timezone,手動指定了時區配置。測試代碼:@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); }

上面兩種的輸出都是正確的。

這裡沒有去分析源碼,簡單說一下,在序列化的時候,會有一個序列化配置;這個配置由兩部分組成:默認配置+這個類自定義的配置。 自定義配置會覆蓋默認配置。

我們的第二種方式,就是修改了默認配置;第三種方式,就是使用自定義配置覆蓋默認配置。

jackson 還挺重要,尤其是 spring cloud 全家桶, feign 也用了這個, restTemplate 也用了,還有 Spring MVC 裡的 httpmessageConverter 有興趣的同學,去看下面這個地方就可以了。

rabbitmq template發送的消息中,Date類型字段比當前時間晚8小時

如果對JsonFormat的處理感興趣,可以看下面的地方:

com.fasterxml.jackson.annotation.JsonFormat.Value#Value(com.fasterxml.jackson.annotation.JsonFormat) (打個斷點在這裡,然後跑個test就到這裡了)

rabbitmq template發送的消息中,Date類型字段比當前時間晚8小時

總結

差點忘了,針對rabbitmq template的問題,最終我們的解決方案就是:

<code>@Bean    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {        RabbitTemplate template = new RabbitTemplate(connectionFactory);        ObjectMapper mapper = new ObjectMapper();        SerializationConfig oldSerializationConfig = mapper.getSerializationConfig();        /**         * 新的序列化配置,要配置時區         */        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);              ...設置callback啥的        return template;    }/<code>


分享到:


相關文章: