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


前言

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

就說說為什么出現吧。

之前的配置是這樣的:

@Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);

        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setMandatory(true);
      
      	...
        return template;
    }

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

@Data
public class TestVO {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date testDate;
}

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

{"testDate":"2019-12-27 05:45:26"}

原因

我們是這么使用rabbitmq template的:

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

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

跟進源碼也能一探究竟:

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

調用了convertMessageIfNessary:

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

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


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

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

	public MessageConverter getMessageConverter() {
		return this.messageConverter;
	}

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

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

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

        return template;
    }

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

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

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

測試代碼:


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

輸出:

{"testDate":"2019-12-27 05:45:26"}

解決辦法

  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有興趣的同學,去看下面這個地方就可以了。

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

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

總結

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

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

以上相關源碼在:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM