Spring读源码系列番外篇---05----类型转换---中

x33g5p2x  于2022-04-11 转载在 Spring  
字(27.9k)|赞(0)|评价(0)|浏览(347)

系列文章:

Spring读源码系列番外篇—01–PropertyValue相关类

Spring读源码系列番外篇—02—PropertyResolver的结构体系剖析—上

Spring读源码系列番外篇—03—PropertyResolver的结构体系剖析—下

Spring读源码系列番外篇03----类型转换–上

新一代类型转换机制

旧版的PropertyEditor设计缺陷

  • 职责不单一:该接口有非常多的方法,但只用到2个而已
  • 类型不安全:setValue()方法入参是Object,getValue()返回值是Object,依赖于约定好的类型强转,不安全
  • 线程不安全:依赖于setValue()后getValue(),实例是线程不安全的
  • 语义不清晰:从语义上根本不能知道它是用于类型转换的组件
  • 只能用于String类型:它只能进行String <-> 其它类型的转换,而非更灵活的Object <-> Object

新一代类型转换Converter

Spring 3.0版本重新设计了一套类型转换接口,有3个核心接口:

  • Converter<S, T>:Source -> Target类型转换接口,适用于1:1转换
  • ConverterFactory<S, R>:Source -> R类型转换接口,适用于1:N转换
  • GenericConverter:更为通用的类型转换接口,适用于N:N转换,因为没有泛型约束,所以是通用
  • ConditionalConverter:前置条件判断,决定是否进行转换

Converter

将源类型S转换为目标类型T。

@FunctionalInterface
public interface Converter<S, T> {
	@Nullable
	T convert(S source);
}

它是个函数式接口,接口定义非常简单。适合1:1转换场景:可以将任意类型 转换为 任意类型。它的实现类非常多,部分截图如下:

值得注意的是:几乎所有实现类的访问权限都是default/private,只有少数几个是public公开的。

实例
class StringToFileConverter implements Converter<String, File> {
    private static final ResourceLoader resourceLoader = new DefaultResourceLoader((ClassLoader)null);

    StringToFileConverter() {
    }

    public File convert(String source) {
        if (ResourceUtils.isUrl(source)) {
            return this.getFile(resourceLoader.getResource(source));
        } else {
        //先尝试直接从文件系统去定位资源
            File file = new File(source);
            if (file.exists()) {
                return file;
            } else {
            //再尝试从类路径下,或者网络资源定位资源
                Resource resource = resourceLoader.getResource(source);
                return resource.exists() ? this.getFile(resource) : file;
            }
        }
    }

    private File getFile(Resource resource) {
        try {
            return resource.getFile();
        } catch (IOException var3) {
            throw new IllegalStateException("Could not retrieve file for " + resource + ": " + var3.getMessage());
        }
    }
}
缺陷

Converter用于解决1:1的任意类型转换,因此它必然存在一个不足:解决1:N转换问题需要写N遍,造成重复冗余代码。

譬如:输入是字符串,它可以转为任意数字类型,包括byte、short、int、long、double等等,如果用Converter来转换的话每个类型都得写个转换器,想想都麻烦有木有。

Spring早早就考虑到了该场景,提供了相应的接口来处理,它就是ConverterFactory<S, R>。

ConverterFactory

从名称上看它代表一个转换工厂:可以将对象S转换为R的所有子类型,从而形成1:N的关系。

public interface ConverterFactory<S, R> {
	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

它同样也是个函数式接口。该接口的实现类并不多(访问权限全部为default):

实例
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

	@Override
	public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
		return new StringToNumber<>(targetType);
	}

    // 私有内部类:实现Converter接口。用泛型边界约束一类类型
	private static final class StringToNumber<T extends Number> implements Converter<String, T> {

		private final Class<T> targetType;

		public StringToNumber(Class<T> targetType) {
			this.targetType = targetType;
		}

		@Override
		@Nullable
		public T convert(String source) {
			if (source.isEmpty()) {
				return null;
			}
			return NumberUtils.parseNumber(source, this.targetType);
		}
	}

}
优点

ConverterFactory作为Converter的工厂,对Converter进行包装,从而达到屏蔽内部实现的目的,对使用者友好,这不正是工厂模式的优点么,符合xxxFactory的语义。但你需要清除的是,工厂内部实现其实也是通过众多if else之类的去完成的,本质上并无差异。

