Spring转换器

756 阅读9分钟

前言

本篇介绍了Spring中类型转换的相关核心源码,需要有一定的Spring框架使用基础

1 spring中对类型转换的管理方式

在看源码前,我们先介绍spring对转换器的设计、以及核心类的简单介绍与继承关系.
spring中的类型转换体系主要分为两块:核心操作类和服务管理类,当进行类型转换时候,由服务管理类获取合适的操作类进行转换操作.

下面分别看下两部分的类图和简要说明.

  • 核心操作接口,是由四个独立的接口构成,适用于不同场景。Spring中默认提供了许多对应的实现
Convert<S, T>:接口实现了从原类型S转换为目标类型T的功能, S —> T,即11。 例如我们可以实现String到Integer的数据类型转换

ConverterFactory<S, R>:接口完成了从 S —> R 以及 S —> R的子类 的转换功能,即为1:N。例如我们需要实现一种功能,将String转换为IntegerFloatDouble等等数字类型,如果用Convert接口实现的话,相似的逻辑我们需要重复实现多次。但如果使用工厂模式,只需要将IntegerFloatDouble统一抽象成一个父类Number,作为泛型中的R,即可轻松完成此工作

GenericConverter:该接口实现了多对多的类型转换操作,通过该接口,可以添加多个类型转换对,同时实现诸如将String->List、String->Map等操作。下面会通过例子来详细跟踪源码进行说明该操作。

ConditionalConverter:上面三个类型转换器似乎已经完全满足我们的需求,不过spring还提供了一个扩展的条件转换器,可以根据原类型和目标类型、通过实现自定义的匹配逻辑,来判断转换器是否可以完成转换操作。如果熟悉@Conditional注解或者Condition接口的话,那对这个转换器应该很好理解。

  • 核心管理服务的接口
ConversionService:通过实现该接口的四个方法,就可以判断是否能够进行类型转换、以及实现类型转换等操作

ConverterRegistry:该接口提供了一组添加、删除转换器的定义操作

GenericConversionService:核心实现类,下面会详细说明。它实现了上面两个接口定义的规范

DefaultConversionService:继承自GenericConversionService,主要是在其构造函数中,注册了一系列spring提供的类型转换器。一般我们使用这个类即可

2 从一个简单的例子来分析核心类GenericConverter的源码

2.1 示例程序:

自定义一个类型转换器,实现GenericConverter接口,完成String->ListString->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类实现了ConversionServiceConverterRegistry这两个接口,因而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中的各个扩展点,结合用法来对源码进行说明。由于本人水平限制,难免有对源码中理解不周的地方,欢迎喜欢探索技术的伙伴们提出来,一起进步成长!