spring 数据类型转换

·  阅读 452

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接口

PropertyEditorJavaBeans规范定义的一个接口,其有一部分方法是与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子类均是重写getAsTextsetAsText这两个方法的。也就是说,**在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类可以将StringClass这两种类型相互转换。

PropertyEditorRegistry接口

前文提到,ClassEditor类可以将StringClass这两种类型相互转换。但当出现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类型的数据转成ByteDoubleFloatInteger等等,而这些目标类型,都是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的转换可以共用转换逻辑,而这些所有的TS并非一个类族,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);
}
复制代码
复制代码

默认情况下,注入到ApplicationContextConversionService实现是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类仅仅是一个工具类,不适合可配置。


作者:yangqingbang
链接:juejin.cn/post/687376… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改