仰不愧天,俯不愧人,内不愧心。關注公衆号【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,2,3,4
- 輸入/傳入一個JSON串(如
),轉換為一個Person對象{"name":"YourBatman","age":18}
- 輸入/傳入一個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版本重新設計了一套類型轉換接口,其中主要包括:
-
:Source -> Target類型轉換接口,适用于1:1轉換Converter<S, T>
- StringToPropertiesConverter:将String類型轉換為Properties
- StringToBooleanConverter:将String類型轉換為Boolean
- EnumToIntegerConverter:将Enum類型轉換為Integer
-
:Source -> R類型轉換接口,适用于1:N轉換ConverterFactory<S, R>
- StringToEnumConverterFactory:将String類型轉任意Enum
- StringToNumberConverterFactory:将String類型轉為任意數字(可以是int、long、double等等)
- NumberToNumberConverterFactory:數字類型轉為數字類型(如int到long,long到double等等)
-
:更為通用的類型轉換接口,适用于N:N轉換GenericConverter
- ObjectToCollectionConverter:任意集合類型轉為任意集合類型(如
轉為List<String>
都使用此轉換器)List<Integer> / Set<Integer>
- CollectionToArrayConverter:解釋基本同上
- MapToMapConverter:解釋基本同上
- ObjectToCollectionConverter:任意集合類型轉為任意集合類型(如
-
:條件轉換接口。可跟上面3個接口組合使用,提供前置條件判斷驗證ConditionalConverter
重新設計的這套接口,解決了
PropertyEditor
做類型轉換存在的所有缺陷,且具有非常高的靈活性和可擴充性。但是,每個接口獨立來看均具有一定的局限性,隻有使用組合拳方才有最大威力。當然喽,這也造成學習曲線變得陡峭。據我了解,很少有同學搞得清楚新的這套類型轉換機制,特别容易混淆。倘若你掌握了是不是自己價值又提升了呢?不信你細品?
新一代轉換服務接口:ConversionService
從上一小節我們知道,新的這套接口中,
Converter、ConverterFactory、GenericConverter
它們三都着力于完成類型轉換。對于使用者而言,如果做個類型轉換需要了解到這三套體系無疑成本太高,是以就有了
ConversionService
用于整合它們三,統一化接口操作。
此接口也是Spring 3.0新增,用于統一化 底層類型轉換實作的差異,對外提供統一服務,是以它也被稱作類型轉換的門面接口,從接口名稱
xxxService
也能看出來其設計思路。它主要有兩大實作:
-
:提供模版實作,如轉換器的注冊、删除、比對查找等,但并不内置轉換器實作GenericConversionService
-
:繼承自GenericConversionService。在它基礎上預設注冊了非常多的内建的轉換器實作,進而能夠實作絕大部分的類型轉換需求DefaultConversionService
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
裡的轉換器去完成。它的主要實作有:
-
:@since 3.2。繼承自TypeConverterSupport
,它主要是為子類PropertyEditorRegistrySupport
提供功能支撐。作用有如下兩方面:BeanWrapperImpl
- 提供對預設編輯器(支援JDK内置類型的轉換如:Charset、Class、Class[]、Properties、Collection等等)和自定義編輯器的管理(PropertyEditorRegistry#registerCustomEditor)
- 提供get/set方法,把
管理上(可選依賴,可為null)ConversionService
- 資料綁定相關:因為資料綁定強依賴于類型轉換,是以資料綁定涉及到的屬性通路操作将會依賴于此元件,不管是直接通路屬性的
還是功能更強大的DirectFieldAccessor
均是如此BeanWrapperImpl
總的來說,
TypeConverter
能把類型的各種實作、API收口于此,Spring把類型轉換的能力都轉嫁到TypeConverter這個API裡面去了。雖然友善了使用,但其内部實作原理稍顯複雜,同樣的這塊内容将在本系列後面具體篇章中得到專題詳解,敬請關注。
Spring Boot使用增強
在傳統Spring Framework場景下,若想使用
ConversionService
還得手動檔去配置,這對于不太了解其運作機制的同學無疑是有使用門檻的。而在Spring Boot場景下這一切都會變得簡單許多,可謂使用起來愈發友善了。
另外,Spring Boot在内建轉換器的基礎上額外擴充了不少實用轉換器,形如:
-
:String -> FileStringToFileConverter
-
NumberToDurationConverter
-
DelimitedStringToCollectionConverter
✍總結
基于配置來控制程式運作總比你修改程式代碼來得更優雅、更富彈性,但這是需要依賴于資料綁定、資料校驗等功能的,而它們又依賴于類型轉換。
雖說幾乎所有的架構都會有類型轉換的功能子產品,但Spring的可能是最為通用、最為經典的存在。是以本系列專題講解Spring Framework的類型轉換,旨在能夠幫你你撬開通往躍升的大門,節節攀高。