springboot rabbitMQ 自定義MessageConverter和ClassMapper實現訊息序列化
阿新 • • 發佈:2019-01-02
背景:公司專案使用springboot + rabbitMQ 處理訂單和推送訊息,最開始的時候,producer都是直接convertAndSend的json資料,
consumer也是接收json資料,然後在轉化為Bean去處理邏輯。當然,這樣雖然沒啥大問題,但是感覺很麻煩,後來查閱文件,SpringAMQP可以指定MessageConverter
訊息轉換器,自動封裝Mesage傳送,這樣就可以直接傳送消費物件了,其實說白了,就就是把頻繁的json序列化和反序列化的過程,封裝到MessageConverter裡面去處理而已.
具體不多說,直接上程式碼:
@Override protected Message createMessage(Object object, MessageProperties messageProperties) { //約定,傳送訊息的除了Bean 就是 String, 不會有其他型別 byte[] bytes = null; try { String jsonString = null; if(object instanceof String){ jsonString=(String) object; }else { jsonString = gson.toJson(object); } bytes = jsonString.getBytes(CHARSET_NAME); } catch (IOException e) { throw new MessageConversionException( "Failed to convert Message content", e); } messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); messageProperties.setContentEncoding(CHARSET_NAME); //設定訊息持久化 messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); if (bytes != null) { messageProperties.setContentLength(bytes.length); } classMapper.fromClass(object.getClass(),messageProperties); return new Message(bytes, messageProperties); }
@Override public Object fromMessage(Message message) throws MessageConversionException { Object content = null; MessageProperties properties = message.getMessageProperties(); if (properties != null) { String contentType = properties.getContentType(); if (contentType != null && contentType.contains("json")) { String encoding = properties.getContentEncoding(); if (encoding == null) { encoding = CHARSET_NAME; } try { Class<?> targetClass = classMapper.toClass(properties); content = convertBytesToObject(message.getBody(), encoding, targetClass); } catch (IOException e) { throw new MessageConversionException( "Failed to convert Message content", e); } } else { log.warn("Could not convert incoming message with content-type [" + contentType + "]"); } } if (content == null) { content = message.getBody(); } return content; }
一般情況下,訊息傳送者和消費者在不同的專案裡面,可能訊息實體對應的包名或者類名都不一定完全一樣,這樣消費者在接收訊息的時候,出現找不到類的異常,如下所以:
Caused by: java.lang.ClassNotFoundException: com.demo.Push.Order at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_91] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_91] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) ~[na:1.8.0_91] at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_91]
當然,如果連名稱,包路徑都完全一樣,則沒有上述問題,但是問題是,誰能保證傳送端和消費的物件的路徑完全一致呢?
沒辦法,使用了個笨辦法,傳送端修改如下:
修改自定義的MyClassMapper裡面的 fromClass 方法,只穿類的名稱
@Override
public void fromClass(Class<?> clazz, MessageProperties properties) {
properties.setHeader(DEFAULT_TYPE_CLASS, clazz.getSimpleName());
}
然後在消費端,自定義的MyClassMapper中配置需要消費的所有物件的map,如下所示:
public class MyClassMapper implements ClassMapper {
private static final String DEFAULT_TYPE_CLASS="__TypeId__";
public static Map<String, Class<?>> mqObjectMap=new HashMap<String, Class<?>>();
static{
mqObjectMap.put(PushMsg.class.getSimpleName(),PushMsg.class);
}
@Override
public Class<?> toClass(MessageProperties properties) {
Object obj = properties.getHeaders().get(DEFAULT_TYPE_CLASS);
if(null != obj){
return mqObjectMap.get(obj.toString());
}
return null;
}
@Override
public void fromClass(Class<?> clazz, MessageProperties properties) {
properties.setHeader(DEFAULT_TYPE_CLASS, clazz.getSimpleName());
}
}
測試,是可以的,但是 總體修改下來,感覺還不如最初直接用json 處理方便,也是蛋疼
折騰了半天,最後我還是選擇保證類名報名路徑完全一致,不改了
參考部落格:https://blog.csdn.net/shengsummer/article/details/79082225