天天看點

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

每篇一句

表像大都這樣:出力的不掙錢,掙錢的不出力

前言

前面聊了

HttpMessageConverter

,它的名稱叫

消息轉換器

,是以它面向的是消息體,和

Http

強相關,是以該接口所在的包為:

org.springframework.http.converter

資料轉換,顧名思義就是資料類型之間的轉換,但是對于資料轉換,有的是可以進行轉化的,例如字元串轉整型,但是有些資料類型之間是不能進行轉換的,例如從“aaa”字元串到整型的轉換。

不同的架構,肯定都有自己的資料轉換的實作,比如MyBatis、Hibernate等這些轉換器都是必備的。然後作為這麼強大的Spring,它肯定也缺席不了。

org.springframework.core.convert.converter.Converter

它位于核心包中,是以它不僅僅運用于

Spring MVC

等web環境,比如spring-jdbc等都是有使用到的~

資料轉換在架構設計中是非常重要的一環,它能讓你的架構更普适,更通用,更自動化,解決的問題更多,是以我個人認為,了解Spring資料轉換的設計思想,以及它的常用實作是非常有必要的。

若是源生Servlet開發,你能想象到那種低下的開發效率嗎以及漫天遍地的“垃圾代碼”嗎?

關于Spring中的資料轉換,首先需要了解兩大主要分支:

  1. Converter<S, T>

    :是Spring中最為簡單的一個接口。位于包:

    org.springframework.core.convert.converter

    。 相關的頂層接口(類)有:

    ConditionalConverter

    GenericConverter

    ConverterFactory

    ConvertingComparator

    ConverterRegistry

  2. ConversionService

    :用于類型轉換的服務接口。這是進入轉換系統的

    入口點

    。位于包:

    org.springframework.core.convert

    。相關的頂層接口(類)有:

    ConversionService

    FormattingConversionService

    DefaultConversionService

    ConversionServiceFactoryBean

    FormattingConversionServiceFactoryBean

注意各子接口,實作類不一定都是core包裡,可能在context包、web包等等~。他倆體系都是@since 3.0

Converter<S, T>

Spring

Converter

是可以将一種類型轉換成另一種類型的一個對象,它的接口定義非常的的簡單。

// 實作此接口的 大都會實作ConditionalConverter
// 請保持線程安全~~
@FunctionalInterface
public interface Converter<S, T> {
	// 把S轉成T
	@Nullable
	T convert(S source);
}
           

Spring提供了3種converter接口,分别是

Converter、ConverterFactory和GenericConverter

.一般用于

1:1

,

1:N

,

N:N

的source->target類型轉化。

Converter接口 :使用最簡單,最不靈活;

ConverterFactory接口 :使用較複雜,比較靈活;

GenericConverter接口 :使用最複雜,也最靈活;

Converter

Converter

的實作類舉例:該接口

Spring

内部的實作也非常多,大多數都是以内部類的形式實作(因為它是一個

@FunctionalInterface

嘛)

// ObjectToStringConverter
final class ObjectToStringConverter implements Converter<Object, String> {
	@Override
	public String convert(Object source) {
		return source.toString();
	}
}
           
// StringToCharsetConverter  @since 4.2
	@Override
	public Charset convert(String source) {
		return Charset.forName(source);
	}
// StringToPropertiesConverter
	@Override
	public Properties convert(String source) {
		try {
			Properties props = new Properties();
			// Must use the ISO-8859-1 encoding because Properties.load(stream) expects it.
			props.load(new ByteArrayInputStream(source.getBytes(StandardCharsets.ISO_8859_1)));
			return props;
		}catch (Exception ex) {
			// Should never happen.
			throw new IllegalArgumentException("Failed to parse [" + source + "] into Properties", ex);
		}
	}
// StringToTimeZoneConverter @since 4.2
	@Override
	public TimeZone convert(String source) {
		return StringUtils.parseTimeZoneString(source);
	}
//ZoneIdToTimeZoneConverter @since 4.0
	@Override
	public TimeZone convert(ZoneId source) {
		return TimeZone.getTimeZone(source);
	}

// StringToBooleanConverter  這個轉換器很有意思  哪些代表true,哪些代表fasle算是業界的一個規範了
// 這就是為什麼,我們給傳值1也會被當作true來封裝進Boolean類型的根本原因所在~
	static {
		trueValues.add("true");
		trueValues.add("on");
		trueValues.add("yes");
		trueValues.add("1");

		falseValues.add("false");
		falseValues.add("off");
		falseValues.add("no");
		falseValues.add("0");
	}