缺陷

既然有了1:1、1:N,自然就有N:N。比如集合转换、数组转换、Map到Map的转换等等,这些N:N的场景,就需要借助下一个接口GenericConverter来实现。

GenericConverter

它是一个通用的转换接口,用于在两个或多个类型之间进行转换。相较于前两个,这是最灵活的SPI转换器接口,但也是最复杂的。

public interface GenericConverter {
	@Nullable
	Set<ConvertiblePair> getConvertibleTypes();
	
	@Nullable
	Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

	final class ConvertiblePair {

		private final Class<?> sourceType;

		private final Class<?> targetType;

		public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
			Assert.notNull(sourceType, "Source type must not be null");
			Assert.notNull(targetType, "Target type must not be null");
			this.sourceType = sourceType;
			this.targetType = targetType;
		}

		public Class<?> getSourceType() {
			return this.sourceType;
		}

		public Class<?> getTargetType() {
			return this.targetType;
		}
		//省略equals,toString和hashcode方法
        ...
	}
}

该接口并非函数式接口,虽然方法不多但稍显复杂。现对出现的几个类型做简单介绍:

  • ConvertiblePair:维护sourceType和targetType的POJO

  • getConvertibleTypes()方法返回此Pair的Set集合。由此也能看出该转换器是可以支持N:N的(大多数情况下只写一对值而已,也有写多对的)

  • TypeDescriptor:类型描述。该类专用于Spring的类型转换场景,用于描述from or to的类型

  • 比单独的Type类型强大,内部借助了ResolvableType来解决泛型议题

GenericConverter的内置实现也比较多,部分截图如下:

ConditionalGenericConverter是GenericConverter和条件接口ConditionalConverter的组合,作用是在执行GenericConverter转换时增加一个前置条件判断方法。

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

分割线下面的4个转换器比较特殊,字面上不好理解其实际作用,比较“高级”。它们如果能被运用在日常工作中可以事半功弎,

例子
final class CollectionToCollectionConverter implements ConditionalGenericConverter {

	private final ConversionService conversionService;

//这是唯一构造器,必须传入ConversionService:
//元素与元素之间的转换是依赖于conversionService转换服务去完成的,最终完成集合到集合的转换。
	public CollectionToCollectionConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

// 集合转集合:如String集合转为Integer集合
//ConvertiblePair是描述SourceType--->TargetType
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class));
	}

//前置判断---判断是否能进行转换
//判断能否转换的依据:集合里的元素与元素之间是否能够转换,底层依赖于ConversionService#canConvert()这个API去完成判断。
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return ConversionUtils.canConvertElements(
				sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
	}

//match判断认可后,才可以进行convert具体转换
	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}
		Collection<?> sourceCollection = (Collection<?>) source;
       //快速返回:对于特殊情况,做快速返回处理
        
        //1.若目标元素类型是源元素类型的子类型(或相同),就没有转换的必要了(copyRequired = false)
        
		//TargetType instance of SourceType的话,就不需要进行转换工作了,直接返回即可
		//例如TargetType是ArrayList,SourceType是List
		boolean copyRequired = !targetType.getType().isInstance(source);
		if (!copyRequired && sourceCollection.isEmpty()) {
			return source;
		}
		//getElementTypeDescriptor():如果是集合,数组或者stream流,返回里面的元素类型
		//但是注意如果Collection集合时泛型化的,才会返回元素类型
		//如果Collection不是泛型化的,那么返回null
		TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
		if (elementDesc == null && !copyRequired) {
		//此时Collection里面的元素可以认为是Object,那这样的话,Collection to Collection直接返回就可以了
			return source;
		}

//若没有触发快速返回。给目标创建一个新集合,然后把source的元素一个一个的放进新集合里去,这里又分为两种处理case		
		Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
				(elementDesc != null ? elementDesc.getType() : null), sourceCollection.size());
//若新集合(目标集合)没有指定泛型类型(那就是Object),就直接putAll即可,并不需要做类型转换
		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);
	}

}
缺陷

如果说它的优点是功能强大,能够处理复杂类型的转换(PropertyEditor和前2个接口都只能转换单元素类型),那么缺点就是使用、自定义实现起来比较复杂。这不官方也给出了使用指导意见:在Converter/ConverterFactory接口能够满足条件的情况下,可不使用此接口就不使用。

ConditionalConverter

条件接口,@since 3.2。它可以为Converter、GenericConverter、ConverterFactory转换增加一个前置判断条件。

public interface ConditionalConverter {
	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

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

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

四个兜底的GenericConverter转换器

上文留下了4个类型转换器,下面来讲讲:

  • StreamConverter:将Stream流与集合/数组之间的转换,必要时转换元素类型

这三个比较特殊,属于“最后的”“兜底类”类型转换器:

  • ObjectToObjectConverter:通用的将原对象转换为目标对象(通过工厂方法or构造器)
  • IdToEntityConverter:给个ID自动帮你兑换成一个Entity对象
  • FallbackObjectToStringConverter:将任何对象调用toString()转化为String类型。当匹配不到任何转换器时,它用于兜底

默认转换器注册情况

Spring新一代类型转换内建了非常多的实现,这些在初始化阶段大都被默认注册进去。注册点在DefaultConversionService提供的一个static静态工具方法里:

public static void addDefaultConverters(ConverterRegistry converterRegistry) {
	    // 1、添加标量转换器(和数字相关)
		addScalarConverters(converterRegistry);
		// 2、添加处理集合的转换器
		addCollectionConverters(converterRegistry);

       	// 3、添加对JSR310时间类型支持的转换器
		converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
		converterRegistry.addConverter(new StringToTimeZoneConverter());
		converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
		converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

        // 4、添加兜底转换器(上面处理不了的全交给这几个哥们处理)
		converterRegistry.addConverter(new ObjectToObjectConverter());
		converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
		converterRegistry.addConverter(new FallbackObjectToStringConverter());
		converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
	}

该静态方法用于注册全局的、默认的转换器们,从而让Spring有了基础的转换能力,进而完成绝大部分转换工作。

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

JSR310转换器只看到TimeZone、ZoneId等转换,更为常用的LocalDate、LocalDateTime等这些类型转换,在spring理解中是格式化操作,因此主要由Formatter组件完成

StreamConverter

用于实现集合/数组类型到Stream类型的互转,这从它支持的Set< ConvertiblePair > 集合也能看出来:

//将 Stream 与集合或数组相互转换,必要时转换元素类型。
class StreamConverter implements ConditionalGenericConverter {
     //Stream类型的描述符
	private static final TypeDescriptor STREAM_TYPE = TypeDescriptor.valueOf(Stream.class);
    //可以转换的类型集合
	private static final Set<ConvertiblePair> CONVERTIBLE_TYPES = createConvertibleTypes();
    //转换服务
	private final ConversionService conversionService;
    
	public StreamConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}
   
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return CONVERTIBLE_TYPES;
	}
    //判断是否可以进行类型转换---可以进行双向转换
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
	//getElementTypeDescriptor()拿到的是集合或者stream流里面的元素类型
		//源类型是Stream类型
		if (sourceType.isAssignableTo(STREAM_TYPE)) {
			return matchesFromStream(sourceType.getElementTypeDescriptor(), targetType);
		}
		//目标类型是Stream类型
		if (targetType.isAssignableTo(STREAM_TYPE)) {
			return matchesToStream(targetType.getElementTypeDescriptor(), sourceType);
		}
		return false;
	}

	/** 
    验证流中包含的元素的 Collection 是否可以转换为指定的 targetType. collection--->stream
	 */
	public boolean matchesFromStream(@Nullable TypeDescriptor elementType, TypeDescriptor targetType) {
	    //获取到集合类型的描述符---集合里面的元素是外部传入的elementType类型的元素
		TypeDescriptor collectionOfElement = TypeDescriptor.collection(Collection.class, elementType);
		//调用conversionService判断是否能够进行类型转换
		return this.conversionService.canConvert(collectionOfElement, targetType);
	}

	/**
    验证指定的 sourceType 是否可以转换为流元素类型的 Collection。stream--->collection
	 */
	public boolean matchesToStream(@Nullable TypeDescriptor elementType, TypeDescriptor sourceType) {
		TypeDescriptor collectionOfElement = TypeDescriptor.collection(Collection.class, elementType);
		//调用conversionService判断是否能够进行类型转换
		return this.conversionService.canConvert(sourceType, collectionOfElement);
	}
   
   
    //进行转换的api接口,这里支持双向转换,即Stream和Collection可以进行互相转换
	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (sourceType.isAssignableTo(STREAM_TYPE)) {
		//stream--->collection
			return convertFromStream((Stream<?>) source, sourceType, targetType);
		}
		if (targetType.isAssignableTo(STREAM_TYPE)) {
		//collection--->stream
			return convertToStream(source, sourceType, targetType);
		}
		// Should not happen
		throw new IllegalStateException("Unexpected source/target types");
	}

	//stream--->collection
	@Nullable
	private Object convertFromStream(@Nullable Stream<?> source, TypeDescriptor streamType, TypeDescriptor targetType) {
			//这里是先把Stream转换为List
		List<Object> content = (source != null ? source.collect(Collectors.<Object>toList()) : Collections.emptyList());
		TypeDescriptor listType = TypeDescriptor.collection(List.class, streamType.getElementTypeDescriptor());
		//然后进行转换
		return this.conversionService.convert(content, listType, targetType);
	}

