Spring的类型转换体系比较混乱,原有PropertyEditor体系,Spring 3.0之后,又引入了ConversionService体系,导致很多人在初次接触这块源码时会有很多疑惑。本文会先简要介绍什么是类型转换体系,然后分别分析PropertyEditor体系和ConversionService体系,接着分析两种体系分别在什么时机被使用并回归到TypeConverter这个主线接口,最后从宏观角度对各模块的聚合关系做个总结。本系列文章的源码分析是基于Spring 5.0版本进行的。
本文原创,转载请注明出处。
什么是类型转换体系
类型转换体系指的是参与完成类型转换功能的所有类/接口的集合。Spring
中,需要类型转换的场景比较多,这里举两个例子:
- 下面是
Spring
容器的一个配置文件,容器启动过程中,需要将"name"
属性转换为String
类型,"age"
属性转换为Integer
类型:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="china" class="im.juejin.yangqingbang.Country">
<property name="name" value="中华人民共和国"/>
<property name="age" value="70"/>
</bean>
</beans>
- 下面是使用
Spring MVC
框架编写的代码,这里需要将request
中的参数age
转换为Integer
类型:
@GetMapping("/test")
public ResponseEntity test(@RequestParam("age") Integer age) {
return ResponseEntity.builder().build();
}
PropertyEditor体系
PropertyEditor接口
PropertyEditor
是JavaBeans
规范定义的一个接口,其有一部分方法是与GUI编程相关的,这些方法在Spring
中用不到。在Spring
中,基本所有PropertyEditor
接口的实现类均是继承自PropertyEditorSupport
这个基类,下面列出重点方法:
public class PropertyEditorSupport implements PropertyEditor {
private Object value;
public void setValue(Object value) { this.value = value; }
public Object getValue() { return value; }
public String getAsText() {
// 默认实现省略,子类会重写这个实现
}
public void setAsText(String text) throws java.lang.IllegalArgumentException {
// 默认实现省略,子类会重写这个实现
}
}
基本上,Spring
中的所有PropertyEditorSupport
子类均是重写getAsText
和setAsText
这两个方法的。也就是说,**在Spring
中,PropertyEditor
接口主要用于字符串和对象之间相互转换。**下面是一个代表性实现:
public class ClassEditor extends PropertyEditorSupport {
private final ClassLoader classLoader;
public ClassEditor() { this(null); }
public ClassEditor(ClassLoader classLoader) {
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text)) {
setValue(ClassUtils.resolveClassName(text.trim(), this.classLoader));
}
else {
setValue(null);
}
}
@Override
public String getAsText() {
Class<?> clazz = (Class<?>) getValue();
if (clazz != null) {
return ClassUtils.getQualifiedName(clazz);
} else {
return "";
}
}
}
ClassEditor
类可以将String
和Class
这两种类型相互转换。
PropertyEditorRegistry接口
前文提到,ClassEditor
类可以将String
和Class
这两种类型相互转换。但当出现String
类型数据需要转换成Class
类型的时候,Spring
是如何知道ClassEditor
类可以完成这个工作的?PropertyEditorRegistry
接口就是用来设置这个绑定关系的:
public interface PropertyEditorRegistry {
// 注册目标类型的PropertyEditor
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
// 注册目标类型指定属性的PropertyEditor
void registerCustomEditor(Class<?> requiredType, String propertyPath, PropertyEditor propertyEditor);
// 根据目标类型和指定属性查找PropertyEditor,指定属性为空,就是查找目标类型的PropertyEditor
PropertyEditor findCustomEditor(Class<?> requiredType, String propertyPath);
}
注释中的指定属性,其实就是目标类型的属性路径,假设有TestBean
:
public class TestBean {
private Integer age;
// 省略getter、setter方法
}
当TestBean.age
需要特殊转换时,可以这样指定:
propertyEditorRegistry.registerCustomEditor(TestBean.class,"age",new XxxPropertyEditor());
这其中的"age"
就是属性路径。Spring
为我们绑定了常见类型的PropertyEditor
,位于PropertyEditorRegistry
接口的默认实现类PropertyEditorRegistrySupport
中:
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
// 创建绑定关系Map
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
// 省略更多
}
// 省略其他方法
}
PropertyEditorRegistrar接口
public interface PropertyEditorRegistrar {
void registerCustomEditors(PropertyEditorRegistry registry);
}
这个接口用于往Spring
注册多个PropertyEditorRegistry
。
CustomEditorConfigurer类
CustomEditorConfigurer
类是BeanFactoryPostProcessor
接口的实现类,用于往ApplicationContext
注册自定义PropertyEditor
:
public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {
private PropertyEditorRegistrar[] propertyEditorRegistrars;
private Map<Class<?>, Class<? extends PropertyEditor>> customEditors;
public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
this.propertyEditorRegistrars = propertyEditorRegistrars;
}
public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
this.customEditors = customEditors;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
}
}
if (this.customEditors != null) {
this.customEditors.forEach(beanFactory::registerCustomEditor);
}
}
// 省略无关重要字段、方法
}
ConversionService体系
Spring 3.0
引入了一套新的类型转换系统,位于org.springframework.core.convert
包中。新的类型转换系统,可以替代PropertyEditor
完成字符串到特定类型的转换,除此之外,它还可以完成任意类型之间的相互转换。
Converter接口
Converter
接口是一个类型转换接口,完成S->T
的转换:
public interface Converter<S, T> {
T convert(S source);
}
ConverterFactory接口
假如有这么一个需求,需要将Character
类型的数据转成Byte
、Double
、Float
、Integer
等等,而这些目标类型,都是Number
类型的子类。假如直接使用Converter
接口实现,要么每个目标类型实现一个实现类,要么泛型直接向上转型为Number
,拿到结果再进行向下强转。前者会造成类膨胀,后者则弱化了编译器类型检查,两者都不是一个优雅的解决方案。ConverterFactory
接口就是为了优雅地解决这个问题的:
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
可以看到,从ConverterFactory.getConverter
方法中获取到的Converter<S, T>
,其泛型参数T
是根据参数targetType
决定的,无需转型就可以通用。另一个问题就是,如何实现一个Converter<Character, T extends Number>
,让其对所有Number
子类通用?Spring
是这样实现的:
final class CharacterToNumberFactory implements ConverterFactory<Character, Number> {
@Override
public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
return new CharacterToNumber<>(targetType);
}
private static final class CharacterToNumber<T extends Number> implements Converter<Character, T> {
private final Class<T> targetType;
public CharacterToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T convert(Character source) {
// 根据targetType,完成Character到T的转换
return NumberUtils.convertNumberToTargetClass((short) source.charValue(), this.targetType);
}
}
}
GenericConverter接口
ConverterFactory
接口解决的是类族问题,如果存在不止一对T->S
的转换可以共用转换逻辑,而这些所有的T
或S
并非一个类族,Spring
提供GenericConverter
接口解决这个问题:
public interface GenericConverter {
// 返回所有可转换的S->T对
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
// S->T对
final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
// 省略其他无关重要方法
}
使用GenericConverter
接口,需要先调用getConvertibleTypes
返回所有ConvertiblePair
进行判断,判断成功后,再调用convert
方法完成转换逻辑。
ConditionalConverter接口
GenericConverter
接口需要根据S->T
对进行判断,有时候我们并不能确定所有S->T
对,只需要符合某些特定条件的类(比如特定类注解),转换器就可以进行转换。为此,Spring
为我们提供了ConditionalConverter
接口:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
ConditionalGenericConverter接口
通常,GenericConverter
接口和ConditionalConverter
需要组合使用,Spring
为我们提供了一个组合接口:
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
这样,ConditionalGenericConverter
同时存在getConvertibleTypes
(来自GenericConverter
接口)和matches
(来自ConditionalConverter
接口)两个判断方法。Spring
的所有实现中,matches
都会先调用getConvertibleTypes
进行判断,再完成自身判断逻辑的,所以,用户只需调用matches
方法进行判断即可。我们在编写自定义ConditionalGenericConverter
实现时,也需要注意这个问题。
ConversionService接口
上面提到的Converter
接口和GenericConverter
接口,均是用于类型转换的,但它们却不是基于共同的接口,这会给使用者造成不便。于是,Spring
提供了ConversionService
接口,作为两者的门面接口:
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
<T> T convert(Object source, Class<T> targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
默认情况下,注入到ApplicationContext
的ConversionService
实现是DefaultConversionService
。
下面我们逐一来看一下这些接口/方法。
ConverterRegistry接口
ConverterRegistry
接口用于将Converter
接口、GenericConverter
接口、ConverterFactory
接口的实现类注册到ApplicationContext
中:
public interface ConverterRegistry {
void addConverter(Converter<?, ?> converter);
<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
void addConverter(GenericConverter converter);
void addConverterFactory(ConverterFactory<?, ?> factory);
void removeConvertible(Class<?> sourceType, Class<?> targetType);
}
其实现位于GenericConversionService
类中:
public class GenericConversionService implements ConfigurableConversionService {
// 注册Converter
@Override
public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
// 先适配为ConditionalGenericConverter,再注册
addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
}
// 注册ConverterFactory
@Override
public void addConverterFactory(ConverterFactory<?, ?> factory) {
ResolvableType[] typeInfo = getRequiredTypeInfo(factory.getClass(), ConverterFactory.class);
// 省略其与分析无关代码
// 先适配为ConditionalGenericConverter,再注册
addConverter(new ConverterFactoryAdapter(factory,
new ConvertiblePair(typeInfo[0].resolve(Object.class), typeInfo[1].resolve(Object.class))));
}
// Converter适配为ConditionalGenericConverter
private final class ConverterAdapter implements ConditionalGenericConverter {
// 省略
}
// ConverterFactory适配为ConditionalGenericConverter
private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
// 省略
}
// 省略其他与分析无关代码
}
可以看到,Converter
接口和ConverterFactory
接口的实现类,都是先适配为ConditionalGenericConverter
类型,再注册到ConverterRegistry
中。也就是说,对于DefaultConversionService
来说,只有GenericConverter
这一种类型的转换器,因此可以很方便地提供统一接口。
ConfigurableConversionService接口
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {}
可以看到,这仅是一个组合接口。
GenericConversionService类
前文在分析ConverterRegistry
接口时,已经看到GenericConversionService
类完整实现了ConverterRegistry
接口的方法。除此之外,该类还完整实现了ConversionService
接口的方法。因此,该类是ConversionService
体系最为核心的类,完整实现了注册和转换功能。
DefaultConversionService类
public class DefaultConversionService extends GenericConversionService {
public DefaultConversionService() {
addDefaultConverters(this);
}
// 注册默认转换器
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
// 省略更多注册
}
// 省略与分析无关代码
}
该类的职责是注册默认转换器。
ConversionServiceFactoryBean类
ConversionServiceFactoryBean
类是InitializingBean
接口的实现类,用于注册自定义Converter
实现、ConverterFactory
实现和GenericConverter
实现:
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
private Set<?> converters;
private GenericConversionService conversionService;
public void setConverters(Set<?> converters) {
this.converters = converters;
}
@Override
public void afterPropertiesSet() {
this.conversionService = createConversionService();
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
}
// 省略其他与分析无关代码
}
如何使用类型转换体系
TypeConverterDelegate类
前文提到,Spring
同时存在两大类型转换体系:PropertyEditor
体系和ConversionService
体系,那么什么时候使用前者,什么时候使用后者呢?为了用户更方便地进行类型转换,Spring
提供了TypeConverterDelegate
类作为工具类,该类作为门面类,屏蔽了两大转换体系的使用细节,提供简单的对外接口。这个问题的答案,也可以从这个工具类中找到。
class TypeConverterDelegate {
/**
* 类型转换对外接口
*/
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
// 1、当PropertyEditor为空时,才会考虑使用ConversionService进行类型转换
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
// 2、尝试使用PropertyEditor进行类型转换
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
if (requiredType != null) {
// 3、对特定类型(Array、List、Map等),使用PropertyEditor进行类型转换
if (requiredType.isArray()) {
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
// 省略部分else if,这些都是使用PropertyEditor进行类型转换的
// 4、最后如果类型转换依然未能得到执行,再考虑ConversionService进行类型转换
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (conversionService != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
}
}
return (T) convertedValue;
}
// 省略与分析无关代码
}
从convertIfNecessary
方法的几个重要片段中,我们可以分析出,Spring
是优先使用PropertyEditor
体系进行类型转换的,其次才是ConversionService
体系。
TypeConverter接口
在Spring
中,Bean
的属性读写都需要伴随着类型转换的,如将一个String
类型的值设置给Integer
类型的age
属性。先看下面类图:
BeanWrapper
接口,就是负责Bean
属性读写的接口。其继承的TypeConverter
接口,就是负责类型转换的。BeanWrapper
接口也是本系列文章重点之一,后面会有专门的文章分析该接口,下面先来看一下TypeConverter
接口:
public interface TypeConverter {
<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;
}
所有方法见名知意,无需赘言。
TypeConverterSupport类
public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport implements TypeConverter {
TypeConverterDelegate typeConverterDelegate;
/**
* 所有类型转换逻辑,都是委托给该方法
*/
private <T> T doConvert(Object value, Class<T> requiredType,
MethodParameter methodParam, Field field) throws TypeMismatchException {
// 所有转换逻辑,都是委托给typeConverterDelegate对象
if (field != null) {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
}
else {
return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
}
}
// 省略其他与分析无关代码
}
TypeConverterSupport
类是TypeConverter
接口的实现类,可以看到,所有类型转换逻辑,都是委托给TypeConverterDelegate
工具类的。
总结
从宏观的角度看,Spring
类型转换系统可以分为4个模块:PropertyEditor
体系、ConversionService
体系、TypeConverterDelegate
工具类、TypeConverter
接口。各模块之间的关系如下图:
TypeConverter
是委托TypeConverterDelegate
工具类完成转换逻辑的,TypeConverterDelegate
工具类又是委托PropertyEditor
体系和ConversionService
体系完成转换逻辑的。有一点需要注意的是,TypeConverterDelegate
类并不直接与ConversionService
关联,而是通过PropertyEditorRegistrySupport
间接关联。这样做的目的是为了可配置,毕竟TypeConverterDelegate
类仅仅是一个工具类,不适合可配置。