// StringToUUIDConverter  @since 3.2
	@Override
	public UUID convert(String source) {
		return (StringUtils.hasLength(source) ? UUID.fromString(source.trim()) : null);
	}
// StringToLocaleConverter
	@Override
	@Nullable
	public Locale convert(String source) {
		return StringUtils.parseLocale(source);
	}

// SerializingConverter:把任意一個對象,轉換成byte[]數組,唯獨這一個是public的,其它的都是Spring内置的
public class SerializingConverter implements Converter<Object, byte[]> {
	// 序列化器:DefaultSerializer   就是new ObjectOutputStream(outputStream).writeObject(object)
	// 就是簡單的把對象寫到輸出流裡~~
	private final Serializer<Object> serializer;
	public SerializingConverter() {
		this.serializer = new DefaultSerializer();
	}
	public SerializingConverter(Serializer<Object> serializer) { // 自己亦可指定實作。
		Assert.notNull(serializer, "Serializer must not be null");
		this.serializer = serializer;
	}

	@Override
	public byte[] convert(Object source) {
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
		try  {
			this.serializer.serialize(source, byteStream);
			// 把此輸出流轉為byte[]數組~~~~~~
			return byteStream.toByteArray();
		} catch (Throwable ex) {
			throw new SerializationFailedException("Failed to serialize object using " +
					this.serializer.getClass().getSimpleName(), ex);
		}
	}

}
           

Converter

接口非常的簡單,是以除了

SerializingConverter

一個是外部類,我們可以拿來使用外,其餘的都是Spring内部自己使用的。從此可以看出:此接口一般也用于我們自己去實作,即:自定義資料轉換器。

自定義轉換器的一個Demo:

// 把形如這樣的字元串:  "fsx:18" 轉換為Person對象
public class PersonConverter implements Converter<String, Person> {

    @Override
    public Person convert(String source) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }
        String[] strings = StringUtils.delimitedListToStringArray(source, ":");
        Person person = new Person();
        person.setName(strings[0]);
        person.setAge(Integer.valueOf(strings[1]));
        return person;
    }

    public static void main(String[] args) {
        PersonConverter personConverter = new PersonConverter();
        System.out.println(personConverter.convert("fsx:18")); //Person{name='fsx', age=18}
    }
}
           
備注:在Spring内部消息轉換器的注冊、使用一般都結合

ConversionService

這個接口

ConditionalConverter

根據source和target來做條件判斷,進而可以判斷哪個轉換器生效,哪個不生效之類的。

// @since 3.2   出現稍微較晚
public interface ConditionalConverter {
	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
           

org.springframework.core.convert.TypeDescriptor

也是一個Spring的基礎類(類似

ResolvableType

)這種,若有需要我們平時也可以使用它。 它能夠把

基礎類型

MethodParameter

Field

org.springframework.core.convert.Property

Class

等都描述進來。并且提供如下非常友善方法:
// @since 3.0
public class TypeDescriptor implements Serializable {
	public Class<?> getType() {
		return this.type;
	}
	public ResolvableType getResolvableType() {
		return this.resolvableType;
	}
	public Object getSource() {
		return this.resolvableType.getSource();
	}
	public String getName();
	public boolean isPrimitive();
	public Annotation[] getAnnotations();
	public boolean hasAnnotation(Class<? extends Annotation> annotationType);
	public <T extends Annotation> T getAnnotation(Class<T> annotationType);
	public boolean isAssignableTo(TypeDescriptor typeDescriptor);
	public boolean isCollection();
	public boolean isArray();
	public boolean isMap();
	public TypeDescriptor getMapKeyTypeDescriptor();
	public TypeDescriptor getMapValueTypeDescriptor()

	// 靜态方法:可吧基礎類型、任意一個class類型轉為這個描述類型  依賴于下面的valueOf方法  source為null  傳回null
	public static TypeDescriptor forObject(@Nullable Object source);
	public static TypeDescriptor valueOf(@Nullable Class<?> type);
	// 把集合轉為描述類型~
	public static TypeDescriptor collection(Class<?> collectionType, @Nullable TypeDescriptor elementTypeDescriptor)
	public static TypeDescriptor map(Class<?> mapType, @Nullable TypeDescriptor keyTypeDescriptor, @Nullable TypeDescriptor valueTypeDescriptor);
	public static TypeDescriptor array(@Nullable TypeDescriptor elementTypeDescriptor);
	public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel);
	public static TypeDescriptor nested(Field field, int nestingLevel);
	public static TypeDescriptor nested(Property property, int nestingLevel);
}
           

