自定義HttpMessageConverter處理多個不同陣列形式的JSON資料
需求
在一個成績管理系統中,有實體類Score和實體類Student,現需要對這兩個實體類關聯的資料庫表分別進行批量插入,因而需要處理兩種不同的JSON資料(均為陣列形式),並轉換為相應的List。在兩種實體類http請求中,Student類中的成員變數與對應json資料格式的一致,而Score類中的成員變數則與相應http請求中的json資料格式則不一致,因此需要在轉型的過程中進行自定義的操作。
分析
對於Student類,可以直接用springmvc提供的MappingJackson2HttpMessageConverter轉換器進行類與json字串之間的轉換,對於Score類,則需要自定義轉換器。自定義轉換器我能想到的有兩種方式:
一丶實現HttpMessageConverter介面
二丶繼承MappingJackson2HttpMessageConverter類並對其功能進行擴充套件
在現在這種情況下,方式一實際上是行不通的,因為springmvc在選擇轉換器時,是通過轉換器類中的canRead方法來進行判斷的,放上兩種方式中實際呼叫的canRead方法的簽名:
實現HttpMessageConverter介面時:
boolean canRead(Class< ? > aClass, MediaType mediaType)
繼承MappingJackson2HttpMessageConverter類時:
boolean canRead(Type type, Class< ? > aClass, MediaType mediaType)
可以看到兩個方法的引數並不一樣,對於第一種方式,轉型的目的java物件型別資訊(此處為List< Score >)存放於Class< ? > aClass中,該變數只能記錄List擦除後的資訊,無法記錄List中持有物件的型別(只知道是List而不知道是持有什麼型別的List),因而無法為springmvc對轉換器的選擇提供準確的判斷資訊;而對於第二種方式,Type type中存放了所有相關的詳細資訊(包括List的泛型資訊),因而可行。
實驗
接下來向
傳送請求體為json格式的資料請求,springmvc會選擇相應的轉換器進行處理,看看兩種方式下springmvc對轉換器的處理情況:
第一種方式,註冊實現了HttpMessageConverter介面的轉換器:
當springmvc呼叫該轉換器的canRead方法時可以看到如下結果:
第二種方式,註冊繼承了MappingJackson2HttpMessageConverter類的轉換器:
當springmvc呼叫該轉換器的canRead方法時可以看到如下結果:
那麼為什麼springmvc為兩種方式下轉換器的canRead方法傳遞的引數不同呢?springmvc主要在下面的方法中對轉換器進行判斷並呼叫:
註冊繼承了MappingJackson2HttpMessageConverter類的轉換器,對該方法進行斷點除錯:
註冊實現了HttpMessageConverter介面的轉換器,對該方法進行斷點除錯:
結論
springmvc根據表示式converter instanceof GenericHttpMessageConverter進行判斷,併為不同的轉換器converter提供不同的引數。實現了HttpMessageConverter介面的轉換器不是GenericHttpMessageConverter的匯出型別,因此其canRead方法無法獲取具體的泛型型別資訊,不適用於包含泛型型別的轉型;MappingJackson2HttpMessageConverter或其子類是GenericHttpMessageConverter的匯出型別,因而適用於包含泛型型別的轉型。判斷結束後springmvc會呼叫轉換器中的Read方法,將請求體中的json字串轉換為相應的List集合並返回。
ps:開始我用的是實現了HttpMessageConverter介面的轉換器,百度上很多人說僅通過class物件就能獲取相應的泛型型別資訊,就像下面這樣:
List<Long> longs = new ArrayList<Long>();
//目標class物件
Class c = longs.getClass();
//若c對應的物件繼承了泛型類
ParameterizedType type = (ParameterizedType) c.getGenericSuperclass();
//若c對應的物件實現了(多個)泛型介面
//ParameterizedType[] type = (ParameterizedType[]) c.getGenericInterfaces();
//輸出其泛型引數型別
System.out.println(type.getActualTypeArguments()[0]);
但其輸出結果為:E
E表示的是ArrayList直接父類的泛型型別(此處僅為一佔位符),因此我認為通過這種方式,只能獲取到目標class物件的直接父類(或者其所實現介面)的泛型型別資訊,無法獲取class物件本身的泛型型別資訊。
以上均為本人通過除錯或者查資料所得出的結論,若大家發現有不對的地方,希望能夠及時指出,對此我表示萬分感謝!