您好,登錄后才能下訂單哦!
前言
rabbitmq template:消息模板。這是spring整合rabbit提供的消息模板。是進行發送消息的關鍵類。該類提供了豐富的發送方法,包括可靠性投遞消息方法、回調監聽消息接口ConfirmCallback、返回值確認接口ReturnCallBack等等。同樣我們需要注入到spring容器中,然后就可以想其他bean那樣正常使用了。
之前的配置是這樣的:
@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"}
解決辦法
指定默認時區配置
@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;
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。