//collection--->stream
	private Object convertToStream(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor streamType) {
	//描述符描述集合的时候,需要集合的具体类型和集合里面的元素类型
		TypeDescriptor targetCollection = TypeDescriptor.collection(List.class, streamType.getElementTypeDescriptor());
		//Collection先转换为List
		List<?> target = (List<?>) this.conversionService.convert(source, sourceType, targetCollection);
		if (target == null) {
			target = Collections.emptyList();
		}
		//List到Stream
		return target.stream();
	}

//默认支持的集合和stream直接互转的类型
	private static Set<ConvertiblePair> createConvertibleTypes() {
		Set<ConvertiblePair> convertiblePairs = new HashSet<>();
		convertiblePairs.add(new ConvertiblePair(Stream.class, Collection.class));
		convertiblePairs.add(new ConvertiblePair(Stream.class, Object[].class));
		convertiblePairs.add(new ConvertiblePair(Collection.class, Stream.class));
		convertiblePairs.add(new ConvertiblePair(Object[].class, Stream.class));
		return convertiblePairs;
	}

}

StreamConverter通过上面源码的分析可以看出,该转换器可以实现stream和collection之间的互相转化

使用场景

StreamConverter它的访问权限是default,我们并不能直接使用到它。通过上面介绍可知Spring默认把它注册进了注册中心里,因此面向使用者我们直接使用转换服务接口ConversionService便可。

@Test
public void test3() {
    System.out.println("----------------StreamConverter使用场景---------------");
    ConversionService conversionService = new DefaultConversionService();
    Stream<Integer> result = conversionService.convert(Collections.singleton(1), Stream.class);

    // 消费
    result.forEach(System.out::println);
    // result.forEach(System.out::println); //stream has already been operated upon or closed
}

运行程序,输出:

----------------StreamConverter使用场景---------------
1

再次特别强调:流只能被读(消费)一次。

因为有了ConversionService提供的强大能力,我们就可以在基于Spring/Spring Boot做二次开发时使用它,提高系统的通用性和容错性。如:当方法入参是Stream类型时,你既可以传入Stream类型,也可以是Collection类型、数组类型,是不是瞬间逼格高了起来。

兜底转换器

按照添加转换器的顺序,Spring在最后添加了4个通用的转换器用于兜底,你可能平时并不关注它,但它实时就在发挥着它的作用。

ObjectToObjectConverter

将源对象转换为目标类型,非常的通用:Object -> Object:

final class ObjectToObjectConverter implements ConditionalGenericConverter {

	// Cache for the latest to-method resolved on a given Class
	private static final Map<Class<?>, Member> conversionMemberCache =
			new ConcurrentReferenceHashMap<>(32);
    
