4. 上新了Spring,全新一代类型转换机制(下)

简介: 4. 上新了Spring,全新一代类型转换机制(下)

下面以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.若源集合为空,或者目标集合没指定泛型,也不需要做转换动作

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


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

  1. 若新集合(目标集合)没有指定泛型类型(那就是Object),就直接putAll即可,并不需要做类型转换
  2. 若新集合(目标集合指定了泛型类型),就遍历源集合委托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);
}


该接口的实现,截图如下:image.png


可以看到,只有通用转换器GenericConverter和它进行了合体。这也很容易理解,作为通用的转换器,加个前置判断将更加严谨和更安全。对于专用的转换器如Converter,它已明确规定了转换的类型,自然就不需要做前置判断喽。


✍总结


本文详细介绍了Spring新一代的类型转换接口,类型转换作为Spring的基石,其重要性可见一斑。


PropertyEditor作为Spring早期使用“转换器”,因存在众多设计缺陷自Spring 3.0起被新一代转换接口所取代,主要有:


  1. Converter<S, T>:Source -> Target类型转换接口,适用于1:1转换
  2. ConverterFactory<S, R>:Source -> R类型转换接口,适用于1:N转换
  3. GenericConverter:更为通用的类型转换接口,适用于N:N转换

下篇文章将针对于GenericConverter的几个特殊实现撰专文为你讲解,你也知道做难事必有所得,做难事才有可能破局、破圈,欢迎保持关注。

相关文章
|
2月前
|
存储 安全 Java
事件的力量:探索Spring框架中的事件处理机制
事件的力量:探索Spring框架中的事件处理机制
39 0
|
2月前
|
设计模式 前端开发 Java
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
50 0
|
2月前
|
Java 开发者 UED
Spring Boot的全局异常处理机制
【2月更文挑战第13天】
137 0
|
2月前
|
人工智能 JSON 前端开发
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
【Spring boot实战】Springboot+对话ai模型整体框架+高并发线程机制处理优化+提示词工程效果展示(按照框架自己修改可对接市面上百分之99的模型)
|
2月前
|
XML Java 数据格式
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
63 0
|
2月前
|
Java 数据库 Spring
Spring 声明式事务机制
Spring 声明式事务机制
35 0
|
2月前
|
Java 测试技术 开发者
Spring IoC容器通过依赖注入机制实现控制反转
【4月更文挑战第30天】Spring IoC容器通过依赖注入机制实现控制反转
35 0
|
9天前
|
设计模式 Java 数据库
Spring Boot中的事件通知机制
Spring Boot中的事件通知机制
|
18天前
|
Java 应用服务中间件 Spring
解析Spring Boot自动装配的原理与机制
解析Spring Boot自动装配的原理与机制
28 4
|
4天前
|
缓存 安全 Java
Spring Boot中的自动配置机制详解
Spring Boot中的自动配置机制详解