• 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

  • 相关阅读:
    详解快速排序算法
    json和字符串、数组
    晚上回去搞这个
    json2使用方法
    mysql格式化时间函数:FROM_UNIXTIME
    java链接sqlite
    第七章 闭包
    产品家:你的闷骚,我的产品!
    关于json的格式
    JQuery选择器$()的工作原理浅析
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/12107016.html
Copyright © 2020-2023  润新知