文章目录
- 一、概述
- 二、基于 JavaBeans 接口的类型转换
- 三、Spring 3 通用类型转换接口
- 参考资料
一、概述
本文主要内容是Spring Bean通过xml等配置进行属性映射时,发生的类型转换的基本原理与源码分析。
1、Spring 类型转换的实现
Spring 3.0以前,基于 JavaBeans 接口的类型转换实现,基于 java.beans.PropertyEditor 接口扩展。
Spring 3.0+ 通用类型转换实现。
2、使用场景
| 场景 | 基于 JavaBeans 接口的类型转换实现 | Spring 3.0+ 通用类型转换实现 | 
|---|---|---|
| 数据绑定 | YES | YES | 
| BeanWrapper | YES | YES | 
| Bean 属性类型装换 | YES | YES | 
| 外部化属性类型转换 | NO | YES | 
3、源码分析
我们之前提到过:Spring数据绑定详解,Spring-Data Binding源码分析
数据绑定通过DataBinder进行绑定的,而DataBinder类实现了PropertyEditorRegistry和TypeConverter。
PropertyEditorRegistry接口和PropertyEditor 有着直接的关系:
public interface PropertyEditorRegistry {
	void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
	void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
	@Nullable
	PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
BeanWrapper接口继承了ConfigurablePropertyAccessor接口,而ConfigurablePropertyAccessor接口提供了对ConversionService(类型转换服务)的操作,同时有个开关,设置是否用PropertyEditor抽取老的值。
上面了解了一些关于类型转换的接口后,我们继续回到DefaultListableBeanFactory的父类AbstractAutowireCapableBeanFactory的doCreateBean方法,同样用到了BeanWrapper,在调用populateBean时,会执行applyPropertyValues方法,这与DataBinder的bind方法中的applyPropertyValues方法的实现基本是一致的。
applyPropertyValues方法中就涉及到了我们本文的重点-Bean属性绑定时发生的类型转换。
二、基于 JavaBeans 接口的类型转换
核心职责:将 String 类型的内容转化为目标类型的对象
扩展原理:
 • Spring 框架将文本内容传递到 PropertyEditor 实现的 setAsText(String) 方法
 • PropertyEditor#setAsText(String) 方法实现将 String 类型转化为目标类型的对象
 • 将目标类型的对象传入 PropertyEditor#setValue(Object) 方法
 • PropertyEditor#setValue(Object) 方法实现需要临时存储传入对象
 • Spring 框架将通过 PropertyEditor#getValue() 获取类型转换后的对象
1、代码实例
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
/**
 * String -> Properties {@link PropertyEditor}
 */
public class StringToPropertiesPropertyEditor extends PropertyEditorSupport implements PropertyEditor {
    // 1. 实现 setAsText(String) 方法
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        // 2. 将 String 类型转换成 Properties 类型
        Properties properties = new Properties();
        try {
            properties.load(new StringReader(text));
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
        // 3. 临时存储 Properties 对象
        setValue(properties);
        // 接下来要 获取临时 Properties 对象 -通过getValue();
    }
    @Override
    public String getAsText() {
        Properties properties = (Properties) getValue();
        StringBuilder textBuilder = new StringBuilder();
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            textBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append(System.getProperty("line.separator")); // 换行符
        }
        return textBuilder.toString();
    }
}
// 模拟 Spring Framework 操作
// 有一段文本 name = 张三;
String text = "name = 张三";
PropertyEditor propertyEditor = new StringToPropertiesPropertyEditor();
// 传递 String 类型的内容
propertyEditor.setAsText(text);
System.out.println(propertyEditor.getValue());
System.out.println(propertyEditor.getAsText());
2、Spring 內建 PropertyEditor 扩展
內建扩展(org.springframework.beans.propertyeditors 包下)(仅限于String转换为其他类型)
| 转换场景 | 实现类 | 
|---|---|
| String -> Byte 数组 | org.springframework.beans.propertyeditors.ByteArrayPropertyEditor | 
| String -> Char | org.springframework.beans.propertyeditors.CharacterEditor | 
| String -> Char 数组 | org.springframework.beans.propertyeditors.CharArrayPropertyEditor | 
| String -> Charset | org.springframework.beans.propertyeditors.CharsetEditor | 
| String -> Class | org.springframework.beans.propertyeditors.ClassEditor | 
| String -> Currency | org.springframework.beans.propertyeditors.CurrencyEditor | 
| … | … | 
ByteArrayPropertyEditor
我们可以看出,基本也都是继承了PropertyEditorSupport ,实现了setAsText和getAsText方法。其他转换器都类似,我们上面的代码实例也是这样实现的。
public class ByteArrayPropertyEditor extends PropertyEditorSupport {
	@Override
	public void setAsText(@Nullable String text) {
		setValue(text != null ? text.getBytes() : null);
	}
	@Override
	public String getAsText() {
		byte[] value = (byte[]) getValue();
		return (value != null ? new String(value) : "");
	}
}
3、自定义 PropertyEditor 扩展整合到springframework
扩展模式: 扩展 java.beans.PropertyEditorSupport 类
实现 org.springframework.beans.PropertyEditorRegistrar
 • 实现 registerCustomEditors(org.springframework.beans.PropertyEditorRegistry) 方法
 • 将 PropertyEditorRegistrar 实现注册为 Spring Bean
向 org.springframework.beans.PropertyEditorRegistry 注册自定义 PropertyEditor 实现
 • 通用类型实现 registerCustomEditor(Class<?>, PropertyEditor) • Java Bean 属性类型实现:registerCustomEditor(Class<?>, String, PropertyEditor)
代码实例
我们复用上面的StringToPropertiesPropertyEditor。
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.stereotype.Component;
/**
 * 自定义 {@link PropertyEditorRegistrar} 实现
 *
 * @see PropertyEditorRegistrar
 */
// @Component // 3. 将其声明为 Spring Bean
public class CustomizedPropertyEditorRegistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        // 1. 通用类型转换
        // 2. Java Bean 属性类型转换
        registry.registerCustomEditor(Properties.class, "context", new StringToPropertiesPropertyEditor());
    }
}
<bean id="customEditorConfigurer" class="com.test.config.CustomEditorConfigurer">
 <property name="propertyEditorRegistrars">
        <list>
            <bean class="com.test.conversion.CustomizedPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>
<bean id="user" class="com.test.domain.User">
    <property name="id" value="1"/>
    <property name="name" value="张三"/>
    <property name="context"> <!-- Properties 类型 -->
        <value>
            id = 1
            name = zhangsan
        </value>
    </property>
    <property name="contextAsText" ref="context"/> <!-- Properties -> String -->
</bean>
private Properties context;
private String contextAsText;
Spring PropertyEditor 的设计缺陷
Spring3之后弃用了PropertyEditor ,PropertyEditor 的设计缺陷主要有以下三种:
1、违反职责单一原则
 • java.beans.PropertyEditor 接口职责太多,除了类型转换,还包括 Java Beans 事件和 Java GUI 交互
2、java.beans.PropertyEditor 实现类型局限
 • 来源类型只能为 java.lang.String 类型
3、java.beans.PropertyEditor 实现缺少类型安全
 • 除了实现类命名可以表达语义,实现类无法感知目标转换类型
三、Spring 3 通用类型转换接口
1、初识Converter、GenericConverter接口
类型转换接口 - org.springframework.core.convert.converter.Converter<S,T>
 • 泛型参数 S:来源类型,参数 T:目标类型
 • 核心方法:T convert(S)
通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter
 • 核心方法:convert(Object,TypeDescriptor,TypeDescriptor)
 • 配对类型:org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
 • 类型描述:org.springframework.core.convert.TypeDescriptor
