每篇一句
表像大都這樣:出力的不掙錢,掙錢的不出力
前言
前面聊了
HttpMessageConverter
,它的名稱叫
消息轉換器
,是以它面向的是消息體,和
Http
強相關,是以該接口所在的包為:
org.springframework.http.converter
資料轉換,顧名思義就是資料類型之間的轉換,但是對于資料轉換,有的是可以進行轉化的,例如字元串轉整型,但是有些資料類型之間是不能進行轉換的,例如從“aaa”字元串到整型的轉換。
不同的架構,肯定都有自己的資料轉換的實作,比如MyBatis、Hibernate等這些轉換器都是必備的。然後作為這麼強大的Spring,它肯定也缺席不了。
org.springframework.core.convert.converter.Converter
它位于核心包中,是以它不僅僅運用于
Spring MVC
等web環境,比如spring-jdbc等都是有使用到的~
資料轉換在架構設計中是非常重要的一環,它能讓你的架構更普适,更通用,更自動化,解決的問題更多,是以我個人認為,了解Spring資料轉換的設計思想,以及它的常用實作是非常有必要的。
若是源生Servlet開發,你能想象到那種低下的開發效率嗎以及漫天遍地的“垃圾代碼”嗎?
關于Spring中的資料轉換,首先需要了解兩大主要分支:
-
:是Spring中最為簡單的一個接口。位于包:Converter<S, T>
。 相關的頂層接口(類)有:org.springframework.core.convert.converter
、ConditionalConverter
、GenericConverter
、ConverterFactory
、ConvertingComparator
ConverterRegistry
-
:用于類型轉換的服務接口。這是進入轉換系統的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
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
ConditionalConverter
根據source和target來做條件判斷,進而可以判斷哪個轉換器生效,哪個不生效之類的。
// @since 3.2 出現稍微較晚
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
也是一個Spring的基礎類(類似
org.springframework.core.convert.TypeDescriptor
)這種,若有需要我們平時也可以使用它。 它能夠把
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
的繼承樹:
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
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);
}
它的實作類不多:
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
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
使用
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);
}
兩大分支。
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);
}
GenericConversionService
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;
}
...
}
絕大多數情況下,我們不會直接使用
GenericConversionService
,而是使用它的子類
DefaultConversionService
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
…也就是properties、el表達式裡、spring-jdbc資料封裝的類型轉換裡都有應用
org.springframework.expression.TypeConverter
關于
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内置的一些屬性編輯器們:
這些PropertyEditors都位于 org.springframework.beans.propertyeditors
包中,大多是都是由BeanWrapperImpl注冊,當屬性編輯器以某種方式進行配置時,開發者仍可以注冊自定義的變體用于覆寫預設的變量
應用的場景描述:
在基于xml的配置中,我們往往通過字面值為Bean各種類型的屬性提供設定值:不管是double類型還是int類型,在配置檔案中都對應字元串類型的字面值。
BeanWrapper
填充Bean屬性時如何将這個字面值轉換為對應的double或int等内部類型呢?我們可以隐約地感覺到一定有一個轉換器在其中起作用,這個轉換器就是屬性編輯器。
Spring MVC架構使用多種PropertyEditors分析HTTP請求的各種參數
有的小夥伴可能會問:既然有了
PropertyEditor
,那為何還需要有Converter呢?其實是因為Java原生的PropertyEditor存在以下兩點不足:
- 隻能用于字元串和Java對象的轉換,不适用于任意兩個Java類型之間的轉換;
- 對源對象及目标對象所在的上下文資訊(如注解、所在宿主類的結構等)不敏感,在類型轉換時不能利用這些上下文資訊實施進階轉換邏輯。
鑒于此,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
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");
}
}
繼承樹如下:
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個可以對應N個
GenericConverter
,1個
ConvertiblePair
對應的
ConvertiblePair
中也可以有N個
ConvertersForPair
GenericConverter
.
Convertible:可轉換的
Spring為何要使用ConversionService替代PropertyEditor
此處總結三個原因,供給大家參考:
-
ConversionService
功能更強大,支援的類型轉換範圍更廣。
1. 相比
隻提供PropertyEditor
的轉換,String<->Object
能夠提供任意ConversionService
的轉換。Object<->Object
-
支援一整個class hierarchy的轉換(也就是多态),ConverterFactory
則不行PropertyEditor
-
Java Bean這個規範最初是和Java GUI(Swing)一起誕生的,PropertyEditor接口裡有大量和GUI相關的方法,顯然已經過時了。
1. Java Bean和POJO不是一個概念,
不僅有Java Bean
,還有一系列和getter、setter
配套的東西。Java GUI
關注A哥
Author | A哥(YourBatman) |
---|---|
個人站點 | www.yourbatman.cn |
[email protected] | |
微 信 | fsx641385712 |
| |
公衆号 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |