6. 抹平差异,统一类型转换服务ConversionService(下)

简介: 6. 抹平差异,统一类型转换服务ConversionService(下)

3、转换功能(ConversionService)


上半部分介绍完GenericConversionService对转换器管理部分的实现(对ConverterRegistry接口的实现),接下来就看看它是如何实现转换功能的(对ConversionService接口的实现)。


判断

@Override
public boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType) {
  return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null), TypeDescriptor.valueOf(targetType));
}
@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
  if (sourceType == null) {
    return true;
  }
  // 查找/匹配对应的转换器
  GenericConverter converter = getConverter(sourceType, targetType);
  return (converter != null);
}


能否执行转换判断的唯一标准:能否匹配到可用于转换的转换器。而这个查找匹配逻辑,稍稍抬头往上就能看到。


转换

@Override
@SuppressWarnings("unchecked")
@Nullable
public <T> T convert(@Nullable Object source, Class<T> targetType) {
  return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}
@Override
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
  if (sourceType == null) {
    return handleResult(null, targetType, convertNullSource(null, targetType));
  }
  // 校验:source必须是sourceType的实例
  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() + "]");
  }
  // ============拿到转换器,执行转换============
  GenericConverter converter = getConverter(sourceType, targetType);
  if (converter != null) {
    Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
    return handleResult(sourceType, targetType, result);
  }
  // 若没进行canConvert的判断直接调动,可能出现此种状况:一般抛出ConverterNotFoundException异常
  return handleConverterNotFound(source, sourceType, targetType);
}



同样的,执行转换的逻辑很简单,非常好理解的两个步骤:


  1. 查找匹配到一个合适的转换器(查找匹配的逻辑同上)
  2. 拿到此转换器执行转换converter.convert(...)


说明:其余代码均为一些判断、校验、容错,并非核心,本文给与适当忽略。


GenericConversionService实现了转换器管理、转换服务的所有功能,是可以直接面向开发者使用的。但是开发者使用时可能并不知道需要注册哪些转换器来保证程序正常运转,Spring并不能要求开发者知晓其内建实现。基于此,Spring在3.1又提供了一个默认实现DefaultConversionService,它对使用者更友好。


DefaultConversionService


Spirng容器默认使用的转换服务实现,继承自GenericConversionService,在其基础行只做了一件事:构造时添加内建的默认转换器们。从而天然具备有了基本的类型转换能力,适用于不同的环境。如:xml解析、@Value解析、http协议参数自动转换等等。


小细节:它并非Spring 3.0就有,而是Spring 3.1新推出的API


// @since 3.1
public class DefaultConversionService extends GenericConversionService {
  // 唯一构造器
  public DefaultConversionService() {
    addDefaultConverters(this);
  }
}


本类核心代码就这一个构造器,构造器内就这一句代码:addDefaultConverters(this)。接下来需要关注Spring默认情况下给我们“安装”了哪些转换器呢?也就是了解下addDefaultConverters(this)这个静态方法


默认注册的转换器们

// public的静态方法,注意是public的访问权限
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
  addScalarConverters(converterRegistry);
  addCollectionConverters(converterRegistry);
  converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
  converterRegistry.addConverter(new StringToTimeZoneConverter());
  converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
  converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
  converterRegistry.addConverter(new ObjectToObjectConverter());
  converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
  converterRegistry.addConverter(new FallbackObjectToStringConverter());
  converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}


该静态方法用于注册全局的、默认的转换器们,从而让Spring有了基础的转换能力,进而完成绝大部分转换工作。为了方便记忆这个注册流程,我把它绘制成图供以你保存:



image.png


特别强调:转换器的注册顺序非常重要,这决定了通用转换器的匹配结果(谁在前,优先匹配谁,first win)。


针对这幅图,你可能还会有如下疑问:


JSR310转换器只看到TimeZone、ZoneId等转换,怎么没看见更为常用的LocalDate、LocalDateTime等这些类型转换呢?难道Spring默认是不支持的?

答:当然不是。 这么常见的场景Spring怎能会不支持呢?不过与其说这是类型转换,倒不如说是格式化更合适。所以放在该系列后几篇关于格式化章节中再做讲述

一般的Converter都见名之意,但StreamConverter有何作用呢?什么场景下会生效

答:上文已讲述

对于兜底的转换器,有何含义?这种极具通用性的转换器作用为何

答:上文已讲述

最后,需要特别强调的是:它是一个静态方法,并且还是public的访问权限,且不仅仅只有本类调用。实际上,DefaultConversionService仅仅只做了这一件事,所以任何地方只要调用了该静态方法都能达到前者相同的效果,使用上可谓给与了较大的灵活性。比如Spring Boot环境下不是使用DefaultConversionService而是ApplicationConversionService,后者是对FormattingConversionService扩展,这个话题放在后面详解。


Spring Boot在web环境默认向容易注册了一个WebConversionService,因此你有需要可直接@Autowired使用


ConversionServiceFactoryBean


顾名思义,它是用于产生ConversionService类型转换服务的工厂Bean,为了方便和Spring容器整合而使用。