ConditionalConverter

的繼承樹:

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

ConditionalGenericConverter

這個子接口,就是把

GenericConverter

ConditionalConverter

聯合起來了。而

GenericConverter

我們上面提到了,它一般用于處理

N:N

的轉換,是以它的子類們放在下面講會更合适~

NumberToNumberConverterFactory

:它是個

ConverterFactory

,是以也放下面

AbstractConditionalEnumConverter

:枚舉類型的轉換

// @since 4.3  也是隻能Spring内部自己用的
abstract class AbstractConditionalEnumConverter implements ConditionalConverter {
	// 它借助了ConversionService這個接口  需要外部自定義轉換邏輯~~
	private final ConversionService conversionService;
	protected AbstractConditionalEnumConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}


	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		// 拿到source所有實作的接口  若沒有實作任何接口,永遠傳回true
		for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClassAsSet(sourceType.getType())) {
			// 最終是委托給conversionService去做這件事了~~~~
			if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
				return false;
			}
		}
		return true;
	}

}
           

它的兩個子類實作:

EnumToIntegerConverter

EnumToStringConverter

就是調用了

source.ordinal()

source.name()

。若你想要實作自己的枚舉自定義屬性的轉換,其實是可以繼承

AbstractConditionalEnumConverter

它的,但是Spring并沒有公開它,so~~~你還是自己寫吧

ConverterFactory

ConverterFactory

:range範圍轉換器的工廠:可以将對象從S轉換為R的子類型(1:N)

public interface ConverterFactory<S, R> {
	//Get the converter to convert from S to target type T, where T is also an instance of R
	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
           

它的實作類不多:

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {

	// ConversionUtils.getEnumType表示拿出枚舉的class類型
	@Override
	public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
		return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
	}
	
	// 内部類的實作  把Integer轉為Enum的子類型~~~   相當于根據integer找到一個enum(注意此處根據角标來找的)
	private class IntegerToEnum<T extends Enum> implements Converter<Integer, T> {
		private final Class<T> enumType;
		public IntegerToEnum(Class<T> enumType) {
			this.enumType = enumType;
		}
		@Override
		public T convert(Integer source) {
			return this.enumType.getEnumConstants()[source];
		}
	}

}

// StringToEnumConverterFactory  大體同上  return (T) Enum.valueOf(this.enumType, source.trim())
...
           

該工廠就是用來建立一個

converter

,把目标類型轉換成子類型,是以它是1->N的。注意:Spring内置的實作也都是外部不可通路的

GenericConverter

用于在兩個或多個類型之間轉換的通用轉換器接口。這是最靈活的轉換器SPI接口,也是最複雜的

靈活是因為它一個轉換器就能轉換多個s/t,是以它是N->N的。實作類們一般情況下也會實作接口:

ConditionalConverter

1個GenericConverter支援轉化的所有類型都寫在了屬性Set内
public interface GenericConverter {
	@Nullable
	Set<ConvertiblePair> getConvertibleTypes();
	@Nullable
	Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

	/**
	 * Holder for a source-to-target class pair.
	 */
	 // 包含有一對  s和t
	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;
		}
		... // 去掉get/set方法  以及toString equals等基礎方法
	}
}
           

它的實作類都是子接口

ConditionalGenericConverter

的實作類(就是GenericConverter和ConditionalConverter的結合).

注意:Spring的所有内部實作,依舊全部未公開,是以本文隻舉例說明一下即可。
final class ArrayToObjectConverter implements ConditionalGenericConverter {
	// 借助了ConversionService 
	private final ConversionService conversionService;
	public ArrayToObjectConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

	// 殘暴:都是object
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object[].class, Object.class));
	}

	// 實作ConditionalConverter的方法,最終是委托給了ConversionService#canConvert方法
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
	}
	...
}

// 這裡的轉換器,都和數組、集合有關,比如:
// StringToCollectionConverter、CollectionToArrayConverter、CollectionToStringConverter
// StringToArrayConverter、StreamConverter、CollectionToArrayConverter等等
           

特别說一句:這裡有一個非常有意思的轉換器:

IdToEntityConverter

