前言
本篇介绍了Spring中类型转换的相关核心源码,需要有一定的Spring框架使用基础
1 spring中对类型转换的管理方式
在看源码前,我们先介绍spring对转换器的设计、以及核心类的简单介绍与继承关系.
spring中的类型转换体系主要分为两块:核心操作类和服务管理类,当进行类型转换时候,由服务管理类获取合适的操作类进行转换操作.
下面分别看下两部分的类图和简要说明.
- 核心操作接口,是由四个独立的接口构成,适用于不同场景。Spring中默认提供了许多对应的实现
Convert<S, T>:接口实现了从原类型S转换为目标类型T的功能, S —> T,即1:1。 例如我们可以实现String到Integer的数据类型转换
ConverterFactory<S, R>:接口完成了从 S —> R 以及 S —> R的子类 的转换功能,即为1:N。例如我们需要实现一种功能,将String转换为Integer、Float、Double等等数字类型,如果用Convert接口实现的话,相似的逻辑我们需要重复实现多次。但如果使用工厂模式,只需要将Integer、Float、Double统一抽象成一个父类Number,作为泛型中的R,即可轻松完成此工作
GenericConverter:该接口实现了多对多的类型转换操作,通过该接口,可以添加多个类型转换对,同时实现诸如将String->List、String->Map等操作。下面会通过例子来详细跟踪源码进行说明该操作。
ConditionalConverter:上面三个类型转换器似乎已经完全满足我们的需求,不过spring还提供了一个扩展的条件转换器,可以根据原类型和目标类型、通过实现自定义的匹配逻辑,来判断转换器是否可以完成转换操作。如果熟悉@Conditional注解或者Condition接口的话,那对这个转换器应该很好理解。
- 核心管理服务的接口
ConversionService:通过实现该接口的四个方法,就可以判断是否能够进行类型转换、以及实现类型转换等操作
ConverterRegistry:该接口提供了一组添加、删除转换器的定义操作
GenericConversionService:核心实现类,下面会详细说明。它实现了上面两个接口定义的规范
DefaultConversionService:继承自GenericConversionService,主要是在其构造函数中,注册了一系列spring提供的类型转换器。一般我们使用这个类即可
2 从一个简单的例子来分析核心类GenericConverter的源码
2.1 示例程序:
自定义一个类型转换器,实现GenericConverter
接口,完成String->List
和String->Map
两种场景的数据类型转换。
例如 转 List:"12_34" -> [12, 34];
或者转 Map:"12_34" -> {"a":"12", "b":"34"}
由于实现比较简单,这里不作过多说明。
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 实现自定义转换器:String->List和String->Map
*/
public class MyArrayToArrayConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> convertiblePairs = Collections.newSetFromMap(new ConcurrentHashMap<>());
ConvertiblePair stringToArray = new ConvertiblePair(String.class, List.class);
ConvertiblePair stringToMap = new ConvertiblePair(String.class, Map.class);
convertiblePairs.add(stringToArray);
convertiblePairs.add(stringToMap);
return convertiblePairs;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
String[] strs = source.toString().split("_");
if (targetType.getType() == List.class) {
return Arrays.asList(strs);
}
if (targetType.getType() == Map.class) {
Map<String, String> map = new HashMap<>();
map.put("a", strs[0]);
map.put("b", strs[1]);
return map;
}
return null;
}
}
下面是测试类,这里直接手动创建和添加,并没有通过spring去进行管理。使用spring托管的方式,在文末做简单说明即可,源码实现上其实都是一样的。
Test.java:
public static void way1() throws NoSuchFieldException {
// 实例化一个默认的类型转换服务类, 并在其构造函数中添加一系列的spring自定义好了的类型转换器
DefaultConversionService defaultConversionService = new DefaultConversionService();
// 再将我们自定义的类型转换器:MyArrayToArrayConverter 添加到转换器集合中
defaultConversionService.addConverter(new MyArrayToArrayConverter());
Person person = new Person();
person.setName("12_34");
List convert = (List) defaultConversionService.convert(person.getName(),
new TypeDescriptor(Person.class.getDeclaredField("name")),
new TypeDescriptor(ResolvableType.forRawClass(List.class),null,null));
System.out.println(convert.get(1));
}
上面的例子,包含了四个主要步骤:
1 创建默认的管理服务
2 将我们自定义的类型转换实现类,添加到创建好的默认管理服务中
3 创建我们的测试用例数据
4 指定原类型和目标类型,进行类型转换操作
2.2 下面我们来分析源码
2.2.1 DefaultConversionService
我们集合步骤1的源码: DefaultConversionService defaultConversionService = new DefaultConversionService();
来分析DefaultConversionService
这个类
1、创建默认管理服务,进入DefaultConversionService,继承关系见上面第二张图
package org.springframework.core.convert.support;
public class DefaultConversionService extends GenericConversionService {
public DefaultConversionService() {
// 在默认的类型转换服务类初始化的时候, 添加默认的的几个类型转化器
addDefaultConverters(this);
}
/**
* 添加一系列的类型转化器
*/
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
// ...................
}
public static void addCollectionConverters(ConverterRegistry converterRegistry) {
ConversionService conversionService = (ConversionService) converterRegistry;
converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
}
private static void addScalarConverters(ConverterRegistry converterRegistry) {
// 通过converterRegistry,完成多个转换器的注册
}
}
总结:该类实现上比较简单,主要完成了一件事,即在初始化该类的时候,在构造函数中,调用addDefaultConverters()方法,通过converterRegistry注册了一系列的由Spring默认实现好了的转换器。(顾名思义:DefaultConversionService即提供好了一组实现好的转换器给框架使用者使用)
2.2.2 GenericConversionService
步骤2的源码: defaultConversionService.addConverter(new MyArrayToArrayConverter())
addConverter
的方法的实现其实是在DefaultConversionService
的父类GenericConversionService
中,用来实现转换器的注册功能。在这个步骤会进入我们自定义的转换器中重写的代码,获取我们的转换器对集合。我们先详细分析GenericConversionService
这个类,然后再来分析测试用例的源码。
GenericConversionService
类的主要属性的定义和内部类的定义如下。
package org.springframework.core.convert.support;
public class GenericConversionService implements ConfigurableConversionService {
// 不需要进行类型转换, 通过下面的源码分析可以得出:当原类型是目标类型的子类时, 会使用该通用的转换器
private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
// 当通过缓存找不到对应的类型转换器, 且原类型也不是目标类型的子类时, 返回NO_MATCH, 表示没找到匹配的转换器
private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");
// 需要通过这个converters将自定义的转换器完成注册、移除、查找等功能
private final Converters converters = new Converters();
// 我这里理解为"一级缓存", 在进行转换时, 会优先从该缓存获取对应的转换器, 找不到才会到内部类Converts的缓存中查找.
// 在该类的内部类Converters中也有一个缓存:Map<ConvertiblePair, ConvertersForPair> converters
private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);
@Override
public void addConverter(GenericConverter converter) {
// 将converter添加到转换器大集合中去
// 注意:这里的converters类型是一个内部类:GenericConversionService.Converters, 这里的add()方法还需要跟进去分析
this.converters.add(converter);
invalidateCache();
}
// 该类下面会详细分析
private static class Converters {
//...
}
// 将Converter适配成GenericConverter, 方便GenericConversionService的统一处理
private final class ConverterAdapter implements ConditionalGenericConverter {
//...
}
// 将ConverterFactory适配成GenericConverter, 也是为了方便GenericConversionService的统一处理
private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
//...
}
// Spring中转换器的存储使用了Map结构, ConverterCacheKey作为key, 该类包装了原类型和目标类型
private static final class ConverterCacheKey implements Comparable<ConverterCacheKey> {
//...
}
// Spring中转换器的存储使用了Map结构, ConvertersForPair作为Value, 该类中有一个链表结构,该链表实际存储了原类型到目标类型的转换器(有多个获取的时候也只是取得其中一个,所以暂时没明白为啥用个链表?)
private static class ConvertersForPair {
private final LinkedList<GenericConverter> converters = new LinkedList<>();
//...
}
}
GenericConversionService
中定义了两个非常重要的属性:converterCache和converters
Map<ConverterCacheKey, GenericConverter> converterCache:
缓存定义好了的转换器,它的key和value分别是GenericConversionService中定义的两个私有内部类。
要注意缓存的时机:converterCache并不是在添加转换器的时候缓存的,而是在进行类型转换时,获取到对应的转换器时候,再存储到该map中的。
Converters converters:
在添加转换器的时候,会将该转换器添加到与原类型和目标类型对应的ConvertersForPair的链表中缓存起来。
GenericConversionService
中还定义了五个内部类,我们先看后四个:
ConverterAdapter和ConverterFactoryAdapter:这两个适配器类,都实现了ConditionalGenericConverter接口,继承如下所示。
通过这两个适配器,可以将基于Convert和ConvertFactory的转换器,统一适配成GenericConverter类型,进行统一的添加、转换处理等逻辑
还有两个:
ConverterCacheKey和ConvertersForPair:在GenericConversionService中有一个用来存储转换器的Map缓存,key和value分别是用这两个类来表示。
通过上面的源码和下面的继承图可知,GenericConversionService
类实现了ConversionService
和ConverterRegistry
这两个接口,因而GenericConversionService
这个类同时具备了类型转换、类型转换器的增删操作等功能。集成关系如图(方便查看,所以这里再展示一次)。
这两个接口的定义代码分别如下:
ConverterRegistry.java
:
package org.springframework.core.convert.converter;
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);
void removeConvertible(Class<?> sourceType, Class<?> targetType);
}
ConversionService.java
:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
<T> T convert(@Nullable Object source, Class<T> targetType);
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}
所以看完GenericConversionService的两个父接口,基本上就能知道该类的实现了。
2.2.3 Converts
下面我们再来分析Converts这个内部类:
Converts.java
:
private static class Converters {
private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
// 存储类型转换器的地方, 这里我理解为"二级缓存", GenericConversionService中的converterCache我当作"一级缓存"
// 它的key是ConvertiblePair, 就是源类型和目标类型的包装
// 它的value是ConvertersForPair, 在ConvertersForPair类里面, 定义了一个链表类型的集合, 用来存储定义好的转换器convert实现类
private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);
// 添加定义的转换器
public void add(GenericConverter converter) {
// 获取所有自定义数据类型转换的类型配对, 即Set集合
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
if (convertibleTypes == null) {
this.globalConverters.add(converter);
}
else {
// 遍历所有的类型转换对, 或者说遍历Set集合
for (ConvertiblePair convertiblePair : convertibleTypes) {
// 从Converters.converters属性所表示的缓存中, 获取到与转换类型对符合的ConvertersForPair
// 该属性的定义为:Map<ConvertiblePair, ConvertersForPair> converters
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
// 添加到链表类型的集合的头部中去
// 具体实现代码就一行为:this.converters.addFirst(converter);
// 这里的converters是ConvertersForPair类的属性, 具体的定义为:LinkedList<GenericConverter> converters;
convertersForPair.add(converter);
}
}
}
private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
// 从"二级缓存"获取与Key(原类型和目标类型)匹配的Value值:即ConvertersForPair, 若找不到, 则新建一个ConvertersForPair放入map, 并返回这个新建的实例
return this.converters.computeIfAbsent(convertiblePair, k -> new ConvertersForPair());
}
public void remove(Class<?> sourceType, Class<?> targetType) {
this.converters.remove(new ConvertiblePair(sourceType, targetType));
}
/**
* 根据原类型和目标类型e查找符合条件的转换器
*/
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Search the full type hierarchy
List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
for (Class<?> sourceCandidate : sourceCandidates) {
for (Class<?> targetCandidate : targetCandidates) {
// 封装成key
ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
if (converter != null) {
return converter;
}
}
}
return null;
}
@Nullable
private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
TypeDescriptor targetType, ConvertiblePair convertiblePair) {
// 1. 先尝试从converters中获取合适的转换器;
// 2. 若找不到匹配的, 再从全局globalConverters查找
// 3. 若还找不到, 则返回null
// 先从Converters的属性converters这个缓存中获取类型对应的转换器
ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
if (convertersForPair != null) {
// 这里的getConverter方法的实现又是在内部类ConvertersForPair中实现的,
// 其实比较简单, 遍历里面的链表, 找到符合转换条件的或者符合Condition添加的转换器就返回出来
GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
if (converter != null) {
return converter;
}
}
// 再从globalConverters中尝试查找
for (GenericConverter globalConverter : this.globalConverters) {
if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
return globalConverter;
}
}
return null;
}
}
该类中包含了两个属性、以及add和find两类方法
Set<GenericConverter> globalConverters:当自定义转换器的getConvertibleTypes()中返回的Set集合为空时候, 会将自定义的转换器实例添加到该集合中; 同时globalConverters也是用来搜索条件转换器的集合, 当普通转换器搜索不到时, 会尝试从该集合查找
Map<ConvertiblePair, ConvertersForPair> converters:存储自定义的转换器对。该对象, 我把它当作"二级缓存"来理解
这里我们结合测试程序的步骤2来看add方法:
该方法会添加该注册器到二级缓存中,在执行该方法时候,会执行到自定义的GenericConverter的实现方法getConvertibleTypes(),得到转化器中的集合,再通过遍历该集合,将转换器添加到对应的ConvertersForPair的链表中。
我们再结合步骤4的convert()来分析这里的find方法:
该方法则是查找匹配的转换器。在开始进行类型转换的时候,通过GenericConversionService的getConverter(..)调用进来。该方法会分别查找一、二级缓存,若查找不到匹配的,还会创建默认的转换器。
我们用一张图来理解转换器的存储结构和流程, 从这张图我们可以看到,在Spring中使用了两层Map来存储转换器,我这里称为一、二级缓存。当我们添加转换器的时候,其实是添加到了二级缓存中。那么一级缓存是什么时候使用的呢?其实在上面有简单提过,在我们执行类型转换的时候会去获取相应的转换器,去干活,这个时候,会先从一级缓存中获取,获取不到时,再会从二级缓存中获取,并再存储到一级缓存中去,以方便下次使用。
注册转换器的流程,也就是我们测试用例里的步骤2和最后一步:
2.2.4 基于spring方式的注入
文末,简单带过基于spring托管方式的定义和使用,这里以xml的方式,将自定义的转换器,注册到ConversionServiceFactoryBean的converters这个Set属性中去,其内部仍然是委托GenericConversionService实例进行操作的。
具体用法可以参考spring官网文档
Test.java:
public static void way2() throws NoSuchFieldException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:convert.xml");
ConversionService conversionService = context.getBean("conversionService", ConversionService.class);
Person person = new Person();
person.setName("12_34");
// 从缓存中获取转换器, 并通过获取到的转换器, 将原类型转换为目标类型
List convertedListValue = (List) conversionService.convert(person.getName(),
new TypeDescriptor(Person.class.getDeclaredField("name")),
new TypeDescriptor(ResolvableType.forRawClass(List.class), null, null));
System.out.println(convertedListValue.get(1));
}
convert.xml:
<xml>
<beans ...>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.mystrace.convert.genericConverter.MyArrayToArrayConverter"></bean>
</set>
</property>
</bean>
</beans>
3 总结
Spring中提供了四个接口,来分别实现1—>1、1—>N、M—>N的转换场景。同时又提供了转换器的管理服务接口、以及默认的实现,通过这些管理接口,便可以方便的完成类型转换。由于类型接口,在这里无法抽象成一个统一的父类接口,不过,Spring通过适配器模式,进行了统一的抽象,这点设计,我觉得很有参考意义。
4 写在最后
源码阅读是一个持续的过程,需要有一定的基础,且通过文字方式来解读源码,对编写者和读者都会有很大的挑战,但博主依然会保持持续的更新,按照Spring中的各个扩展点,结合用法来对源码进行说明。由于本人水平限制,难免有对源码中理解不周的地方,欢迎喜欢探索技术的伙伴们提出来,一起进步成长!