    //获取当前转换器可以转换的类型
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
	}
    
    //hasConversionMethodOrConstructor: 是否有转换方法或者构造器
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
	    //源类型不要和目标类型相同,不然就没有转换的意义了
		return (sourceType.getType() != targetType.getType() &&
		//是否有转换方法或者相关构造器---下面会分析
				hasConversionMethodOrConstructor(targetType.getType(), sourceType.getType()));
	}

   //进行转换的方法
	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}
		Class<?> sourceClass = sourceType.getType();
		Class<?> targetClass = targetType.getType();
		//通过源类型和目标类型,拿到对应的转化方法或者构造器
		Member member = getValidatedMember(targetClass, sourceClass);

		try {
		//如果拿到的是转换方法
			if (member instanceof Method) {
				Method method = (Method) member;
				ReflectionUtils.makeAccessible(method);
				//对成员方法和静态方法进行区分
				//成员方法需要关联一个具体对象实例才可以调用,静态方法不需要
				//这里转换方法的会要求传入待转换的原对象,然后该方法会返回转换后的对象
				if (!Modifier.isStatic(method.getModifiers())) {
					return method.invoke(source);
				}
				else {
					return method.invoke(null, source);
				}
			}
			//如果拿到的是构造器
			else if (member instanceof Constructor) {
			//这里拿到的是目标对象的构造器--通过构造器传入原对象来构造目标对象
				Constructor<?> ctor = (Constructor<?>) member;
				ReflectionUtils.makeAccessible(ctor);
				return ctor.newInstance(source);
			}
		}
		catch (InvocationTargetException ex) {
			throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException());
		}
		catch (Throwable ex) {
			throw new ConversionFailedException(sourceType, targetType, source, ex);
		}

		// If sourceClass is Number and targetClass is Integer, the following message should expand to:
		// No toInteger() method exists on java.lang.Number, and no static valueOf/of/from(java.lang.Number)
		// method or Integer(java.lang.Number) constructor exists on java.lang.Integer.
		throw new IllegalStateException(String.format("No to%3$s() method exists on %1$s, " +
				"and no static valueOf/of/from(%1$s) method or %3$s(%1$s) constructor exists on %2$s.",
				sourceClass.getName(), targetClass.getName(), targetClass.getSimpleName()));
	}

     //是否有可进行转换的方法或者构造器
	static boolean hasConversionMethodOrConstructor(Class<?> targetClass, Class<?> sourceClass) {
		return (getValidatedMember(targetClass, sourceClass) != null);
	}
     
     //获取有效的转方式---转换方法或者可用的构造器
	@Nullable
	private static Member getValidatedMember(Class<?> targetClass, Class<?> sourceClass) {
	//先去缓存中拿-----spring一贯作风
		Member member = conversionMemberCache.get(targetClass);
		//是否可用---具体如何判断的我们下面会进行分析
		if (isApplicable(member, sourceClass)) {
			return member;
		}
       //缓存中没---那就去寻找一个--如何寻找的,下面分析 
		member = determineToMethod(targetClass, sourceClass);
		if (member == null) {
		//没找到---去找工厂方法
			member = determineFactoryMethod(targetClass, sourceClass);
			if (member == null) {
			//还是没有,去寻找工厂构造器
				member = determineFactoryConstructor(targetClass, sourceClass);
				if (member == null) {
				//尽力了,放弃了,找不到啊!!!
					return null;
				}
			}
		}
        //因为寻找的过程比较浪费资源,怎么办,那就缓存起来呗
		conversionMemberCache.put(targetClass, member);
		return member;
	}

//判断某个转换方法或者构造器是否可用
	private static boolean isApplicable(Member member, Class<?> sourceClass) {
		if (member instanceof Method) {
		//传入的是一个转换方法
			Method method = (Method) member;
			return (!Modifier.isStatic(method.getModifiers()) ?
			       //转换方法是一个普通的成员方法---该方法管理对象的类型必须是原对象的某个方法
					ClassUtils.isAssignable(method.getDeclaringClass(), sourceClass) :
					//转换方法是一个静态方法,该方法第一个参数必须是原对象类型
					method.getParameterTypes()[0] == sourceClass);
		}
		//传入的是一个构造器
		else if (member instanceof Constructor) {
			Constructor<?> ctor = (Constructor<?>) member;
			//构造器第一个参数必须是原对象类型
			return (ctor.getParameterTypes()[0] == sourceClass);
		}
		else {
			return false;
		}
	}
    
   
   //返回一个可用的转化方法---转换方法存在于原对象中且必须是非静态成员方法---命名规范为to+类名
	@Nullable
	private static Method determineToMethod(Class<?> targetClass, Class<?> sourceClass) {
	//string类型不能进行Object--->Object的转化
		if (String.class == targetClass || String.class == sourceClass) {
			// Do not accept a toString() method or any to methods on String itself
			return null;
		}
        //sourceClass是原对象
        //targetClass是目标对象
        //从这里我们可以看出获取转换方法的过程:
        //例如: 自定义User转自定义Peo,转换方法找的是User类中的toPeo方法----to+类名
		Method method = ClassUtils.getMethodIfAvailable(sourceClass, "to" + targetClass.getSimpleName());
		return (
       //方法存在
       method != null && 
       //方法为普通成员方法
       !Modifier.isStatic(method.getModifiers()) &&
		//方法返回值必须是目标对象类型
				ClassUtils.isAssignable(targetClass, method.getReturnType()) ? 
				//满足上面的条件,会返回该方法,否则返回NULL
				method : null);
	}