,SpringMVC預設給我們這已經注冊進去了,在

Spring MVC

自定義常用的、通用的Controller的時候,我們會借助它實作通用方案,讓controller異常的友善,好使~~~暫時可先參考:路由id轉化為控制器Entity參數

ConverterRegistry

使用

ConverterRegistry

可以使我們對類型轉換器做一個統一的注冊。正如前言所說的,要實作自己的類型轉換邏輯我們可以實作Converter接口、ConverterFactory接口和GenericConverter接口,ConverterRegistry接口就分别為這三種類型提供了對應的注冊方法,至于裡面的邏輯就可以發揮自己的設計能力進行設計實作了。

通過ConverterAdapter或者ConverterFactoryAdapter最後都會轉化成GenericConverter,我想應該是因為這種converter是最通用的原因吧

一般而言:我們在實作

ConversionService

接口的時候也會實作

ConverterRegistry

接口
// @since 3.0  Converter 注冊處,用于存儲 Converter 執行個體
public interface ConverterRegistry {

	void addConverter(Converter<?, ?> 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);

	// 移除方法隻有一個:它是面向s和t來做移除的~~~~ 删除所有比對指定源和目标類型的 Converter
	// Remove any converters from {@code sourceType} to {@code targetType}
	void removeConvertible(Class<?> sourceType, Class<?> targetType);

}
           
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

兩大分支。

FormatterRegistry

用于注冊格式化器,下面再說

ConfigurableConversionService

:它就是把ConversionService和ConverterRegistry綁定在一起,自己并不提供新接口

// @since 3.1
public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {
}
           

是以它的具體内容,放到

ConversionService

裡描述吧。

ConversionService

用于類型轉換的服務接口。這是轉換系統的

**入口點**

。請保證它convert方法的線程安全,這個接口非常的重要。

舉個例子,使用

Environment

<T> T getProperty(String key, Class<T> targetType)

這裡的類型轉換,就是要通過

ConversionService

來完成的。

// @since 3.0
public interface ConversionService {
	
	// 特别說明:若是Map、集合、數組轉換時。即使下面方法convert轉換抛出了異常,這裡也得傳回true  因為Spring希望調用者處理這個異常:ConversionException
	boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
	boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

	// 注意此處:轉換的source都是對象,target隻需要類型即可~~~
	@Nullable
	<T> T convert(@Nullable Object source, Class<T> targetType);
	@Nullable
	Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}
           
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

GenericConversionService

它也并不是一個抽象類,它是一個通用的處理。但是一般不會直接使用它,而是使用它的更具體的子類

// @since 3.0  實作了接口ConversionService和ConverterRegistry  
public class GenericConversionService implements ConfigurableConversionService {
	
	// 啥都不做,但是呢conversion is not required,相當于占位的意思
	private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
	// 當轉換器緩存中沒有任何比對時,它上場
	// 請不要把它直接return,用null代替傳回
	private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");

