分享、成长,拒绝浅藏辄止。关注公众号【BAT的乌托邦】,回复关键字
专栏
有Spring技术栈、中间件等小而美的原创专栏供以避免费学习。本文已被 https://www.yourbatman.cn 收录。java
你好,我是YourBatman。程序员
经过前两篇文章的介绍已经很是熟悉Spirng 3.0全新一代的类型转换机制了,它提供的三种类型转换器(Converter、ConverterFactory、GenericConverter),分别可处理1:一、1:N、N:N的类型转换。按照Spring的设计习惯,必有一个注册中心来统一管理,负责它们的注册、删除等,它就是ConverterRegistry
。web
对于
ConverterRegistry
在文首多说一句:我翻阅了不少博客文章介绍它时几乎无一例外的提到有查找的功能,但其实是没有的。Spring设计此API接口并无暴露其查找功能,选择把最为复杂的查找匹配逻辑私有化,目的是让开发者使可无需关心,细节之处充分体现了Spring团队API设计的卓越能力。缓存
另外,内建的绝大多数转换器访问权限都是default/private,那么如何使用它们,以及屏蔽各类转换器的差别化呢?为此,Spring提供了一个统一类型转换服务,它就是ConversionService
。app
ConverterRegistry和ConversionService的关系密不可分,前者为后者提供转换器管理支撑,后者面向使用者提供服务。本文涉及到的接口/类有:框架
ConverterRegistry
:转换器注册中心。负责转换器的注册、删除ConversionService
:统一的类型转换服务。属于面向开发者使用的门面接口ConfigurableConversionService
:上两个接口的组合接口GenericConversionService
:上个接口的实现,实现了注册管理、转换服务的几乎全部功能,是个实现类而非抽象类DefaultConversionService
:继承自GenericConversionService
,在其基础上注册了一批默认转换器(Spring内建),从而具有基础转换能力,能解决平常绝大部分场景Spring 3.0引入的转换器注册中心,用于管理新一套的转换器们。dom
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); // 惟一移除方法:按照转换pair对来移除 void removeConvertible(Class<?> sourceType, Class<?> targetType); }
它的继承树以下:ide
ConverterRegistry有子接口FormatterRegistry,它属于格式化器的范畴,故不放在本文讨论。但仍旧属于本系列专题内容,会在接下来的几篇内容里介入,敬请关注。性能
面向使用者的统一类型转换服务。换句话说:站在使用层面,你只须要知道ConversionService
接口API的使用方式便可,并不须要关心其内部实现机制,可谓对使用者很是友好。学习
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); }
它的继承树以下:
能够看到ConversionService和ConverterRegistry的继承树异曲同工,都直接指向了ConfigurableConversionService
这个分支,下面就对它进行介绍。
ConversionService
和ConverterRegistry
的组合接口,本身并未新增任何接口方法。
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry { }
它的继承树可参考上图。接下来就来到此接口的直接实现类GenericConversionService。
对ConfigurableConversionService
接口提供了完整实现的实现类。换句话说:ConversionService和ConverterRegistry接口的功能均经过此类获得了实现,因此它是本文重点。
该类颇有些值得学习的地方,能够细品,在咱们本身设计程序时加以借鉴。
public class GenericConversionService implements ConfigurableConversionService { private final Converters converters = new Converters(); private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<ConverterCacheKey, GenericConverter>(64); }
它用两个成员变量来管理转换器们,其中converterCache是缓存用于加速查找,所以更为重要的即是Converters喽。
Converters是GenericConversionService
的内部类,用于管理(添加、删除、查找)转换器们。也就说对ConverterRegistry
接口的实现最终是委托给它去完成的,它是整个转换服务正常work的内核,下面咱们对它展开详细叙述。
它管理全部转换器,包括添加、删除、查找。
GenericConversionService: // 内部类 private static class Converters { private final Set<GenericConverter> globalConverters = new LinkedHashSet<GenericConverter>(); private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36); }
说明:这里使用的集合/Map均为
LinkedHashXXX
,都是有序的(存入顺序和遍历取出顺序保持一致)
用这两个集合/Map存储着注册进来的转换器们,他们的做用分别是:
globalConverters
:存取通用的转换器,并不限定转换类型,通常用于兜底converters
:指定了类型对,对应的转换器们的映射关系。
final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; } private static class ConvertersForPair { private final Deque<GenericConverter> converters = new ArrayDeque<>(1); }
public void add(GenericConverter converter) { Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes(); if (convertibleTypes == null) { ... // 放进globalConverters里 } else { ... // 放进converters里(若支持多组pair就放多个key) } }
在此以前须要了解个前提:对于三种转换器Converter、ConverterFactory、GenericConverter
在添加到Converters以前都统一被适配为了GenericConverter
,这样作的目的是方便统一管理。对应的两个适配器是ConverterAdapter和ConverterFactoryAdapter,它俩都是ConditionalGenericConverter的内部类。
添加的逻辑被我用伪代码简化后其实很是简单,无非就是一个非此即彼的关系而已:
public void remove(Class<?> sourceType, Class<?> targetType) { this.converters.remove(new ConvertiblePair(sourceType, targetType)); }
移除逻辑很是很是的简单,这得益于添加时候作了统一适配的抽象。
@Nullable public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { // 找到该类型的类层次接口(父类 + 接口),注意:结果是有序列表 List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType()); List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType()); // 双重遍历 for (Class<?> sourceCandidate : sourceCandidates) { for (Class<?> targetCandidate : targetCandidates) { ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate); ... // 从converters、globalConverters里匹配到一个合适转换器后立马返回 } } return null; }
查找逻辑也并不复杂,有两个关键点须要关注:
getClassHierarchy(class)
:获取该类型的类层次(父类 + 接口),注意:结果List是有序的List
了解了Converters
以后再来看GenericConversionService
是如何管理转换器,就如鱼得水,一目了然了。
为了方便使用者调用,ConverterRegistry接口提供了三个添加方法,这里一一给与实现。
说明:暴露给调用者使用的API接口使用起来应尽可能的方便,重载多个是个有效途径。内部作适配、归口便可,用户至上
@Override public void addConverter(Converter<?, ?> converter) { // 获取泛型类型 -> 转为ConvertiblePair ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class); ... // converter适配为GenericConverter添加 addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1])); } @Override public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) { addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType))); } @Override public void addConverter(GenericConverter converter) { this.converters.add(converter); invalidateCache(); }
前两个方法都会调用到第三个方法上,每调用一次addConverter()
方法都会清空缓存,也就是converterCache.clear()
。因此动态添加转换器对性能是有损的,所以使用时候需稍加注意一些。
ConverterRegistry接口并未直接提供查找方法,而只是在实现类内部作了实现。提供一个钩子方法用于查找给定sourceType/targetType对的转换器。
@Nullable protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType); // 一、查缓存 GenericConverter converter = this.converterCache.get(key); if (converter != null) { ... // 返回结果 } // 二、去converters里查找 converter = this.converters.find(sourceType, targetType); if (converter == null) { // 若尚未匹配的,就返回默认结果 // 默认结果是NoOpConverter -> 什么都不作 converter = getDefaultConverter(sourceType, targetType); } ... // 把结果装进缓存converterCache里 return null; }
有了对Converters查找逻辑的分析,这个步骤就很简单了。绘制成图以下:
上半部分介绍完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); }
一样的,执行转换的逻辑很简单,很是好理解的两个步骤:
converter.convert(...)
说明:其他代码均为一些判断、校验、容错,并不是核心,本文给与适当忽略。
GenericConversionService实现了转换器管理、转换服务的全部功能,是能够直接面向开发者使用的。可是开发者使用时可能并不知道须要注册哪些转换器来保证程序正常运转,Spring并不能要求开发者知晓其内建实现。基于此,Spring在3.1又提供了一个默认实现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有了基础的转换能力,进而完成绝大部分转换工做。为了方便记忆这个注册流程,我把它绘制成图供以你保存:
特别强调:转换器的注册顺序很是重要,这决定了通用转换器的匹配结果(谁在前,优先匹配谁,first win)。
针对这幅图,你可能还会有以下疑问:
最后,须要特别强调的是:它是一个静态方法,而且仍是public的访问权限,且不只仅只有本类调用。实际上,DefaultConversionService
仅仅只作了这一件事,因此任何地方只要调用了该静态方法都能达到前者相同的效果,使用上可谓给与了较大的灵活性。好比Spring Boot环境下不是使用DefaultConversionService
而是ApplicationConversionService
,后者是对FormattingConversionService扩展,这个话题放在后面详解。
Spring Boot在web环境默认向容易注册了一个WebConversionService,所以你有须要可直接@Autowired使用
顾名思义,它是用于产生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; } ... }
这里只有两个信息量须要关注:
setConverters()
方法添加进来
Set<?>
并无明确泛型类型,所以那三种转换器(1:1/1:N/N:N)你是均可以添加.通读本文事后,相信可以给与你这个感受:曾经望而却步的Spring类型转换服务ConversionService
,其实也不过如此嘛。通篇我用了多个简单字眼来讲明,由于拆开以后,无一高复杂度知识点。
迎难而上是积攒涨薪底气和勇气的途径,何况某些知识点其实并不难,因此我以为从性价比角度来看这类内容是很是划算的,你pick到了麽?
正所谓类型转换和格式化属于两组近义词,在Spring体系中也常常交织在一块儿使用,有种傻傻分不清楚之感。从下篇文章起进入到本系列关于Formatter格式化器知识的梳理,什么日期格式化、@DateTimeFormat、@NumberFormat都将帮你捋清楚喽,有兴趣者可保持持续关注。
【Spring类型转换】系列:
【Jackson】系列:
【数据校验Bean Validation】系列:
【新特性】系列:
【程序人生】系列:
还有诸如【Spring配置类】【Spring-static】【Spring数据绑定】【Spring Cloud Netflix】【Feign】【Ribbon】【Hystrix】...更多原创专栏,关注BAT的乌托邦
回复专栏
二字便可所有获取,也可加我fsx1056342982
,交个朋友。
有些已完结,有些连载中。我是A哥(YourBatman),我们下期见