类型转换基本意思就是把一个数据从一个数据类型转换为另一种数据类型的值。
Spring的类型转换
Spring中,类型转换是将外部化(来自输入或其它来源)的bean属性值按属性对应的数据类型进行转换。类型转换在Spring容器中广泛被使用。也可以在应用程序中任何需要类型转换的地方使用公共API。
Spring的core.covert包提供了一个通用的类型转换系统。系统定义了一个SPI来实现类型转换逻辑,以及一个API来在运行时执行类型转换。
转换SPI
实现类型转换逻辑的SPI如下:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
core.covert.support包中提供了多个转换器实现,包括从字符串到数字的转换器以及其他常见类型。典型例子如下:
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
我们也可以实现自己的转换器,实现该接口即可。
GenericConverter
当需要复杂的Converter实现时,请考虑使用GenericConverter接口。GenericConverter具有比Converter更灵活,支持在多个源类型和目标类型之间进行转换。此外,GenericConverter提供了可用的源字段和目标字段上下文,可以在实现转换逻辑时使用这些上下文。这样的上下文允许由字段注释或字段签名上声明的泛型信息来驱动类型转换。GenericConverter接口定义如下:
package org.springframework.core.convert.converter;
public interface GenericConverter {
// 支持的源烈性->目标类型对(Pair)
public Set<ConvertiblePair> getConvertibleTypes();
// 转换处理 TypeDescriptor见下一节
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
Spring很多转换器都实现了GenericConverter接口,例如ArrayToCollectionConverter。
GenericConverter同Converter区别主要是:Converter大多提供基础类型转换;GenericConverter提供复杂类型转换。
另Spring还提供了ConditionalGenericConverter,是为了满足转换器仅在特定条件成立时运行。例如,仅在目标字段上存在特定注释的情况下运行Converter,或者只在目标类上定义了特定方法(例如静态valueOf方法)的情况下才运行Converter。
TypeDescriptor
org.springframework.core.convert.TypeDescriptor是为了统一描述bean属性类型的类,使类型转换可用统一的方式进行。其能力如下:
1、基于属性(Property)、字段(Field)、方法参数(MethodParameter)、解析类型(ResolvableType)构建TypeDescriptor
2、可基于实例对象(TypeDescriptor.forObject(Object ))、类(TypeDescriptor.valueOf(Class<?>))获取TypeDescriptor
3、提供了针对数组、结合、Map等类型获取其元素类型对应的TypeDescriptor
4、可判断类型(TypeDescriptor)是否是数组、集合、Map等
5、可获取属性是否有注解
6、可获取注解及注解信息
7、可获取类型嵌套层数,如List嵌套层数为1,List<List>嵌套层数为2
解析类型(ResolvableType):是Spring对Java.lang.reflect.Type的封装,提供对getSuperType()、getInterface()和getGeneric(int…)(泛型参数)的访问能力,最终可解析到Java.lang.Class。
ConversionService
ConversionService接口是定义类型转换方法,定义如下:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
其默认实现为GenericConversionService。在Spring应用程序中,通常为每个Spring容器(或ApplicationContext)配置一个ConversionService实例。Spring获取ConversionService,并在框架需要执行类型转换时使用它。应用也可以将此ConversionService注入到任何bean中并直接调用它。
类关系图
类型转换的实现在TypeConverterDelegate,该类聚合到TypeConverterSupport。TypeConverterSupport是抽象类,子孙类BeanWrapperImpl主要用数据绑定(见“https://editor.csdn.net/md?articleId=131913078”),这也是TypeConverter主要使用场景。
源码
类型转换的实现处理在TypeConverterDelegate.convertIfNecessary,我们以此开始分析:
TypeConverterDelegate.convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class requiredType, @Nullable TypeDescriptor typeDescriptor)
/**
* 转换值到特定属性的数据类型
* @param propertyName 属性名
* @param oldValue 属性原有的值(通常为null)
* @param newValue 待转换数据
* @param requiredType 要转换的数据类型
* @param typeDescriptor 要转换的数据类型的typeDescriptor
* @return 返回转换后的新值
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 获取属性编辑器(首先按propertyName获取,如果没有再按requiredType获取)
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// 获取用于类型转换的服务(实现是GenericConversionService)
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
// 获取newValue的TypeDescriptor
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
// 类型可以转换
try {
// 转换
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// 转换发生异常,记录异常,并继续往下执行
conversionAttemptEx = ex;
}
}
}
// 转换变量赋初值为待转换的数据(提醒:待转换的数据在转换过程中可能需要经过多次转换,如:字符串先到字符串数组再转换等)
Object convertedValue = newValue;
// Value not of required type?
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String text) {
// 获取要转换的数据类型typeDescriptor的ElementTypeDescriptor(数组或集合或Stream的TypeDescriptor)
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
// 把包含有分隔符的数据转换为字符串数组
convertedValue = StringUtils.commaDelimitedListToStringArray(text);
}
}
}
if (editor == null) {
// 按需要类型取属性编辑器
editor = findDefaultEditor(requiredType);
}
// 使用给定的属性编辑器将值转换为所需的类型
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
boolean standardConversion = false;
if (requiredType != null) {
if (convertedValue != null) {
/* 标准类型转换 */
if (Object.class == requiredType) {
// requiredType是Object类型,直接返回
return (T) convertedValue;
}
else if (requiredType.isArray()) {
// 数组类型转换
if (convertedValue instanceof String text && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray(text);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
else if (convertedValue instanceof Collection<?> coll) {
// 集合类型转换
convertedValue = convertToTypedCollection(coll, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
else if (convertedValue instanceof Map<?, ?> map) {
// Map类型转换
convertedValue = convertToTypedMap(map, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
// 处理待转换数据是数组类型但只有一个数据的情况
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
// 目标类型是String且convertedValue是基本类型(boolean, byte, * char, short, int, long, float, double, void或包装过的Boolean, Byte, Character, Short, Integer, Long, Float,Double, or Void),直接串化返回
return (T) convertedValue.toString();
}
else if (convertedValue instanceof String text && !requiredType.isInstance(convertedValue)) {
/* 待转换值是String,但不能直接实例化为目标类型 */
if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
try {
// 前面转换无异常且目标类型非接口非枚举情况下,用字符串的构建器实例化待转换值
Constructor<T> strCtor = requiredType.getConstructor(String.class);
return BeanUtils.instantiateClass(strCtor, convertedValue);
}
catch (NoSuchMethodException ex) {
// proceed with field lookup
if (logger.isTraceEnabled()) {
logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
}
}
}
// 去掉待转换值空格
String trimmedValue = text.trim();
if (requiredType.isEnum() && trimmedValue.isEmpty()) {
// 目标类型为枚举,但待转换值为空返回null
return null;
}
// 把待转换值转换为枚举类型
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion = true;
}
else if (convertedValue instanceof Number num && Number.class.isAssignableFrom(requiredType)) {
// 待转换值为Number类型(包括Byte、Short、Integer、Long、BigInteger、Float、Double、BigDecimal)且目标类型也是,把待转换值转换为Number
convertedValue = NumberUtils.convertNumberToTargetClass(num, (Class<Number>) requiredType);
standardConversion = true;
}
}
else {
// convertedValue == null,如果requiredType是Optional,给空值
if (requiredType == Optional.class) {
convertedValue = Optional.empty();
}
}
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
// convertedValue不能按目标类型赋值
if (conversionAttemptEx != null) {
// 有异常直接抛出
throw conversionAttemptEx;
}
else if (conversionService != null && typeDescriptor != null) {
// 前面用属性编辑器生成convertedValue不满足目标类型赋值(此种情况下没有用conversionService转换过),且满足类型可转换,则用conversionService转换返回
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
// 定义异常消息,抛出异常(IllegalArgumentException/IllegalStateException)
StringBuilder msg = new StringBuilder();
msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append('\'');
if (propertyName != null) {
msg.append(" for property '").append(propertyName).append('\'');
}
if (editor != null) {
msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
"] returned inappropriate value of type '").append(
ClassUtils.getDescriptiveType(convertedValue)).append('\'');
throw new IllegalArgumentException(msg.toString());
}
else {
msg.append(": no matching editors or conversion strategy found");
throw new IllegalStateException(msg.toString());
}
}
}
if (conversionAttemptEx != null) {
if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
/* 无属性编辑器情况发生异常,且后续也没有转换成功,则抛异常 */
throw conversionAttemptEx;
}
logger.debug("Original ConversionService attempt failed - ignored since " +
"PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
}
return (T) convertedValue;
}
GenericConversionService.convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType)
类型转换。
@Override
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
// 源类型为空
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
// 源数据和源类型不匹配
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
// 获取转换器,不同类型的转换器是不同的 注1
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
// 实际就是执行converter.convert(source, sourceType, targetType),ConversionUtils对其封装(目的是异常处理)
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
// 返回result (内部处理只针对result==null情况)
return handleResult(sourceType, targetType, result);
}
// 未找到对应转换器的处理
return handleConverterNotFound(source, sourceType, targetType);
}
注1:类型转换器实现见下图:
应用示例
@Component
public class DemoTypeConverter {
public void demo() {
// 简单转换
DefaultConversionService defaultConversionService = new DefaultConversionService();
System.out.println(defaultConversionService.convert(" 20", Integer.class));
System.out.println(defaultConversionService.convert(20.1, String.class));
/* 基于bean转换 */
Address addr=new Address();
addr.setProvince("四川");
addr.setCity("成都");
addr.setCounty("高新区");
addr.setDesc("学习大道168");
Map<String, Object> input = new HashMap<>();
input.put("surname", "wang");
input.put("name", "wang");
input.put("age", 20);
input.put("address", addr);
Driver driver=new Driver();
// 生成driver的封装类,偏于操作相关属性
BeanWrapperImpl beanWrapper=new BeanWrapperImpl(driver);
beanWrapper.setExtractOldValueForEditor(true);
beanWrapper.setAutoGrowNestedPaths(true);
beanWrapper.setAutoGrowCollectionLimit(10000);
// 值按指定属性类型转换 (自动trim空格)
System.out.println(beanWrapper.convertForProperty(30, "age"));
System.out.println(beanWrapper.convertForProperty("30", "age"));
System.out.println(beanWrapper.convertForProperty(" 30", "age"));
// System.out.println(beanWrapper.convertForProperty("aa", "age")); // 会抛异常
// 转换input值到driver对应属性
input.forEach((key,value)->{
beanWrapper.setPropertyValue(key, beanWrapper.convertForProperty(value,key));
// 打印driver属性值
System.out.println(key+":"+beanWrapper.getPropertyValue(key));
});
}
}