pring类型转换早期使用的PropertyEditor详细介绍

新一代类型转换

为了解决PropertyEditor作为类型转换方式的设计缺陷,Spring 3.0版本重新设计了一套类型转换接口,有3个核心接口:

Converter:Source -> Target类型转换接口,适用于1:1转换

ConverterFactory:Source -> R类型转换接口,适用于1:N转换

GenericConverter:更为通用的类型转换接口,适用于N:N转换

注意:就它没有泛型约束,因为是通用

另外,还有一个条件接口ConditionalConverter,可跟上面3个接口搭配组合使用,提供前置条件判断验证。

这套接口,解决了PropertyEditor做类型转换存在的所有缺陷,且具有非常高的灵活性和可扩展性。下面进入详细了解。

Converter

将源类型S转换为目标类型T。

4@FunctionalInterfacepublicinterface Converter {T convert(S source);}

它是个函数式接口,接口定义非常简单。适合1:1转换场景:可以将任意类型 转换为 任意类型。它的实现类非常多,部分截图如下:

网络异常 取消重新上传

值得注意的是:几乎所有实现类的访问权限都是default/private,只有少数几个是public公开的,下面我用代码示例来“近距离”感受一下。

代码示例

* Converter:1:1

*/@Testpublic void test() {    System.out.println("----------------StringToBooleanConverter---------------");    Converter converter =newStringToBooleanConverter();// trueValues.add("true");// trueValues.add("on");// trueValues.add("yes");// trueValues.add("1");System.out.println(converter.convert("true"));    System.out.println(converter.convert("1"));// falseValues.add("false");// falseValues.add("off");// falseValues.add("no");// falseValues.add("0");System.out.println(converter.convert("FalSe"));    System.out.println(converter.convert("off"));// 注意:空串返回的是nullSystem.out.println(converter.convert(""));    System.out.println("----------------StringToCharsetConverter---------------");    Converter converter2 =newStringToCharsetConverter();// 中间横杠非必须,但强烈建议写上  不区分大小写System.out.println(converter2.convert("uTf-8"));    System.out.println(converter2.convert("utF8"));}

运行程序,正常输出:

1

9----------------StringToBooleanConverter---------------truetruefalsefalsenull----------------StringToCharsetConverter---------------UTF-8UTF-8

说明:StringToBooleanConverter/StringToCharsetConverter访问权限都是default,外部不可直接使用。此处为了做示例用到一个小技巧 -> 将Demo的报名调整为和转换器的一样,这样就可以直接访问。

关注点:true/on/yes/1都能被正确转换为true的,且对于英文字母来说一般都不区分大小写,增加了容错性(包括Charset的转换)。

不足

Converter用于解决1:1的任意类型转换,因此它必然存在一个不足:解决1:N转换问题需要写N遍,造成重复冗余代码。

譬如:输入是字符串,它可以转为任意数字类型,包括byte、short、int、long、double等等,如果用Converter来转换的话每个类型都得写个转换器,想想都麻烦有木有。

Spring早早就考虑到了该场景,提供了相应的接口来处理,它就是ConverterFactory

ConverterFactory

从名称上看它代表一个转换工厂:可以将对象S转换为R的所有子类型,从而形成1:N的关系。

3publicinterface ConverterFactory {Converter getConverter(Class targetType);}

它同样也是个函数式接口。该接口的实现类并不多,Spring Framework共提供了5个内建实现(访问权限全部为default):

网络异常 取消重新上传

以StringToNumberConverterFactory为例看看实现的套路:

26finalclass StringToNumberConverterFactory implements ConverterFactory {@OverridepublicConverter getConverter(Class targetType) {returnnewStringToNumber(targetType);}// 私有内部类:实现Converter接口。用泛型边界约束一类类型privatestaticfinalclass StringToNumber implements Converter {privatefinalClass targetType;public StringToNumber(Class targetType) {this.targetType = targetType;}@Overridepublic T convert(String source) {if(source.isEmpty()) {returnnull;}returnNumberUtils.parseNumber(source,this.targetType);}}}

由点知面,ConverterFactory作为Converter的工厂,对Converter进行包装,从而达到屏蔽内部实现的目的,对使用者友好,这不正是工厂模式的优点么,符合xxxFactory的语义。但你需要清除的是,工厂内部实现其实也是通过众多if else之类的去完成的,本质上并无差异。

12/**

* ConverterFactory:1:N

*/@Testpublic void test2() {    System.out.println("----------------StringToNumberConverterFactory---------------");    ConverterFactory converterFactory =newStringToNumberConverterFactory();// 注意:这里不能写基本数据类型。如int.class将抛错System.out.println(converterFactory.getConverter(Integer.class).convert("1").getClass());    System.out.println(converterFactory.getConverter(Double.class).convert("1.1").getClass());    System.out.println(converterFactory.getConverter(Byte.class).convert("0x11").getClass());}

运行程序,正常输出:

4----------------StringToNumberConverterFactory---------------class java.lang.Integerclass java.lang.Doubleclass java.lang.Byte

关注点:数字类型的字符串,是可以被转换为任意Java中的数字类型的,String(1) -> Number(N)。这便就是ConverterFactory的功劳,它能处理这一类转换问题。

不足

既然有了1:1、1:N,自然就有N:N。比如集合转换、数组转换、Map到Map的转换等等,这些N:N的场景,就需要借助下一个接口GenericConverter来实现。

GenericConverter

它是一个通用的转换接口,用于在两个或多个类型之间进行转换。相较于前两个,这是最灵活的SPI转换器接口,但也是最复杂的。

11publicinterface GenericConverter {Set getConvertibleTypes();Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);// 普通POJOfinalclass ConvertiblePair {privatefinalClass sourceType;privatefinalClass targetType;}}

该接口并非函数式接口,虽然方法不多但稍显复杂。现对出现的几个类型做简单介绍:

ConvertiblePair:维护sourceType和targetType的POJO

getConvertibleTypes()方法返回此Pair的Set集合。由此也能看出该转换器是可以支持N:N的(大多数情况下只写一对值而已,也有写多对的)

TypeDescriptor:类型描述。该类专用于Spring的类型转换场景,用于描述from or to的类型

比单独的Type类型强大,内部借助了ResolvableType来解决泛型议题

GenericConverter的内置实现也比较多,部分截图如下:

网络异常 取消重新上传

ConditionalGenericConverter是GenericConverter和条件接口ConditionalConverter的组合,作用是在执行GenericConverter转换时增加一个前置条件判断方法。

转换器描述示例

ArrayToArrayConverter数组转数组Object[] -> Object[]["1","2"] -> [1,2]

ArrayToCollectionConverter数组转集合 Object[] -> Collection同上

CollectionToCollectionConverter数组转集合 Collection -> Collection同上

StringToCollectionConverter字符串转集合String -> Collection1,2 -> [1,2]

StringToArrayConverter字符串转数组String -> Array同上

MapToMapConverterMap -> Map(需特别注意:key和value都支持转换才行)略

CollectionToStringConverter集合转字符串Collection -> String[1,2] -> 1,2

ArrayToStringConverter委托给CollectionToStringConverter完成同上

------

StreamConverter集合/数组 <-> Stream互转集合/数组类型 -> Stream类型

IdToEntityConverterID->Entity的转换传入任意类型ID -> 一个Entity实例

ObjectToObjectConverter很复杂的对象转换,任意对象之间obj -> obj

FallbackObjectToStringConverter上个转换器的兜底,调用Obj.toString()转换obj -> String

说明:分割线下面的4个转换器比较特殊,字面上不好理解其实际作用,比较“高级”。它们如果能被运用在日常工作中可以事半功弎,因此放在在下篇文章专门给你介绍

下面以CollectionToCollectionConverter为例分析此转换器的“复杂”之处:

14finalclass CollectionToCollectionConverter implements ConditionalGenericConverter {privatefinalConversionService conversionService;public CollectionToCollectionConverter(ConversionService conversionService) {this.conversionService = conversionService;}// 集合转集合:如String集合转为Integer集合@Overridepublic Set getConvertibleTypes() {returnCollections.singleton(newConvertiblePair(Collection.class, Collection.class));}}

这是唯一构造器,必须传入ConversionService:元素与元素之间的转换是依赖于conversionService转换服务去完成的

6CollectionToCollectionConverter:@Overridepublic boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {returnConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(),this.conversionService);}

判断能否转换的依据:集合里的元素与元素之间是否能够转换,底层依赖于ConversionService#canConvert()这个API去完成判断。

接下来再看最复杂的转换方法:

40CollectionToCollectionConverter:@Overridepublic Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {if(source ==null) {returnnull;}Collection sourceCollection = (Collection) source;// 判断:这些情况下,将不用执行后续转换动作了,直接返回即可booleancopyRequired = !targetType.getType().isInstance(source);if(!copyRequired && sourceCollection.isEmpty()) {returnsource;}TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();if(elementDesc ==null&& !copyRequired) {returnsource;}Collection target = CollectionFactory.createCollection(targetType.getType(),(elementDesc !=null? elementDesc.getType() :null), sourceCollection.size());// 若目标类型没有指定泛型(没指定就是Object),不用遍历直接添加全部即可if(elementDesc ==null) {target.addAll(sourceCollection);}else{// 遍历:一个一个元素的转,时间复杂度还是蛮高的// 元素转元素委托给conversionService去完成for(Object sourceElement : sourceCollection) {Object targetElement =this.conversionService.convert(sourceElement,sourceType.elementTypeDescriptor(sourceElement), elementDesc);target.add(targetElement);if(sourceElement != targetElement) {copyRequired =true;}}}return(copyRequired ? target : source);}

该转换步骤稍微有点复杂,我帮你屡清楚后有这几个关键步骤:

快速返回:对于特殊情况,做快速返回处理

若目标元素类型是源元素类型的子类型(或相同),就没有转换的必要了(copyRequired = false)

若源集合为空,或者目标集合没指定泛型,也不需要做转换动作

源集合为空,还转换个啥

目标集合没指定泛型,那就是Object,因此可以接纳一切,还转换个啥

若没有触发快速返回。给目标创建一个新集合,然后把source的元素一个一个的放进新集合里去,这里又分为两种处理case

若新集合(目标集合)没有指定泛型类型(那就是Object),就直接putAll即可,并不需要做类型转换

若新集合(目标集合指定了泛型类型),就遍历源集合委托conversionService.convert()对元素一个一个的转

代码示例

以CollectionToCollectionConverter做示范:List -> Set

16@Testpublic void test3() {    System.out.println("----------------CollectionToCollectionConverter---------------");    ConditionalGenericConverter conditionalGenericConverter =newCollectionToCollectionConverter(newDefaultConversionService());// 将Collection转为Collection(注意:没有指定泛型类型哦)System.out.println(conditionalGenericConverter.getConvertibleTypes());    List sourceList = Arrays.asList("1","2","2","3","4");    TypeDescriptor sourceTypeDesp = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class));    TypeDescriptor targetTypeDesp = TypeDescriptor.collection(Set.class, TypeDescriptor.valueOf(Integer.class));    System.out.println(conditionalGenericConverter.matches(sourceTypeDesp, targetTypeDesp));    Object convert = conditionalGenericConverter.convert(sourceList, sourceTypeDesp, targetTypeDesp);    System.out.println(convert.getClass());    System.out.println(convert);}

运行程序,正常输出:

4[java.util.Collection -> java.util.Collection]trueclass java.util.LinkedHashSet[1, 2, 3, 4]

关注点:target最终使用的是LinkedHashSet来存储,这结果和CollectionFactory#createCollection该API的实现逻辑是相关(Set类型默认创建的是LinkedHashSet实例)。

不足

如果说它的优点是功能强大,能够处理复杂类型的转换(PropertyEditor和前2个接口都只能转换单元素类型),那么缺点就是使用、自定义实现起来比较复杂。这不官方也给出了使用指导意见:在Converter/ConverterFactory接口能够满足条件的情况下,可不使用此接口就不使用。

ConditionalConverter

条件接口,@since 3.2。它可以为Converter、GenericConverter、ConverterFactory转换增加一个前置判断条件。

3publicinterface ConditionalConverter {boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);}

龙华大道1号 http://www.kinghill.cn/Dynamics/2106.html

你可能感兴趣的:(pring类型转换早期使用的PropertyEditor详细介绍)