public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
  @Nullable
  private Set<?> converters;
  @Nullable
  private GenericConversionService conversionService;
  public void setConverters(Set<?> converters) {
    this.converters = converters;
  }
  @Override
  public void afterPropertiesSet() {
    // 使用的是默认实现哦
    this.conversionService = new DefaultConversionService();
    ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
  }
  @Override
  @Nullable
  public ConversionService getObject() {
    return this.conversionService;
  }
  ...
}



这里只有两个信息量需要关注:


使用的是DefaultConversionService,因此那一大串的内建转换器们都会被添加进来的

自定义转换器可以通过setConverters()方法添加进来

值得注意的是方法入参是Set<?>并没有明确泛型类型,因此那三种转换器(1:1/1:N/N:N)你是都可以添加.、


✍总结


通读本文过后,相信能够给与你这个感觉:曾经望而却步的Spring类型转换服务ConversionService,其实也不过如此嘛。通篇我用了多个简单字眼来说明,因为拆开之后,无一高复杂度知识点。


迎难而上是积攒涨薪底气和勇气的途径,况且某些知识点其实并不难,所以我觉得从性价比角度来看这类内容是非常划算的,你pick到了麽?


正所谓类型转换和格式化属于两组近义词,在Spring体系中也经常交织在一起使用,有种傻傻分不清楚之感。从下篇文章起进入到本系列关于Formatter格式化器知识的梳理,什么日期格式化、@DateTimeFormat、@NumberFormat都将帮你捋清楚喽,有兴趣者可保持持续关注。

相关文章
|
3月前
|
人工智能 供应链 安全
AI时代下,2025年中国低代码市场发展如何了?
技术民主化正重塑企业数字化边界。低代码与AI融合,让业务人员也能快速构建系统,开发效率倍增、成本大降。从制造到金融,平台已承担核心业务,推动IT与业务协同创新,释放全员创造力。
|
6月前
|
Web App开发 存储 前端开发
Python+Selenium自动化爬取携程动态加载游记
Python+Selenium自动化爬取携程动态加载游记
|
3月前
|
Web App开发 资源调度 算法
Fresnel变换的详解
菲涅耳变换是描述光波近场衍射的核心工具,由法国物理学家菲涅耳提出,用于精确刻画光通过孔径后的传播行为。它在傍轴近似下将衍射积分转化为含二次相位因子的傅里叶形式,广泛应用于激光传输、全息成像与光学系统设计。该变换介于精确的瑞利-索末菲积分与远场的夫琅禾费衍射之间,体现了波动光学的基本特征。
698 5
|
5月前
|
C++
什么是单项式
单项式是代数式中的一种
|
9月前
|
程序员 测试技术 开发工具
怎么开发Python第三方库?手把手教你参与开源项目!
大家好,我是程序员晚枫。本文将分享如何开发Python第三方库,并以我维护的开源项目 **popdf** 为例,指导参与开源贡献。Popdf是一个PDF操作库,支持PDF转Word、转图片、合并与加密等功能。文章涵盖从fork项目、本地开发、单元测试到提交PR的全流程,适合想了解开源贡献的开发者。欢迎访问[popdf](https://gitcode.com/python4office/popdf),一起交流学习!
322 21
怎么开发Python第三方库?手把手教你参与开源项目!
|
9月前
|
存储 弹性计算 Linux
阿里云服务器购买流程参考:快速、自定义、活动三种购买方式详解与流程指南
对于初次接触阿里云服务器的用户来说,选择合适的购买方式并了解详细的购买流程至关重要,阿里云提供了快速购买、自定义购买和活动购买等多种购买方式,以满足不同用户的需求。本文将为大家展示阿里云服务器的三种主要购买方式:快速购买、自定义购买以及通过活动购买,以供大家了解具体的流程,帮助用户轻松上手,快速搭建高效、稳定的云端环境。
407 10
|
JavaScript
基于Vue2.X对WangEditor 5富文本编辑器进行封装与使用,支持单个或多个图片点击、粘贴、拖拽上传,Vue3.X项目也可直接使用
这篇文章介绍了如何在Vue 2.X项目中封装和使用WangEditor 5富文本编辑器,支持图片的点击、粘贴和拖拽上传,同时提到封装的组件也适用于Vue 3.X项目,并提供了详细的使用示例和后端配置。
1965 1
基于Vue2.X对WangEditor 5富文本编辑器进行封装与使用,支持单个或多个图片点击、粘贴、拖拽上传,Vue3.X项目也可直接使用
|
负载均衡 监控 算法
HAproxy
HAProxy 是一个高性能的负载均衡软件,可以将客户端的请求均衡地分发给多个后端服务器。HAProxy支持多种负载均衡算法,并提供灵活的配置选项。与LVS相比,HAProxy更加灵活和高级,可以进行更复杂的负载均衡策略和应用层的请求转发。通常,HAProxy可以与Keepalived结合使用,以提供高可用性和负载均衡的解决方案。
359 5
|
XML 开发框架 监控
面试题:springboot比spring有哪些优点?
面试题:springboot比spring有哪些优点?
564 0