我们看一下Converter接口,泛型S、T分别代表来源和目标类型,这个接口的实现是线程安全的,并且可以共享:
@FunctionalInterface
public interface Converter<S, T> {
	@Nullable
	T convert(S source);
}
我们再看一下ConditionalConverter接口,Converter接口尽管强大但是仍有不足,增加了一个有条件的接口:
public interface ConditionalConverter {
	// 转换之前,预判输入类型和输出类型
	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
TypeDescriptor 就是类型描述,里面包含了许多类型的信息。
我们再来看一下通用类型转换接口-GenericConverter接口,使用ConvertiblePair包装了一个Set,可以支持多种类型:
public interface GenericConverter {
	@Nullable
	Set<ConvertiblePair> getConvertibleTypes();
	@Nullable
	Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
	/**
	 * Holder for a source-to-target class pair.
	 */
	final class ConvertiblePair {
		private final Class<?> sourceType;
		private final Class<?> targetType;
		/**
		 * Create a new source-to-target pair.
		 * @param sourceType the source type
		 * @param targetType the target type
		 */
		public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
			Assert.notNull(sourceType, "Source type must not be null");
			Assert.notNull(targetType, "Target type must not be null");
			this.sourceType = sourceType;
			this.targetType = targetType;
		}
		public Class<?> getSourceType() {
			return this.sourceType;
		}
		public Class<?> getTargetType() {
			return this.targetType;
		}
		@Override
		public boolean equals(@Nullable Object other) {
			if (this == other) {
				return true;
			}
			if (other == null || other.getClass() != ConvertiblePair.class) {
				return false;
			}
			ConvertiblePair otherPair = (ConvertiblePair) other;
			return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType);
		}
		@Override
		public int hashCode() {
			return (this.sourceType.hashCode() * 31 + this.targetType.hashCode());
		}
		@Override
		public String toString() {
			return (this.sourceType.getName() + " -> " + this.targetType.getName());
		}
	}
}
2、Spring 內建类型转换器
| 转换场景 | 实现类所在包名(package) | 
|---|---|
| 日期/时间相关 | org.springframework.format.datetime | 
| Java 8 日期/时间相关 | org.springframework.format.datetime.standard | 
| 通用实现 | org.springframework.core.convert.support | 
通过这些源码来看,使用方式与上面的PropertyEditor方式有着较大的差别(篇幅限制,大家自行翻阅源码)。
3、Converter 接口的局限性及解决方案
局限一:缺少 Source Type 和 Target Type 前置判断
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,没有判断是否支持这个参数类型的转换。
应对:增加 org.springframework.core.convert.converter.ConditionalConverter 实现,同时实现Converter、ConditionalConverter 接口,其中ConditionalConverter 接口的matches方法可以判断是否支持该类型参数的转换。
局限二:仅能转换单一的 Source Type 和 Target Type
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,对集合类型的参数转换不友好。
应对:使用 org.springframework.core.convert.converter.GenericConverter 代替,GenericConverter 接口我们上面也分析到,用一个内部类ConvertiblePair可以实现集合类型转换。
实际上,Converter和GenericConverter 会相互配合,简单类型的转换一般使用Converter,复合类型的转换一般使用GenericConverter
4、GenericConverter 接口详解
org.springframework.core.convert.converter.GenericConverter接口,是一个通用的类型转换接口,它比Converter适用性更广。
| 核心要素 | 说明 | 
|---|---|
| 使用场景 | 主要用于“复合”类型转换场景,比如 Collection、Map、数组等 | 
| 转换范围 | Set<ConvertiblePair> getConvertibleTypes() | 
| 配对类型 | org.springframework.core.convert.converter.GenericConverter.ConvertiblePair | 
| 转换方法 | convert(Object,TypeDescriptor,TypeDescriptor) | 
| 类型描述 | org.springframework.core.convert.TypeDescriptor | 
GenericConverter接口的局限性
与Converter接口一样,GenericConverter接口也缺少 Source Type 和 Target Type 前置判断。
同时,GenericConverter接口单一类型转换实现复杂,也就是说,GenericConverter比较适合复合类型的转换,Converter接口比较适合单一类型的转换。
GenericConverter 优化接口 - ConditionalGenericConverter接口
ConditionalGenericConverter接口整合了GenericConverter接口和ConditionalConverter接口,解决了GenericConverter接口缺少 Source Type 和 Target Type 前置判断的问题。
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
案例分析
我们分析一下StringToCollectionConverter转换类(其他大量转换类同理):
final class StringToCollectionConverter implements ConditionalGenericConverter {
	private final ConversionService conversionService; // 通过ConversionService 来进行处理
	public StringToCollectionConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(String.class, Collection.class));
	}
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		 // 通过ConversionService 来进行处理,判断类型是否匹配
		return (targetType.getElementTypeDescriptor() == null ||
				this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()));
	}
	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}
		String string = (String) source;
		String[] fields = StringUtils.commaDelimitedListToStringArray(string);
		TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
		Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
				(elementDesc != null ? elementDesc.getType() : null), fields.length);
		if (elementDesc == null) {
			for (String field : fields) {
				target.add(field.trim());
			}
		}
		else {
			for (String field : fields) {
				Object targetElement = this.conversionService.convert(field.trim(), sourceType, elementDesc);
				target.add(targetElement);
			}
		}
		return target;
	}
}
5、扩展 Spring 类型转换器
实现转换器接口
 • org.springframework.core.convert.converter.Converter
 • org.springframework.core.convert.converter.ConverterFactory
 • org.springframework.core.convert.converter.GenericConverter
