Spring 類型轉(zhuǎn)換的實現(xiàn)
spring 有新老兩套實現(xiàn)
- 基于 JavaBeans 接口的類型轉(zhuǎn)換實現(xiàn),java.beans.PropertyEditor接口擴(kuò)展
- spring 3.0 通用類型轉(zhuǎn)換,
使用場景
| 場景 | 基于JavaBeans | Spring 3.0+ |
|---|---|---|
| 數(shù)據(jù)綁定 DataBinder | y | y實現(xiàn) PropertyEditorRegistry |
| BeanWrapper | y | y實現(xiàn)ConfigurablePropertyAccessor#setConversionService |
| Bean 屬性轉(zhuǎn)換 | y | y |
| 外部化屬性類型轉(zhuǎn)換 | n(springboot會用到) | y |
DataBinder

DataBinder實現(xiàn)了 PropertyEditorRegistry 和 TypeConverter 接口,分別支持兩種實現(xiàn)。
BeanWrapper
BeanWrapper 通常不會直接使用,一般是框架通過 DataBinder 或者 BeanFactory 使用。
ps: 在 BeanFactory 中 bean 創(chuàng)建的過程會通過 BeanWrapper 創(chuàng)建對象,可以查看 #doCreateBea -> #populateBean ->
// 通過 PropertyValues 設(shè)置屬性
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
...
}
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
...
try {
bw.setPropertyValues(mpvs);
return;
}
...
}
ps: DataBinder 也是通過 apply 的方式把 propertyValues 設(shè)置到bean上。
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
///

BeanWrapper 的父接口 ConfigurablePropertyAccessor 在3.0的時候增加了 setConversionService 方法設(shè)置服務(wù)類,并且通過方法setExtractOldValueForEditor設(shè)置在設(shè)置 property editor 時是否抽取舊值。

父類 PropertyEditorRegistrySupport 保存 ConversionService 變量,并且實現(xiàn)了 PropertyEditorRegistry 接口,說明支持新舊兩種實現(xiàn),是功能的一種整合。