	// 說明:Converter是一個靜态内部類 它會Manages all converters registered with the service
	private final Converters converters = new Converters();
	// 緩存轉換器。用的ConcurrentReferenceHashMap是Spring自己實作的一個軟引用/弱引用的Map
	private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);
	
	// 僅有一個空構造函數,構造函數内啥都沒做

	@Override
	public void addConverter(Converter<?, ?> converter) {
		// 這個處理很有意思:getRequiredTypeInfo    拿到兩個泛型參數類型(若沒有指定泛型  傳回的是null)
		ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
		// Decorate和Proxy模式的差別。Decorate模式可用于函數防抖   Proxy模式就是我們常用的代理模式
		if (typeInfo == null && converter instanceof DecoratingProxy) {
			typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class);
		}
		// 由此可見這個轉換器的泛型類型是必須的~~~
		if (typeInfo == null) {
			throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " +
					"Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
		}
	
		// ConverterAdapter是個GenericConverter。由此課件最終都是轉換成了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)));
	}
	// 最終都是轉換成了GenericConverter  進行轉換器的儲存  全部放在Converters裡儲存着
	@Override
	public void addConverter(GenericConverter converter) {
		this.converters.add(converter);
		invalidateCache(); // 清空緩存
	}

	// 使用ConverterFactoryAdapter轉換成GenericConverter
	@Override
	public void addConverterFactory(ConverterFactory<?, ?> factory) { ... }
	// 注意ConvertiblePair是重寫了equals方法和hash方法的
	@Override
	public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
		this.converters.remove(sourceType, targetType);
		invalidateCache();
	}

	// 主要是getConverter() 方法  相當于隻有有轉換器比對,就是能夠被轉換的
	@Override
	public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
		Assert.notNull(targetType, "Target type to convert to cannot be null");
		if (sourceType == null) {
			return true;
		}
		GenericConverter converter = getConverter(sourceType, targetType);
		return (converter != null);
	}
	@Nullable
	protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
		ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
		GenericConverter converter = this.converterCache.get(key);
		// 這個處理:如果緩存有值   但是為NO_MATCH 那就傳回null,而不是把No_Match直接return
		if (converter != null) {
			return (converter != NO_MATCH ? converter : null);
		}

		converter = this.converters.find(sourceType, targetType);
		if (converter == null) {
			converter = getDefaultConverter(sourceType, targetType);
		}
	
		// 如果預設的不為null 也可以return的
		// NO_OP_CONVERTER還是可以return的~~~
		if (converter != null) {
			this.converterCache.put(key, converter);
			return converter;
		}

		this.converterCache.put(key, NO_MATCH);
		return null;
	}
	@Nullable
	protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
	}
	// 拿到泛型類型們
	@Nullable
	private ResolvableType[] getRequiredTypeInfo(Class<?> converterClass, Class<?> genericIfc) {
		ResolvableType resolvableType = ResolvableType.forClass(converterClass).as(genericIfc);
		ResolvableType[] generics = resolvableType.getGenerics();
		if (generics.length < 2) {
			return null;
		}
		Class<?> sourceType = generics[0].resolve();
		Class<?> targetType = generics[1].resolve();
		if (sourceType == null || targetType == null) {
			return null;
		}
		return generics;
	}
	...
}
           
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

絕大多數情況下,我們不會直接使用

GenericConversionService

,而是使用它的子類

DefaultConversionService

DefaultConversionService

它能适用于絕大多數的場景中。

// @since 3.1
public class DefaultConversionService extends GenericConversionService {
	// @since 4.3.5 改變量出現得還是比較晚的
	@Nullable
	private static volatile DefaultConversionService sharedInstance;
	// 空構造,那就注冊到自己this身上~~~因為自己也是個ConverterRegistry
	public DefaultConversionService() {
		addDefaultConverters(this);
	}

	// 就是把sharedInstance傳回出去~~~(永遠不可能傳回null)
	public static ConversionService getSharedInstance() { ... }

	// 預設情況下,這個ConversionService注冊的轉換器們~~~~  幾乎涵蓋了所有~~~~
	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));
	}
	...
}
           

從源碼可以看出,它幾乎覆寫注冊了所有的通用的類型轉換,若涉及到自定義的對象的轉換,亦可自己自定義轉換器。

備注:

DefaultConversionService

它在

PropertyResolver

org.springframework.jdbc.core.RowMapper

org.springframework.expression.TypeConverter

…也就是properties、el表達式裡、spring-jdbc資料封裝的類型轉換裡都有應用

關于

FormattingConversionService

,它和格式化有關,是以放在

Formatter

章節裡了,可參考:

【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat…

ConversionServiceFactoryBean

它是我們

自定義轉換器

的一個入口。比如之前我們見過這麼配置的自定義轉換器:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
		<property name="converters">
			<set>
				<ref bean="userConverter"/>
			</set>
		</property>
	</bean>
           

這樣,我們的自定義的轉換器

userConverter

就被添加進去了。我們在Spring MVC中需要自定義轉換器的時候,也是這麼來弄的。(使用java配置的方式添加,此處省略)

它的源碼比較簡單:

public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
	// 儲存着我們diy set撿來的轉換器們
	@Nullable
	private Set<?> converters;
	// 最終是一個DefaultConversionService,然後向裡添加自定義的轉換器~
	@Nullable
	private GenericConversionService conversionService;
	
	// Bean初始化結束後,注冊自定義的轉換器進去~~
	@Override
	public void afterPropertiesSet() {
		this.conversionService = createConversionService();
		ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
	}
	protected GenericConversionService createConversionService() {
		return new DefaultConversionService();
	}

	@Override
	@Nullable
	public ConversionService getObject() {
		return this.conversionService;
	}
	// 最終是個GenericConversionService,實際是個DefaultConversionService
	@Override
	public Class<? extends ConversionService> getObjectType() {
		return GenericConversionService.class;
	}
	@Override
	public boolean isSingleton() {
		return true;
	}

}
           