注册转换器实现
 • 通过 ConversionServiceFactoryBean Spring Bean
 • 通过 org.springframework.core.convert.ConversionService API
代码实例
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
 * {@link Properties} -> {@link String} {@link ConditionalGenericConverter} 实现
 *
 * @see Properties
 * @see ConditionalGenericConverter
 */
public class PropertiesToStringConverter implements ConditionalGenericConverter {
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return Properties.class.equals(sourceType.getObjectType())
                && String.class.equals(targetType.getObjectType());
    }
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(Properties.class, String.class));
    }
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        Properties properties = (Properties) source;
        StringBuilder textBuilder = new StringBuilder();
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            textBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append(System.getProperty("line.separator"));
        }
        return textBuilder.toString();
    }
}
<!-- 声明 ConversionServiceFactoryBean 并且 name 必须为 "conversionService" -->
<bean id="conversionService" class="com.test.ConversionServiceFactoryBean">
    <property name="converters">
        <bean class="com.test.PropertiesToStringConverter"/>
    </property>
</bean>
<!-- java.util.Properties -->
<util:properties id="context">
    <prop key="id">1</prop>
    <prop key="name">zhangsan</prop>
</util:properties>
<bean id="user" class="com.test.domain.User">
    <property name="contextAsText" ref="context"/> <!-- Properties -> String -->
</bean>
6、内置统一类型转换服务-ConversionService
通过类型转换的源码我们看到,类型转换都需要使用org.springframework.core.convert.ConversionService类型转换服务。
| 实现类型 | 说明 | 
|---|---|
| GenericConversionService | 通用 ConversionService 模板实现,不内置转化器实现 | 
| DefaultConversionService | 基础 ConversionService 实现,内置常用转化器实现 | 
| FormattingConversionService | 通用 Formatter + GenericConversionService 实现,不内置转化器和Formatter 实现 | 
| DefaultFormattingConversionService | DefaultConversionService + 格式化 实现(如:JSR-354 Money & Currency, JSR-310 Date-Time) | 
以上是Spring自带的类型转换服务,Springboot中提供了WebConversionService继承了DefaultFormattingConversionService,在这个基础上做了一些扩展。
tips1
在xml文件中注册ConversionServiceFactoryBean,此类中组合了GenericConversionService(实际上是DefaultConversionService),该类作为Bean定义时,名称一定要是conversionService。
在AbstractApplicationContext#finishBeanFactoryInitialization中,beanFactory.setConversionService(
 beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
 },
 此处会以CONVERSION_SERVICE_BEAN_NAME(conversionService)作为bean名称,ConversionService.class作为类型传进去,出于好奇,想对比一下xml文件中定义的conversionService和beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class))获取到的是不是同一个对象,结果发现applicationContext.getBean(“conversionService”)拿到的居然不是ConversionServiceFactoryBean,而是其组合的DefaultConversionService,通过跟踪getBean发现,在AbstractBeanFactory#getObjectForBeanInstance中会判断该对象是否为FactoryBean,如果是,则在FactoryBeanRegistrySupport#doGetObjectFromFactoryBean中通过factory.getObject()返回,返回的正是ConversionServiceFactoryBean中组合的DefaultConversionService。
因此beanFactory.setConversionService中传入的就是ConversionServiceFactoryBean中组合的DefaultConversionService,在doCreatBean中,会去获取BeanWrapper实例(BeanWrapperImpl),实际上最终是通过
 protected void initBeanWrapper(BeanWrapper bw) {
 bw.setConversionService(getConversionService());
 registerCustomEditors(bw);
 }
 设置conversionService对象。
