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

仰不愧天,俯不愧人,內不愧心。關注公衆號【BAT的烏托邦】,有Spring技術棧、MyBatis、JVM、中間件等小而美的原創專欄供以免費學習。分享、成長,拒絕淺嘗輒止。本文已被 https://www.yourbatman.cn 收錄。

✍前言

你好,我是YourBatman。

Spring Framework是一個現代化的框架,儼然已發展成爲Java開發的基石。隨着高度封裝、高度智能化的Spring Boot的普及,發現團隊內越來越少的人知道其深層次機制,哪怕只有一點點。這是讓Spirng團隊開心,但卻是讓使用的團隊比較擔憂的現象。


若運行一個完全黑箱程序無疑像抱着一個定時炸彈,總是如履薄冰、戰戰兢兢。團隊內需要這樣的同學來爲它保駕護航,驚爆之時方可泰然自諾。所以,你願意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提供、一方庫、二方庫、三方庫提供

在企業級開發環境中,會遇到更爲複雜的數據轉換場景,譬如說:

  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的類型轉換並非易事,需要有一定的脈絡按步驟進行。本文作爲類型轉換系列第一篇文章,將繪製目錄大綱,將從以下幾個方面逐步展開討論。

早期類型轉換之PropertyEditor

早期的Spirng(3.0之前)類型轉換是基於Java Beans接口java.beans.PropertyEditor來實現的(全部繼承自PropertyEditorSupport):

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<S, T>:Source -> Target類型轉換接口,適用於1:1轉換
    • StringToPropertiesConverter:將String類型轉換爲Properties
    • StringToBooleanConverter:將String類型轉換爲Boolean
    • EnumToIntegerConverter:將Enum類型轉換爲Integer
  • ConverterFactory<S, R>:Source -> R類型轉換接口,適用於1:N轉換
    • StringToEnumConverterFactory:將String類型轉任意Enum
    • StringToNumberConverterFactory:將String類型轉爲任意數字(可以是int、long、double等等)
    • NumberToNumberConverterFactory:數字類型轉爲數字類型(如int到long,long到double等等)
  • GenericConverter:更爲通用的類型轉換接口,適用於N:N轉換
    • ObjectToCollectionConverter:任意集合類型轉爲任意集合類型(如List<String>轉爲List<Integer> / Set<Integer>都使用此轉換器)
    • 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<T>接口,作用爲:將Object格式化爲類型T。從語義上理解它也具有類型轉換(數據轉換的作用),相較於Converter<S,T>它強調的是格式化,因此一般用於時間/日期、數字(小數、分數、科學計數法等等)、貨幣等場景,舉例它的實現:

  • 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處理。

它一共提供三個重載方法:

// @since 2.0
public interface TypeConverter {

 	// value:待轉換的source源數據
 	// requiredType:目標類型targetType
 	// methodParam:轉換的目標方法參數,主要爲了分析泛型類型,可能爲null
 	// field:目標的反射字段,爲了泛型,可能爲null
	<T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException;
	<T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException;
	<T> T convertIfNecessary(Object value, Class<T> 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的類型轉換,旨在能夠幫你你撬開通往躍升的大門,節節攀高。


✔推薦閱讀:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章