前言

前一阵开发过程遇到的问题,用的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

最新文章

  1. logstash无法使用conf启动
  2. Random:产生随机数的类
  3. sysbench测试服务器性能
  4. HTML的快速写法:Emmet和Haml
  5. JAVA if条件语句,switch多分支结构,与逻辑运算符
  6. HW6.11
  7. mac os 10.10下安装android studio问题:android studio was unable to find a valid jvm
  8. WTL CEdit关联绑定ID,滚动到最新的一行
  9. 在64位Ubuntu系统上安装32位程序包
  10. 最小生成数之Kruskal算法
  11. caffe错误
  12. python(练习实例)
  13. 改善database schema
  14. [译]Ocelot - Middleware Injection and Overrides
  15. 「Mobile Testing Summit China 2017」第三届中国移动互联网测试开发大会-讲师征集
  16. BZOJ.4888.[TJOI2017]异或和(树状数组)
  17. selenium之关于 chromedriver的安装和使用
  18. laravel查找某个类拥有的方法:
  19. IBM WebSphere MQ介绍安装以及配置服务详解
  20. PYQT5实现控制台显示功能

热门文章

  1. IOS 第三方管理库管理 CocoaPods
  2. LRM-00109: could not open parameter file '/u01/app/oracle/product/12.1.0/db_1/dbs/initepps.ora'
  3. python 函数的语法规则
  4. [Linux]环境配置之jdk的安装 标签: jdk服务器linux 2016-08-07 22:18 502人阅读 评论(21)
  5. @bzoj - 4298@ [ONTAK2015]Bajtocja
  6. phpexcel使用说明4
  7. ADO.NET_01
  8. Spring AOP 的实现 原理
  9. bzoj 4386: [POI2015]Wycieczki
  10. [wikipedia] List of free and open-source software packages