//寻找是否存在工厂方法---必须是静态的--存在于目标对象内部---命名规范为valueOf或者of或者from
	@Nullable
	private static Method determineFactoryMethod(Class<?> targetClass, Class<?> sourceClass) {
		if (String.class == targetClass) {
			// Do not accept the String.valueOf(Object) method
			return null;
		}
       //工厂方法存在于目标对象内
       //命名规范为valueOf或者of或者from
		Method method = ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass);
		if (method == null) {
			method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass);
			if (method == null) {
				method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass);
			}
		}
		return method;

	}

//寻找工厂构造器---是去目标对象中寻一个构造器,该构造器参数为原对象
//例如: 我们要把User转换为Peo,那么这里的工厂构造器就是指: Peo(User u)
	@Nullable
	private static Constructor<?> determineFactoryConstructor(Class<?> targetClass, Class<?> sourceClass) {
		return ClassUtils.getConstructorIfAvailable(targetClass, sourceClass);
	}

}

Member相关实现类主要是来描述一个字段,方法或者构造器相关信息的

使用演示

ObjectToObjectConverter这个转换器看上去第一眼可能比较难,但是大家如果读完了上面的源码分析,会发现,不过如此,那么下面我们来验证一下上面的分析

  • 因为该类是spring内部使用,因此类的标识符是包内可见,但是我们可以将测试类包名和其相同来进行测试
public class User {
    String name;
    Integer age;

