前言git
前一陣開發過程遇到的問題,用的 rabbitmq template 發送消息,消息body裏的時間是比當前時間少了8小時的,這種一看就是時區問題了。redis
就說說爲何出現吧。spring
以前的配置是這樣的:json
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); template.setMessageConverter(new Jackson2JsonMessageConverter()); template.setMandatory(true); ... return template; }
要發送出去的消息vo是這樣的:app
@Data public class TestVO { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date testDate; }
而後,出現的問題就是,消息體裏,時間比當前時間少了8個小時。ide
{"testDate":"2019-12-27 05:45:26"}
緣由測試
咱們是這麼使用rabbitmq template的:ui
@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。this
跟進源碼也能一探究竟:3d
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"}
解決辦法
指定默認時區配置
@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); }
在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; }