另外,如果你還需要格式化的功能,使用

FormattingConversionServiceFactoryBean

代替即可

Spring中的PropertyEditor屬性編輯器

在文末稍微介紹一下Spring中的PropertyEditor屬性編輯器,因為它和類型轉換器特别的像。

PropertyEditor是JavaBean規範定義的接口,這是

java.beans

中一個接口,其設計的意圖是圖形化程式設計上,友善對象與String之間的轉換工作,而spring将其擴充,友善各種對象與String之間的轉換工作。

Spring所有的擴充都是通過繼承

PropertyEditorSupport

,因為它隻聚焦于

轉換

上,是以隻需複寫

setAsText()

getAsText()

以及構造方法即可實作擴充。

Spring 使用PropertyEditors的接口來實作對象和字元串之間的轉換,比如将 2007-14-09轉化為日期類型等,可以通過注冊自定義編輯器來實作此功能

下面貼出Spring内置的一些屬性編輯器們:

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
這些PropertyEditors都位于

org.springframework.beans.propertyeditors

包中,大多是都是由BeanWrapperImpl注冊,當屬性編輯器以某種方式進行配置時,開發者仍可以注冊自定義的變體用于覆寫預設的變量

應用的場景描述:

在基于xml的配置中,我們往往通過字面值為Bean各種類型的屬性提供設定值:不管是double類型還是int類型,在配置檔案中都對應字元串類型的字面值。

BeanWrapper

填充Bean屬性時如何将這個字面值轉換為對應的double或int等内部類型呢?我們可以隐約地感覺到一定有一個轉換器在其中起作用,這個轉換器就是屬性編輯器。

Spring MVC架構使用多種PropertyEditors分析HTTP請求的各種參數

有的小夥伴可能會問:既然有了

PropertyEditor

,那為何還需要有Converter呢?其實是因為Java原生的PropertyEditor存在以下兩點不足:

  1. 隻能用于字元串和Java對象的轉換,不适用于任意兩個Java類型之間的轉換;
  2. 對源對象及目标對象所在的上下文資訊(如注解、所在宿主類的結構等)不敏感,在類型轉換時不能利用這些上下文資訊實施進階轉換邏輯。

鑒于此,Spring 3.0在核心模型中添加了一個通用的類型轉換子產品,類型轉換子產品位于

org.springframework.core.convert

包中。Spring希望用這個類型轉換體系替換Java标準的PropertyEditor。但由于曆史原因,Spring将同時支援兩者。在Bean配置、Spring MVC處理方法入參綁定中使用它們。

Spring提供了

PropertyEditorRegistry

來注冊自定義的

Editor

~ 提供了

PropertyEditorRegistrar

這個注冊官來

registerCustomEditors

。它的實作類隻有

ResourceEditorRegistrar

使用

ResourceLoader

來加載資源~預設注冊了:Resource、InputStream、InputSource、File、Reader等等和資源先關的屬性編輯器。

public interface PropertyEditorRegistry {
	void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
	// propertyPath可以是name,可以是person.name這種複合的~~~~
	void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
	
	// 查找一個PropertyEditor 
	@Nullable
	PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
           
Converter or PropertyEditor?

Spring有兩種自動類型轉換器,一種是Converter,一種是PropertyEditor。

Converter是類型轉換成類型,Editor:從string類型轉換為其他類型。

從某種程度上,Converter包含Editor。如果出現需要從string轉換到其他類型。首選Editor。

org.springframework.beans.TypeConverter

TypeConverter

org.springframework.expression

包中還有一個,注意區分。

// @since 2.0
// 定義類型轉換方法的接口。通常(但不一定)與PropertyEditorRegistry接口一起實作
// 通常接口TypeConverter的實作是基于非線程安全的PropertyEditors類,是以也不是線程安全的
public interface TypeConverter {

	// 将參數中的value轉換成requiredType類型
	// 從String到任何類型的轉換通常使用PropertyEditor類的setAsText方法或ConversionService中的Spring Converter
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException;

	// 意義同上,增加了作為轉換目标的方法參數,主要用于分析泛型類型,可能是null
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException;
	// 意義同上,增加了轉換目标的反射field
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException;