    public Peo toPeo()
    {
        Peo peo=new Peo();
        peo.setName(name);
        peo.setAge(age);
        return peo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
public class Peo {
    String name;
    Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Peo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Main {
    public static void main(String[] args) {
        ObjectToObjectConverter objectToObjectConverter = new ObjectToObjectConverter();
        Set<GenericConverter.ConvertiblePair> types = objectToObjectConverter.getConvertibleTypes();
        System.out.println("ObjectToObjectConverter可以对下面的类型进行相关转换: ");
        System.out.println(types);
        System.out.println("User是否可以转换为Peo");
        TypeDescriptor sourceClass =TypeDescriptor.valueOf(User.class);
        TypeDescriptor targetClass = TypeDescriptor.valueOf(Peo.class);
        boolean matches = objectToObjectConverter.matches(sourceClass, targetClass);
        if(matches)
        {
            User user = new User();
            user.setAge(18);
            user.setName("大忽悠");
            System.out.println("类型转换结果为: "+objectToObjectConverter.convert(user,sourceClass,targetClass));
        }
        else
        {
            System.out.println("无法进行类型转换");
        }
    }
}

还可以去掉User类里面的toPeo方法,在Peo中增加from/valueOf/of(User)或者增加一个Peo(User u)的构造器也可以完成转换,不信大家可以试试看

ConversionService负责管理转换器,DefaultConversionService默认会注册很多转换器,包括上面给出的这个兜底转换器,因此上面测试的写法,还可以写出下面这样子:

//DefaultConversionService()会注册大量默认转换器
        ConversionService conversionService=new DefaultConversionService();
        User user = new User();
        user.setAge(18);
        user.setName("大忽悠");
        //最终选用的是Object--->Object的转换器
        Peo convert = conversionService.convert(user, Peo.class);
        System.out.println(convert);
public class Peo {
    String name;
    Integer age;

    public Peo() {
    }

    public Peo(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

//必须是静态方法
    static public Peo valueOf(User user)
    {
        return new Peo(user.name, user.age);
    }

    @Override
    public String toString() {
        return "Peo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class User {
    String name;
    Integer age;
}

使用场景

基于本转换器可以完成任意对象 -> 任意对象的转换,只需要遵循方法名/构造器默认的一切约定即可,在我们平时开发书写转换层时是非常有帮助的,借助ConversionService可以解决这一类问题。

对于Object -> Object的转换,另外一种方式是自定义Converter<S,T>,然后注册到注册中心。至于到底选哪种合适,这就看具体应用场景喽,本文只是多给你一种选择

IdToEntityConverter
/**
通过在目标实体类型上调用静态查找器方法,将实体标识符转换为实体引用。
要使此转换器匹配,finder 方法必须是静态的,具有签名 find[EntityName]([IdType]),
并返回所需实体类型的实例。
 */
final class IdToEntityConverter implements ConditionalGenericConverter {
   //转换器服务类
	private final ConversionService conversionService;

	public IdToEntityConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

    //当前转换器负责将Object--->Object
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
	}

   //判断当前转换器能否进行转换
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
	//找到指定负责转换的方法
		Method finder = getFinder(targetType.getType());
		return 
         //对应方法存在
         (finder != null &&
				//并且能够将sourceType转换为方法的第一个参数---source: "1" target: long id
				this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0])));
	}

//进行具体的转化操作
	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}
		//找到对应的转化方法
		Method finder = getFinder(targetType.getType());
		Assert.state(finder != null, "No finder method");
		//将source转换为finder方法第一个参数的类型
		Object id = this.conversionService.convert(
				source, sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0]));
        //finder是目标类里面的转换方法,id就是source转换后作为find方法入参的参数
        //第二个参数本应该传入目标对象,但是因为这里是静态方法,因此不需要传入目标对象
        //所以按理这里应该传入null,但是又因为传入其他参数,只要jdk发现是静态方法,那么就会忽略该参数
        //因此这里传入source也不会影响				
		return ReflectionUtils.invokeMethod(finder, source, id);
	}

   //定位转换方法
	@Nullable
	private Method getFinder(Class<?> entityClass) {
		//方法名是固定的---find+实体类的名字---例如Peo--->findPeo()
		String finderMethod = "find" + getEntityName(entityClass);
		Method[] methods;
		boolean localOnlyFiltered;
		try {
		   //找到目标对象中声明的所有方法
			methods = entityClass.getDeclaredMethods();
			localOnlyFiltered = true;
		}
		catch (SecurityException ex) {
			// Not allowed to access non-public methods...
			// Fallback: check locally declared public methods only.
			methods = entityClass.getMethods();
			localOnlyFiltered = false;
		}
		//遍历目标对象中的所有方法
		for (Method method : methods) {
		//方法必须是静态的,方法名必须是find+类名,方法参数个数必须是一个,方法返回值必须是目标对象类型
			if (Modifier.isStatic(method.getModifiers()) && method.getName().equals(finderMethod) &&
					method.getParameterCount() == 1 && method.getReturnType().equals(entityClass) &&
					(localOnlyFiltered || method.getDeclaringClass().equals(entityClass))) {
				return method;
			}
		}
		return null;
	}

	    //去除包名,返回类名
	private String getEntityName(Class<?> entityClass) {
		String shortName = ClassUtils.getShortName(entityClass);
		int lastDot = shortName.lastIndexOf('.');
		if (lastDot != -1) {
			return shortName.substring(lastDot + 1);
		}
		else {
			return shortName;
		}
	}

}
使用演示
@Data
public class Peo {
    Long id;
    String name;
    Integer age;

    /**
     * 根据ID定位一个Person实例
     */
    public static Peo findPerson(Long id) {
        // 一般根据id从数据库查,本处通过new来模拟
        Peo person = new Peo();
        person.setId(id);
        person.setName("大忽悠");
        person.setAge(18);
        return person;
    }

