下面以CollectionToCollectionConverter为例分析此转换器的“复杂”之处:
final class CollectionToCollectionConverter implements ConditionalGenericConverter { private final ConversionService conversionService; public CollectionToCollectionConverter(ConversionService conversionService) { this.conversionService = conversionService; } // 集合转集合:如String集合转为Integer集合 @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class)); } }
这是唯一构造器,必须传入ConversionService:元素与元素之间的转换是依赖于conversionService转换服务去完成的,最终完成集合到集合的转换。
CollectionToCollectionConverter: @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService); }
判断能否转换的依据:集合里的元素与元素之间是否能够转换,底层依赖于ConversionService#canConvert()
这个API去完成判断。
接下来再看最复杂的转换方法
CollectionToCollectionConverter: @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } Collection<?> sourceCollection = (Collection<?>) source; // 判断:这些情况下,将不用执行后续转换动作了,直接返回即可 boolean copyRequired = !targetType.getType().isInstance(source); if (!copyRequired && sourceCollection.isEmpty()) { return source; } TypeDescriptor elementDesc = targetType.getElementTypeDescriptor(); if (elementDesc == null && !copyRequired) { return source; } Collection<Object> 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); }
该转换步骤稍微有点复杂,我帮你屡清楚后有这几个关键步骤:
1.快速返回:对于特殊情况,做快速返回处理
1.若目标元素类型是源元素类型的子类型(或相同),就没有转换的必要了(copyRequired = false)
2.若源集合为空,或者目标集合没指定泛型,也不需要做转换动作
- 源集合为空,还转换个啥
- 目标集合没指定泛型,那就是Object,因此可以接纳一切,还转换个啥
2.若没有触发快速返回。给目标创建一个新集合,然后把source的元素一个一个的放进新集合里去,这里又分为两种处理case
- 若新集合(目标集合)没有指定泛型类型(那就是Object),就直接putAll即可,并不需要做类型转换
- 若新集合(目标集合指定了泛型类型),就遍历源集合委托conversionService.convert()对元素一个一个的转
代码示例
以CollectionToCollectionConverter做示范:List<String> -> Set<Integer>
@Test public void test3() { System.out.println("----------------CollectionToCollectionConverter---------------"); ConditionalGenericConverter conditionalGenericConverter = new CollectionToCollectionConverter(new DefaultConversionService()); // 将Collection转为Collection(注意:没有指定泛型类型哦) System.out.println(conditionalGenericConverter.getConvertibleTypes()); List<String> 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); }
运行程序,正常输出:
[java.util.Collection -> java.util.Collection] true class 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转换增加一个前置判断条件。
public interface ConditionalConverter { boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType); }
该接口的实现,截图如下:
可以看到,只有通用转换器GenericConverter和它进行了合体。这也很容易理解,作为通用的转换器,加个前置判断将更加严谨和更安全。对于专用的转换器如Converter,它已明确规定了转换的类型,自然就不需要做前置判断喽。
✍总结
本文详细介绍了Spring新一代的类型转换接口,类型转换作为Spring的基石,其重要性可见一斑。
PropertyEditor作为Spring早期使用“转换器”,因存在众多设计缺陷自Spring 3.0起被新一代转换接口所取代,主要有:
- Converter<S, T>:Source -> Target类型转换接口,适用于1:1转换
- ConverterFactory<S, R>:Source -> R类型转换接口,适用于1:N转换
- GenericConverter:更为通用的类型转换接口,适用于N:N转换
下篇文章将针对于GenericConverter的几个特殊实现撰专文为你讲解,你也知道做难事必有所得,做难事才有可能破局、破圈,欢迎保持关注。