1. 程式人生 > >1. 揭祕Spring型別轉換 - 框架設計的基石

1. 揭祕Spring型別轉換 - 框架設計的基石

> 仰不愧天,俯不愧人,內不愧心。關注公眾號【**BAT的烏托邦**】,有Spring技術棧、MyBatis、JVM、中介軟體等小而美的**原創專欄**供以免費學習。分享、成長,拒絕淺嘗輒止。本文已被 [**https://www.yourbatman.cn**](https://www.yourbatman.cn) 收錄。 [TOC] ![](https://img-blog.csdnimg.cn/20201129162517566.png) # ✍前言 你好,我是YourBatman。 `Spring Framework`是一個現代化的框架,儼然已發展成為Java開發的基石。隨著高度封裝、高度智慧化的Spring Boot的普及,發現團隊內越來越少的人知道其深層次機制,哪怕只有一點點。這是讓Spirng團隊開心,但卻是讓使用的團隊比較擔憂的現象。 ![](https://img-blog.csdnimg.cn/20201129133001611.png#pic_center) 若執行一個完全黑箱程式無疑像抱著一個定時炸彈,總是如履薄冰、戰戰兢兢。團隊內需要這樣的同學來為它保駕護航,驚爆之時方可泰然自諾。所以,你願意pick嗎? 本系列將討論`Spring Framework`裡貫穿其上下文,具有舉足輕重地位的一個模組:**型別轉換**(也可叫資料轉換)。 # ✍正文 Java是個多型別且強型別語言,型別轉換這個概念對它來說並不陌生。比如: - 自動型別轉換(隱式):小型別 -> 大型別。eg:`int a = 10; double b = a;` - 強制型別轉換(顯式):大型別 -> 小型別。eg:`double a = 10.123; int b = (int)a;` - 說明:強轉有可能產生精度丟失 - 呼叫API型別轉換:常見的是字串和其它型別的互轉。eg:`parseInt(String); parseBoolean(String); JSON.toJSONString(Obj); LocalDate.parse(String)` - 說明:API可能來自於JDK提供、一方庫、二方庫、三方庫提供 ![](https://img-blog.csdnimg.cn/20201129084119479.png#pic_center) 在企業級開發環境中,會遇到更為複雜的資料轉換場景,譬如說: 1. 輸入/傳入一個規格字串(如`1,2,3,4`),轉換為一個數組 2. 輸入/傳入一個JSON串(如`{"name":"YourBatman","age":18}`),轉換為一個Person物件 3. 輸入/傳入一個URL串(如:`C:/myfile.txt、classpath:myfile.txt`),轉換為一個`org.springframework.core.io.Resource`物件 雖說資料輸入/傳入絕大部分都會是字串(如Http請求資訊、XML配置資訊),但結構可以千差萬別,那麼這就必然會涉及到大量的資料型別、結構轉換的邏輯。倘若這都需要程式設計師自己手動編碼做轉換處理,那會讓人望而生畏甚至怯步。 **還好我們有Spring**。從本文起,A哥就幫你解密Spring Framework它是如何幫你接管型別轉換,實現“自動化”的。有了此部分知識的儲備,後續再討論自動化資料繫結、自動化資料校驗、Spring Boot鬆散繫結等,一切都變得容易接受得多。 > 說明:型別轉換其實每個框架都會存在,其中Java領域以Spring的實現最為經典,學會後便可舉一反三 ## Spring型別轉換 **Spring的型別轉換也並非一步到位**。完全掌握Spring的型別轉換並非易事,需要有一定的脈絡按步驟進行。本文作為型別轉換系列第一篇文章,將繪製目錄大綱,將從以下幾個方面逐步展開討論。 ![](https://img-blog.csdnimg.cn/20201129164614705.png#pic_center) ### 早期型別轉換之PropertyEditor 早期的Spirng(3.0之前)型別轉換是基於Java Beans介面`java.beans.PropertyEditor`來實現的(全部繼承自`PropertyEditorSupport`): ```java public interface PropertyEditor { ... // String -> Object void setAsText(String text) throws java.lang.IllegalArgumentException; // Object -> String String getAsText(); ... } ``` 這類實現舉例有: - `StringArrayPropertyEditor`:`,`分隔的字串和`String[]`型別互轉 - `PropertiesEditor`:鍵值對字串和`Properties`型別互轉 - `IntegerEditor`:字串和`Integer`型別互轉 - ... 基於`PropertyEditor`的型別轉換作為一種古老的、遺留下來的方式,是具有一些設計缺陷的,如:職責不單一,型別不安全,只能實現`String`型別的轉換等。雖然自Spring 3.0起提供了現代化的型別轉換介面,但是此部分機制一直得以**保留**,保證了向下相容性。 > 說明:Spring 3.0之前在Java領域還未完全站穩腳跟,因此良好的向下相容顯得尤為重要 這塊內容將在本系列後面具體篇章中得到專題詳解,敬請關注。 ### 新一代型別轉換介面Converter、GenericConverter 為了解決`PropertyEditor`作為型別轉換方式的設計缺陷,Spring 3.0版本重新設計了一套型別轉換介面,其中主要包括: - `Converter`:Source -> Target型別轉換介面,適用於1:1轉換 - StringToPropertiesConverter:將String型別轉換為Properties - StringToBooleanConverter:將String型別轉換為Boolean - EnumToIntegerConverter:將Enum型別轉換為Integer - `ConverterFactory`:Source -> R型別轉換介面,適用於1:N轉換 - StringToEnumConverterFactory:將String型別轉任意Enum - StringToNumberConverterFactory:將String型別轉為任意數字(可以是int、long、double等等) - NumberToNumberConverterFactory:數字型別轉為數字型別(如int到long,long到double等等) - `GenericConverter`:更為通用的型別轉換介面,適用於N:N轉換 - ObjectToCollectionConverter:任意集合型別轉為任意集合型別(如`List`轉為`List / Set`都使用此轉換器) - CollectionToArrayConverter:解釋基本同上 - MapToMapConverter:解釋基本同上 - `ConditionalConverter`:條件轉換介面。可跟上面3個介面組合使用,提供**前置條件**判斷驗證 重新設計的這套介面,解決了`PropertyEditor`做型別轉換存在的所有缺陷,且具有非常高的靈活性和可擴充套件性。但是,每個介面獨立來看均具有一定的侷限性,只有使用**組合拳**方才有最大威力。當然嘍,這也造成學習曲線變得陡峭。據我瞭解,很少有同學搞得清楚新的這套型別轉換機制,特別容易混淆。倘若你掌握了是不是自己價值又提升了呢?不信你細品? 這塊內容將在本系列後面具體篇章中得到專題詳解,敬請關注。 ### 新一代轉換服務介面:ConversionService 從上一小節我們知道,新的這套介面中,`Converter、ConverterFactory、GenericConverter`它們三都著力於完成型別轉換。對於使用者而言,如果做個型別轉換需要了解到這三套體系無疑成本太高,因此就有了`ConversionService`用於整合它們三,統一化介面操作。 此介面也是Spring 3.0新增,用於**統一化** 底層型別轉換實現的差異,對外提供統一服務,所以它也被稱作型別轉換的**門面介面**,從介面名稱`xxxService`也能看出來其設計思路。它主要有兩大實現: 1. `GenericConversionService`:提供模版實現,如轉換器的註冊、刪除、匹配查詢等,但並不內建轉換器實現 2. `DefaultConversionService`:繼承自GenericConversionService。在它基礎上預設註冊了非常多的內建的轉換器實現,從而能夠實現**絕大部分**的型別轉換需求 `ConversionService`轉換服務它貫穿於Spring上下文`ApplicationContext`的多項功能,包括但不限於:BeanWrapper處理Bean屬性、DataBinder資料繫結、PropertySource外部化屬性處理等等。因此想要進一步深入瞭解的話,ConversionService是你繞不過去的坎。 > 說明:很多小夥伴問WebConversionService是什麼場景下使用?我說:它並非Spirng Framework的API,而屬於Spring Boot提供的增強,且起始於2.x版本,這點需引起注意 這塊內容將在本系列後面具體篇章中得到專題詳解,敬請關注。 ### 型別轉換整合格式化器Formatter Spring 3.0還新增了一個`Formatter`介面,作用為:將Object格式化為型別T。從語義上理解它也具有型別轉換(資料轉換的作用),相較於`Converter`它強調的是**格式化**,因此一般用於時間/日期、數字(小數、分數、科學計數法等等)、貨幣等場景,舉例它的實現: - `DurationFormatter`:字串和`Duration`型別的互轉 - `CurrencyUnitFormatter`:字串和`javax.money.CurrencyUnit`貨幣型別互轉 - `DateFormatter`:字串和`java.util.Date`型別互轉。這個就使用得太多了,它預設支援什麼格式?支援哪些輸出方式,這將在後文詳細描述 - ...... 為了和型別轉換服務`ConversionService`完成整合,對外只提供統一的API。Spring提供了`FormattingConversionService`專門用於整合Converter和Formatter,從而使得兩者具有一致的程式設計體驗,對開發者更加友好。 這塊內容將在本系列後面具體篇章中得到專題詳解,敬請關注。 ### 型別轉換底層介面TypeConvert 定義型別轉換方法的介面,它在Spring 2.0就已經存在。在還沒有`ConversionService`之前,它的型別轉換動作均委託給已註冊的`PropertyEditor`來完成。但自3.0之後,這個轉換動作可能被PropertyEditor來做,也可能交給`ConversionService`處理。 它一共提供三個過載方法: ```java // @since 2.0 public interface TypeConverter { // value:待轉換的source源資料 // requiredType:目標型別targetType // methodParam:轉換的目標方法引數,主要為了分析泛型型別,可能為null // field:目標的反射欄位,為了泛型,可能為null T convertIfNecessary(Object value, Class requiredType) throws TypeMismatchException; T convertIfNecessary(Object value, Class requiredType, MethodParameter methodParam) throws TypeMismatchException; T convertIfNecessary(Object value, Class requiredType, Field field) throws TypeMismatchException; } ``` 它是Spring內部使用型別轉換的**入口**,最終委託給`PropertyEditor`或者註冊到`ConversionService`裡的轉換器去完成。它的主要實現有: - `TypeConverterSupport`:@since 3.2。繼承自`PropertyEditorRegistrySupport`,它主要是為子類`BeanWrapperImpl`提供功能支撐。作用有如下兩方面: 1. 提供對**預設編輯器**(支援JDK內建型別的轉換如:Charset、Class、Class[]、Properties、Collection等等)和**自定義編輯器**的管理(PropertyEditorRegistry#registerCustomEditor) 2. 提供get/set方法,把`ConversionService`管理上(可選依賴,可為null) - 資料繫結相關:因為資料繫結**強依賴於**型別轉換,因此資料繫結涉及到的屬性訪問操作將會依賴於此元件,不管是直接訪問屬性的`DirectFieldAccessor`還是功能更強大的`BeanWrapperImpl`均是如此 總的來說,`TypeConverter`能把型別的各種實現、API收口於此,Spring把型別轉換的能力都轉嫁到TypeConverter這個API裡面去了。雖然方便了使用,但其內部實現原理稍顯複雜,同樣的這塊內容將在本系列後面具體篇章中得到專題詳解,敬請關注。 ### Spring Boot使用增強 在傳統Spring Framework場景下,若想使用`ConversionService`還得手動檔去配置,這對於不太瞭解其執行機制的同學無疑是有使用門檻的。而在Spring Boot場景下這一切都會變得簡單許多,可謂使用起來愈發方便了。 另外,Spring Boot在內建轉換器的基礎上額外擴充套件了不少實用轉換器,形如: - `StringToFileConverter`:String -> File - `NumberToDurationConverter`: - `DelimitedStringToCollectionConverter`: - ...... # ✍總結 **基於配置**來控制程式執行總比你修改程式程式碼來得更優雅、更富彈性,但這是需要依賴於資料繫結、資料校驗等功能的,而它們又依賴於型別轉換。 雖說幾乎所有的框架都會有型別轉換的功能模組,但Spring的可能是最為通用、最為經典的存在。因此本系列專題講解Spring Framework的型別轉換,旨在能夠幫你你撬開通往躍升的大門,節節攀高。 --- ##### ✔推薦閱讀: - [Spring Boot 2.4.0正式釋出,全新的配置檔案載入機制(不向下相容)](https://mp.weixin.qq.com/s/KywpJkLDHZbZTxUf4WFxhw) - [如果程式設計師和產品經理都用凡爾賽文學對話......](https://mp.weixin.qq.com/s/SZUJ0sy4vM7UH10pk6NM3g) - [Spring Framework 5.3.0正式釋出,在雲原生路上繼續發力](https://mp.weixin.qq.com/s/Sw6EqAY0DmF-p2qPoaetUg) - [Spring改變版本號命名規則:此舉對非英語國家很友好](https://mp.weixin.qq.com/s/ZoUG9h1TndW2QpnPyGeIQA) - [JDK15正式釋出,劃時代的ZGC同時宣佈轉正](https://mp.weixin.qq.com/s/3QaiUGzj5nW2N4Aipm47PQ) - [IntelliJ IDEA 2020.2正式釋出,諸多亮點總有幾款能助你提效](https://mp.weixin.qq.com/s/8voJSbmcBbdfNUCUBIcKcA) - [Spring Boot 2.3.0正式釋出:優雅停機、配置檔案位置萬用字元新特性一覽](https://mp.weixin.qq.com/s/ikI9c3XyR_czOSmsu6FLMw) - [搞事情?Spring Boot今天一口氣釋出三個版本](https://mp.weixin.qq.com/s/nb4oT02dlU4pxINZw9aLLw)