    @Override
    public String toString() {
        return "Peo{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
IdToEntityConverter idToEntityConverter = new IdToEntityConverter(new DefaultConversionService());
        TypeDescriptor source = TypeDescriptor.valueOf(String.class);
        TypeDescriptor target = TypeDescriptor.valueOf(Peo.class);
        boolean matches = idToEntityConverter.matches(source, target);
        System.out.println("是否能够转换: "+matches);

        //执行转换
        Object convert = idToEntityConverter.convert("1", source, target);
        System.out.println(convert);

使用场景

这个使用场景就比较多了,需要使用到findById()的地方都可以通过它来代替掉。如:

Controller层:

@GetMapping("/ids/{id}")
public Object getById(@PathVariable Person id) {
    return id;
}

@GetMapping("/ids")
public Object getById(@RequestParam Person id) {
    return id;
}

Tips:在Controller层这么写我并不建议,因为语义上没有对齐,势必在代码书写过程中带来一定的麻烦。

Service层

@Autowired
private ConversionService conversionService;

public Object findById(String id){
    Person person = conversionService.convert(id, Person.class);

    return person;
}

Tips:在Service层这么写,我个人觉得还是OK的。用类型转换的领域设计思想代替了自上而下的过程编程思想。

FallbackObjectToStringConverter

通过简单的调用Object#toString()方法将任何支持的类型转换为String类型,它作为底层兜底。

final class FallbackObjectToStringConverter implements ConditionalGenericConverter {
    //支持将任意类型转换为string
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object.class, String.class));
	}
    //判断是否支持转换
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		Class<?> sourceClass = sourceType.getObjectType();
		//如果原类型就是string,那么不需要进行转换操作
		if (String.class == sourceClass) {
			// no conversion required
			return false;
		}
		return (CharSequence.class.isAssignableFrom(sourceClass) ||
				StringWriter.class.isAssignableFrom(sourceClass) ||
				//这里是判断原对象是否存在toString方法
				ObjectToObjectConverter.hasConversionMethodOrConstructor(sourceClass, String.class));
	}

	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		return (source != null ? source.toString() : null);
	}

}

说明:ObjectToObjectConverter不处理任何String类型的转换,原来都是交给它了

ObjectToOptionalConverter

将任意类型转换为一个Optional< T >类型,它作为最最最最最底部的兜底,稍微了解下即可。

final class ObjectToOptionalConverter implements ConditionalGenericConverter {

	private final ConversionService conversionService;

	public ObjectToOptionalConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

//支持将集合,Object[],Object都转换为Optional
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		Set<ConvertiblePair> convertibleTypes = new LinkedHashSet<>(4);
		convertibleTypes.add(new ConvertiblePair(Collection.class, Optional.class));
		convertibleTypes.add(new ConvertiblePair(Object[].class, Optional.class));
		convertibleTypes.add(new ConvertiblePair(Object.class, Optional.class));
		return convertibleTypes;
	}

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (targetType.getResolvableType().hasGenerics()) {
			return this.conversionService.canConvert(sourceType, new GenericTypeDescriptor(targetType));
		}
		else {
			return true;
		}
	}

	@Override
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return Optional.empty();
		}
		else if (source instanceof Optional) {
			return source;
		}
		else if (targetType.getResolvableType().hasGenerics()) {
			Object target = this.conversionService.convert(source, sourceType, new GenericTypeDescriptor(targetType));
			if (target == null || (target.getClass().isArray() && Array.getLength(target) == 0) ||
						(target instanceof Collection && ((Collection<?>) target).isEmpty())) {
				return Optional.empty();
			}
			return Optional.of(target);
		}
		else {
			return Optional.of(source);
		}
	}

	@SuppressWarnings("serial")
	private static class GenericTypeDescriptor extends TypeDescriptor {

		public GenericTypeDescriptor(TypeDescriptor typeDescriptor) {
			super(typeDescriptor.getResolvableType().getGeneric(), null, typeDescriptor.getAnnotations());
		}
	}

}
使用场景

一个典型的应用场景:在Controller中可传可不传的参数中,我们不仅可以通过@RequestParam(required = false) Long id来做,还是可以这么写:@RequestParam Optional< Long > id。

小结

针对于Spring注册转换器,需要特别注意如下几点:

  • 注册顺序很重要。先注册,先服务(若支持的话)
  • 默认情况下,Spring会注册大量的内建转换器,从而支持String/数字类型转换、集合类型转换,这能解决协议层面的大部分转换问题。
  • 如Controller层,输入的是JSON字符串,可用自动被封装为数字类型、集合类型等等
  • 如@Value注入的是String类型,但也可以用数字、集合类型接收

对于复杂的对象 -> 对象类型的转换,一般需要你自定义转换器,或者参照本文的标准写法完成转换。总之:Spring提供的ConversionService专注于类型转换服务,是一个非常非常实用的API,特别是你正在做基于Spring二次开发的情况下。

相关文章