综上所述,conversionService实际是ConversionServiceFactoryBean中组合的GenericConversionService贯穿了整个上下文。
tips2
那么GenericConversionService是在什么时候被实例化的呢?
不难发现,ConversionServiceFactoryBean实现了InitializingBean接口,在回调方法afterPropertiesSet中实例化:
 this.conversionService = createConversionService();
 protected GenericConversionService createConversionService() {
 return new DefaultConversionService();
 }
 构造GenericConversionService的过程中,注册了一些诸如:ByteBufferConverter、StringToTimeZoneConverter、ZoneIdToTimeZoneConverterZonedDateTimeToCalendarConverter、ObjectToObjectConverter、IdToEntityConverter、FallbackObjectToStringConverter、ObjectToOptionalConverter的转换器,然后通过ConversionServiceFactory.registerConverters(this.converters, this.conversionService),将自定义的转换器注册到conversionService中。
7、ConversionService 作为依赖
类型转换器底层接口 - org.springframework.beans.TypeConverter
 • 起始版本:Spring 2.0
 • 核心方法 - convertIfNecessary 重载方法
 • 抽象实现 - org.springframework.beans.TypeConverterSupport
 • 简单实现 - org.springframework.beans.SimpleTypeConverter
类型转换器底层抽象实现 - org.springframework.beans.TypeConverterSupport
 • 实现接口 - org.springframework.beans.TypeConverter
 • 扩展实现 - org.springframework.beans.PropertyEditorRegistrySupport
 • 委派实现 - org.springframework.beans.TypeConverterDelegate
类型转换器底层委派实现 - org.springframework.beans.TypeConverterDelegate
 • 构造来源 - org.springframework.beans.AbstractNestablePropertyAccessor 实现:org.springframework.beans.BeanWrapperImpl
 • 依赖 - java.beans.PropertyEditor 实现:默认內建实现 - PropertyEditorRegistrySupport#registerDefaultEditors
 • 可选依赖 - org.springframework.core.convert.ConversionService 实现
TypeConverter
TypeConverter接口在Spring2.0时就已经存在了。
TypeConverter接口有三个重载方法:convertIfNecessary,顾名思义,就是如果能转的时候就会转换。
TypeConverterSupport
TypeConverterSupport抽象类继承了PropertyEditorRegistrySupport,实现了TypeConverter。
// org.springframework.beans.TypeConverterSupport#convertIfNecessary(java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)
@Nullable
@Override
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
		@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
	Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
	try {
		return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
	}
	catch (ConverterNotFoundException | IllegalStateException ex) {
		throw new ConversionNotSupportedException(value, requiredType, ex);
	}
	catch (ConversionException | IllegalArgumentException ex) {
		throw new TypeMismatchException(value, requiredType, ex);
	}
}
通过以上源码可以看出,它会将类型转换委派给TypeConverterDelegate,调用其convertIfNecessary方法。
TypeConverterDelegate
TypeConverterDelegate的核心方法convertIfNecessary就是转换逻辑,先调用ConversionService的canConvert判断能否转换,然后调用convert方法进行转换。
TypeConverterDelegate在AbstractNestablePropertyAccessor的构造方法中new了一个出来,而AbstractNestablePropertyAccessor有一个实现类,就是BeanWrapperImpl。
也就是说我们的BeanWrapper在创建的时候,就关联了一个TypeConverterDelegate,也就是关联了一个类型转换服务。
整体流程
AbstractApplicationContext -> finishBeanFactoryInitialization方法
 -> 获取beanName为"conversionService" 的ConversionService Bean
 -> ConfigurableBeanFactory#setConversionService(ConversionService)
 -> AbstractAutowireCapableBeanFactory#doCreateBean
 -> 调用createBeanInstance方法,在instantiateBean方法中,会new BeanWrapperImpl
 -> 调用initBeanWrapper方法,set了conversionService
 -> AbstractAutowireCapableBeanFactory.instantiateBean
 -> AbstractBeanFactory#getConversionService ->
BeanDefinition -> BeanWrapper -> 属性转换(数据来源:PropertyValues)->
 setPropertyValues(PropertyValues) -> TypeConverter#convertIfNecessnary ->
 TypeConverterDelegate#convertIfNecessnary -> PropertyEditor or ConversionService
参考资料
极客时间-《小马哥讲 Spring 核心编程思想》