	// @since 5.1.4
	@Nullable
	default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
			@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {

		throw new UnsupportedOperationException("TypeDescriptor resolution not supported");
	}

}
           

繼承樹如下:

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
TypeConverterSupport

TypeConverter的基本實作類,同時也是BeanWrapperImpl類的依賴類。

public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport implements TypeConverter {
	@Nullable
	TypeConverterDelegate typeConverterDelegate;
	... //它所有的convertIfNecessary工作都是委托給了TypeConverterDelegate
}
           

TypeConverterDelegate

類型轉換的委托類,所有類型轉換的工作都由該類完成,即将屬性轉換為其他類型的Spring内部使用方法(内部實作: 先使用PropertyEditor轉換器器轉換,如果沒找到對應的轉換器器,會⽤ConversionService來進⾏行行對象轉換。)

// @since 2.0
class TypeConverterDelegate {

	// PropertyEditorRegistrySupport#findCustomEditor和getConversionService
	// 就是處理這麼一個基本邏輯的~~~~
    private final PropertyEditorRegistrySupport propertyEditorRegistry;
    private final Object targetObject;
    // ......
}
           

是以從此處就可以看到,

PropertyEditor

ConversionService

的差别和聯系。

SimpleTypeConverter

不在特定目标對象上運作的

TypeConverter

接口的簡單實作。這是使用完整的

BeanWrapperImpl

執行個體來實作任意類型轉換需求的替代方法,同時使用相同的轉換算法(包括委托給

PropertyEditor和ConversionService

)。

public class SimpleTypeConverter extends TypeConverterSupport {

	public SimpleTypeConverter() {
		this.typeConverterDelegate = new TypeConverterDelegate(this);
		registerDefaultEditors();
	}

}
           

SimpleTypeConverter

經常會被作為預設實作。

PropertyEditor用于字元串到其它對象的轉換,由于其局限性,spring提供了converter接口,由ConversionService來調用對外提供服務,而TypeConverter綜合了上述兩種轉換方式,交由TypeConverterDelegate來進行轉換。

TypeConverterDelegater先使用PropertyEditor轉換器器轉換,如果沒找到對應的轉換器器,會⽤ConversionService來進⾏行行對象轉換

總結

1.

Spring

使用

ConversionService

來convert各種類型.預設提供的是

DefaultConversionService

.同時它實作了

ConverterRegistry

接口,是以也可以添加你自定義的converter.

2.Spring提供了3種converter接口,分别是

Converter

,

ConverterFactory

GenericConverter

.一般用于1:1, 1:N, N:N的source->target類型轉化.

3.在

DefaultConversionService

内部3種converter都會轉化成

GenericConverter

放到靜态内部類Converters中.

4.接口

GenericConverter

的内部類

ConvertiblePair

是source的class與target的Class的封裝。

GenericConversionService

的靜态内部類

ConvertersForPair

是多個converter對應的LinkedList的封裝。。。

GenericConversionService

的靜态内部類Converters中含有1個

Map<ConvertiblePair, ConvertersForPair>

用來儲存所有converter.

1個

GenericConverter

可以對應N個

ConvertiblePair

,1個

ConvertiblePair

對應的

ConvertersForPair

中也可以有N個

GenericConverter

.

Convertible:可轉換的

Spring為何要使用ConversionService替代PropertyEditor

此處總結三個原因,供給大家參考:

  1. ConversionService

    功能更強大,支援的類型轉換範圍更廣。

    1. 相比

    PropertyEditor

    隻提供

    String<->Object

    的轉換,

    ConversionService

    能夠提供任意

    Object<->Object

    的轉換。
  2. ConverterFactory

    支援一整個class hierarchy的轉換(也就是多态),

    PropertyEditor

    則不行
  3. Java Bean這個規範最初是和Java GUI(Swing)一起誕生的,PropertyEditor接口裡有大量和GUI相關的方法,顯然已經過時了。

    1. Java Bean和POJO不是一個概念,

    Java Bean

    不僅有

    getter、setter

    ,還有一系列和

    Java GUI

    配套的東西。

關注A哥

Author A哥(YourBatman)
個人站點 www.yourbatman.cn
E-mail [email protected]
微 信 fsx641385712

活躍平台

【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥
公衆号 BAT的烏托邦(ID:BAT-utopia)
知識星球 BAT的烏托邦
每日文章推薦 每日文章推薦
【小家Spring】聊聊Spring中的資料轉換:Converter、ConversionService、TypeConverter、PropertyEditor關注A哥

繼續閱讀