spring 数据类型的转换

spring 数据类型的转换从宏观的角度看, Spring 类型转换系统可以分为4个模块: PropertyEditor 体系、 ConversionService 体系、

欢迎大家来到IT世界,在知识的湖畔探索吧!

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>
复制代码
复制代码

欢迎大家来到IT世界,在知识的湖畔探索吧!

  • 下面是使用 Spring MVC 框架编写的代码,这里需要将 request 中的参数 age 转换为 Integer 类型:
欢迎大家来到IT世界,在知识的湖畔探索吧!@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 接口主要用于字符串和对象之间相互转换。**下面是一个代表性实现:

欢迎大家来到IT世界,在知识的湖畔探索吧!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

spring 数据类型的转换

下面我们逐一来看一下这些接口/方法。

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 属性。先看下面类图:

spring 数据类型的转换

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 接口。各模块之间的关系如下图:

spring 数据类型的转换

TypeConverter 是委托 TypeConverterDelegate 工具类完成转换逻辑的, TypeConverterDelegate 工具类又是委托 PropertyEditor 体系和 ConversionService 体系完成转换逻辑的。有一点需要注意的是, TypeConverterDelegate 类并不直接与 ConversionService 关联,而是通过 PropertyEditorRegistrySupport 间接关联。这样做的目的是为了可配置,毕竟 TypeConverterDelegate 类仅仅是一个工具类,不适合可配置。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/36706.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信