基于 Javabeans 接口的類型轉(zhuǎn)換
核心職責(zé):將String 類型的內(nèi)容轉(zhuǎn)化為目標(biāo)類型的對象
擴(kuò)展原理:
- spirng 框架將文本內(nèi)容傳遞到 PropertyEditor 實現(xiàn)的 setAsText 方法
- PropertyEditor#setAsText方法實現(xiàn)將 String 轉(zhuǎn)化為目標(biāo)類型對象
- 將目標(biāo)類型的對象傳入PropertyEditor#setValue(Object)方法
- PropertyEditor#setValue(Object)方法實現(xiàn)需要臨時存儲傳入對象,有臨時存儲中間轉(zhuǎn)換對象的能力
- Spring 框架通過PropertyEditor#getValue 獲取類型轉(zhuǎn)換后的對象
ps : demo 代碼:conversion.String2PropertiesEditor
public class String2PropertiesEditor extends PropertyEditorSupport {
/**
* 原始值
*/
String text;
@Override
public void setAsText(String text) throws IllegalArgumentException {
this.text = text;
Properties properties = new Properties();
try {
properties.load(new StringReader(text));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
setValue(properties);
}
@Override
public String getAsText() {
return this.text;
}
}
public class PropertyEditorDemo {
public static void main(String[] args) {
String text = "name=why";
PropertyEditor propertyEditor = new String2PropertiesEditor();
propertyEditor.setAsText(text);
// 獲取轉(zhuǎn)換后的 property 對象
System.out.println("propertyEditor.getValue() = " + propertyEditor.getValue());
// 獲取原始 text
System.out.println("propertyEditor.getAsText() = " + propertyEditor.getAsText());
}
}
自定義 PropertyEditor 擴(kuò)展,添加到 spring 框架中
Spring 內(nèi)建 PropertyEditor 內(nèi)建擴(kuò)展都在 org.springframework.beans.propertyeditors 包下,下面看下如何自定義。
- 擴(kuò)展模式:擴(kuò)展 PropertyEditorSupport
- 實現(xiàn) org.springframework.beans.PropertyEditorRegistrar
- 實現(xiàn) registerCustomEditors
- 將 PropertyEditorRegistor 實現(xiàn) 注冊為 Spring bean
- 向 PropertyEditorRegistry 注冊自定義 PropertyEditor 實現(xiàn)
- 通用類型實現(xiàn) registerCustomEditor(class,PropertyEditor)
- Java Bean 屬性類型實現(xiàn):熱狗is投入CustomEditor(class,PropertyEditor)
public class DIYPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 1 類型轉(zhuǎn)換器
String2PropertiesEditor propertyEditor = new String2PropertiesEditor();
// 2 注冊屬性轉(zhuǎn)換
registry.registerCustomEditor(User.class, "context", propertyEditor);
// 3 DIYPropertyEditorRegistrar 定義為 spring bean 對象
//
}
}
xml 配置如下:
<bean class="conversion.DIYPropertyEditorRegistrar"/>
<bean id="user" class="pojo.User">
<property name="id" value="1"/>
<property name="context">
<value>
id=1
name=why
</value>
</property>
</bean>
demo 程序
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"META-INF/conversion/property-editor.xml");
User user = context.getBean("user", User.class);
// context 屬性會注入
System.out.println(user);
context.close();
}
Spring PropertyEditor的設(shè)計缺陷
- 違反單一職責(zé)原則
PropertyEditor 接口的職責(zé)太多,除了類型轉(zhuǎn)換,還有事件和GUI程序的交互邏輯 - PropertyEditor 實現(xiàn)類型局限
源類型只能為 String 類型 - 缺少類型強(qiáng)類型轉(zhuǎn)換
除了實現(xiàn)類的名稱可以表達(dá)語義(如CharsetEditor),實現(xiàn)類無法感知目標(biāo)轉(zhuǎn)換類型。setValue(Object value)可以設(shè)置任意類型的對象,同樣public Object getValue() 返回的是 Object 類型,也不知道具體是什么類型。
Spring 3 通用類型轉(zhuǎn)換接口
- 增加類型轉(zhuǎn)換接口 Converter<S,T>,實現(xiàn)要求線程安全。
- 核心方法 T convert(S s);
- 局限性
- 缺少前置判斷。如果判斷convert方法的入?yún)⑹欠裰С诸愋娃D(zhuǎn)換,不支持的時候返回null,也可以實現(xiàn)類似的功能。但是接口的職責(zé)就不單一了,而且返回值有二義性。小比較來說由 ConditionalConverter 實現(xiàn)職責(zé)更合理。
- 僅支持一對一轉(zhuǎn)換。由 GenericConverter 代替,復(fù)合類型的實現(xiàn)。
Converter 相較 PropertyEditor 增加了原和目標(biāo)的泛型,支持更廣泛的類型轉(zhuǎn)換。因為是泛型,會有泛型擦寫的問題,在運行時我們并不一定明確的知道具體的類型,所以有了 GenericConverter 。
-
GenericConverter
- 核心方法 convert(Object, TypeDescriptor,TypeDescriptor)
- 配對類型 GenericConverter.ConvertiblePair,保存原和目標(biāo)的一對值。支持多個鍵值對。
- TypeDescriptor,類型的描述,不只是class,還有原生類型,也可能是泛型,比較復(fù)雜。
ConditionalConverter。可以讓Converter, GenericConverter,ConverterFactory 根據(jù)條件執(zhí)行。boolean matches(TypeDescriptor sourceType, TypeDe scriptor targetType) 方法參數(shù)使用的是 TypeDescriptor 更加抽象,可以更好的描述類型。
spring 內(nèi)建類型轉(zhuǎn)換器
| 轉(zhuǎn)換場景 | 實現(xiàn)類 |
|---|---|
| 時間/日期 | format.datetime |
| joda 時間/日期 | format.datetime.joda |
| java8 時間/日期 | format.datetime.standard |
| 通用實現(xiàn) | core.convert.support |
可以看下每個包里的實現(xiàn)類。如:StringToArrayConverter。
GenericConverter 接口
| 核心要素 | 說明 |
|---|---|
| 使用場景 | 可用于復(fù)合類型轉(zhuǎn)換場景,如 Collection,Map,數(shù)組等 |
| 轉(zhuǎn)換范圍 | Set<ConvertiblePair> getConvertibleTypes(); |
| 配對類型 | GenericConverter.ConvertiblePair |
| 轉(zhuǎn)換方法 | Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); |
| 類型描述 | org.springframework.core.convert.TypeDescriptor |
GenericConverter 會和 Converter 配合使用。如果轉(zhuǎn)換的是集合,集合中的元素會使用 Converter 轉(zhuǎn)換。看一下 CollectionToArrayConverter 的實現(xiàn)。
// Collection 支持泛型,保存數(shù)據(jù)都是用的 Object[]。所以這里原核目標(biāo)都是object
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class));
}
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
// 類型轉(zhuǎn)換
Collection<?> sourceCollection = (Collection<?>) source;
TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
Assert.state(targetElementType != null, "No target element type");
Object array = Array.newInstance(targetElementType.getType(), sourceCollection.size());
int i = 0;
for (Object sourceElement : sourceCollection) {
// 每個元素調(diào)用單獨轉(zhuǎn)換,convert 方法用到了 Converter 接口的實現(xiàn)
Object targetElement = this.conversionService.convert(sourceElement,
sourceType.elementTypeDescriptor(sourceElement), targetElementType);
Array.set(array, i++, targetElement);
}
return array;
}
優(yōu)化 GenericConverter 接口
-
局限性
- 缺少S和T的前置判斷,這一點和Converter一樣
- 單一類型轉(zhuǎn)換復(fù)雜。從 CollectionToArrayConverter 可以看到每個元素逐個轉(zhuǎn)換,轉(zhuǎn)換依賴 Converter。
-
優(yōu)化,條件話的接口 ConditionalGenericConverter
- 接口繼承 GenericConverter, ConditionalConverter
- spring 內(nèi)部實現(xiàn)都是實現(xiàn)此接口,如CollectionToArrayConverter
- 如果我們要擴(kuò)展,也要實現(xiàn)此接口。
統(tǒng)一類型轉(zhuǎn)換服務(wù)
org.springframework.core.convert.ConversionService,主要的實現(xiàn)類繼承關(guān)系如下圖:

- GenericConversionService
通用模板實現(xiàn),不內(nèi)置轉(zhuǎn)化器實現(xiàn)。
public class GenericConversionService implements ConfigurableConversionService {
// 轉(zhuǎn)化器集合
private final Converters converters = new Converters();
// 保存轉(zhuǎn)化器容器緩存
private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);
}
GenericConversionService 實現(xiàn)了 ConfigurableConversionService 接口,說明是可配置的,converters 保存轉(zhuǎn)化器,模板方法都提供了所有的操作方法。
- DefaultConversionService
基礎(chǔ)實現(xiàn),內(nèi)置常用轉(zhuǎn)化器實現(xiàn)。
public class DefaultConversionService extends GenericConversionService {
// 單例
private static volatile DefaultConversionService sharedInstance;
public DefaultConversionService() {
// 構(gòu)造方法添加轉(zhuǎn)化器
addDefaultConverters(this);
}
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}
}
DefaultConversionService 繼承自 GenericConversionService ,使用繼承自模板類的能力添加單個對象和集合對象的轉(zhuǎn)化器,實現(xiàn)了 sharedInstance 單例方式。
FormattingConversionService
通用FOrmatter+GenericConversionService實現(xiàn),不內(nèi)置轉(zhuǎn)化器實現(xiàn)。增加對 spring 內(nèi)部實現(xiàn)的支持,如:PrinterConverter,ParserConverter。DefaultFormattingConversionService
DefaultConversionService +格式化實現(xiàn),如JSR-354 Money,Currency,JSR-310 Date-Time。
spring web 實現(xiàn)了 WebConversionService ,擴(kuò)展了更多的類型支持,基本上大同小異。
總結(jié)
bean 創(chuàng)建的過程和TypeConversionService結(jié)合的主體流程:
- 容器初始化時保存conversionService對象,AbstractApplicationContext#finishBeanFactoryInitialization,查找CONVERSION_SERVICE_BEAN_NAME,并設(shè)置到 beanFactory
- bean創(chuàng)建過程:
- BeanDefination,通過xml或者掃描的方式
- bean 實例會轉(zhuǎn)換成 BeanWrapper,
- AbstractBeanFactory#initBeanWrapper 設(shè)置BeanWrapper的 ConversionService 對象
- 數(shù)據(jù)來源是 PropertiesValues
- bw.setPropertyValues(mpvs);
- TypeConverter#converIfNecessnary
- TypeConverterDelegate#converIfNecessnary
- PropertyEditor 或者 ConversionService
Spring 類型轉(zhuǎn)化接口有哪些?
- 單一類型轉(zhuǎn)化:org.springframework.core.convert.converter.Converter
- 通用類型轉(zhuǎn)化:org.springframework.core.convert.converter.GenericConverter
- 條件轉(zhuǎn)化:org.springframework.core.convert.converter.ConditionalConverter
- 綜合類型轉(zhuǎn)化接口org.springframework.core.convert.converter.ConditionalGenericConverter
-
BeanWrapper使用的類型轉(zhuǎn)換實現(xiàn):
beanWrapper
BeanWrapper 繼承了TypeConverterSupport,在初始化的時候會給typeConverterDelegate賦值。

this 是當(dāng)前BeanWrapper,因為繼承了PropertyEditorRegistrySupport,所以有 PropertyEditor 和 ConversionService 信息。
這樣 BeanWrapper 和 PropertyEditor 和 ConversionService 就關(guān)聯(lián)起來了。
