歡迎光臨
每天分享高質量文章

【死磕 Spring】—– IOC 之深入分析 Bean 的型別轉換體系

我們知道不管 bean 物件裡面的屬性時什麼型別,他們都是透過 XML 、Properties 或者其他方式來配置這些屬性物件型別的。在 Spring 容器載入過程中,這些屬性都是以 String 型別載入進容器的,但是最終都需要將這些 String 型別的屬性轉換 Bean 物件屬性所對應真正的型別,要想完成這種由字串到具體物件的轉換,就需要這種轉換規則相關的資訊,而這些資訊以及轉換過程由 Spring 型別轉換體系來完成。

我們依然以 xml 為例,在 Spring 容器載入階段,容器將 xml 檔案中定義的  解析為 BeanDefinition,BeanDefinition 中儲存著我們定義一個 bean 需要的所有資訊,包括屬性,這些屬性是以 String 型別的儲存的。當使用者觸發 Bean 實體化階段時,Spring 容器會將這些屬性轉換為這些屬性真正對應的型別。我們知道在 bean 實體化階段,屬性的註入是在實體化 bean 階段的屬性註入階段,即 populateBean() 方法。在 populateBean() 中會將 BeanDefinition 中定義的屬性值翻譯為 PropertyValue 然後呼叫 applyPropertyValues() 進行屬性應用。其中 PropertyValue 用於儲存單個 bean 屬性的資訊和值的物件。在 applyPropertyValues() 中會呼叫 convertForProperty() 進行屬性轉換,如下:

  1.    private Object convertForProperty(
  2.            @Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
  3.  
  4.        if (converter instanceof BeanWrapperImpl) {
  5.            return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
  6.        }
  7.        else {
  8.            PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
  9.            MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
  10.            return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
  11.        }
  12.    }

若 TypeConverter 為 BeanWrapperImpl 型別,則使用 BeanWrapperImpl 來進行型別轉換,這裡主要是因為 BeanWrapperImpl 實現了 PropertyEditorRegistry 介面。否則則呼叫 TypeConverter 的 convertIfNecessary()進行型別轉換。TypeConverter 是定義型別轉換方法的介面,通常情況下與 PropertyEditorRegistry 配合使用實現型別轉換。關於 BeanWrapperImpl 小編後續專門出文分析它。

convertIfNecessary() 的實現者有兩個:DataBinder 和 TypeConverterSupport ,其中 DataBinder 主要用於引數系結(熟悉 Spring MVC 的都應該知道這個類),TypeConverterSupport 則是 TypeConverter 的基本實現,使用的是 package-private 策略。 所以這裡我們只需要關註 TypeConverterSupport 的 convertIfNecessary(),如下:

  1.    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam)
  2.            throws TypeMismatchException {
  3.  
  4.        return doConvert(value, requiredType, methodParam, null);
  5.    }
  6.  
  7.    private <T> T doConvert(@Nullable Object value,@Nullable Class<T> requiredType,
  8.            @Nullable MethodParameter methodParam, @Nullable Field field) throws TypeMismatchException {
  9.  
  10.        Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
  11.        try {
  12.            if (field != null) {
  13.                return this.typeConverterDelegate.convertIfNecessary(value, requiredType, field);
  14.            }
  15.            else {
  16.                return this.typeConverterDelegate.convertIfNecessary(value, requiredType, methodParam);
  17.            }
  18.        }
  19.        catch (ConverterNotFoundException | IllegalStateException ex) {
  20.            throw new ConversionNotSupportedException(value, requiredType, ex);
  21.        }
  22.        catch (ConversionException | IllegalArgumentException ex) {
  23.            throw new TypeMismatchException(value, requiredType, ex);
  24.        }
  25.    }

我們一直往下跟會跟蹤到 TypeConverterDelegate 的 convertIfNecessary() ,會發現如下程式碼段:

如果沒有自定義的編輯器則使用 ConversionService 。ConversionService 是 Spring 自 3 後推出來用來替代 PropertyEditor 轉換樣式的轉換體系,介面定義如下:

  1. public interface ConversionService {
  2.  
  3.    boolean canConvert(@Nullable Class> sourceType, Class> targetType);
  4.  
  5.    boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
  6.  
  7.    @Nullable
  8.    <T> T convert(@Nullable Object source, Class<T> targetType);
  9.  
  10.    @Nullable
  11.    Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
  12. }

其 UML 類圖如下:

  • ConfigurableConversionService:ConversionService 的配置介面,繼承 ConversionService 和 ConverterRegistry 兩個介面,用於合併他們兩者的操作,以便於透過 add 和 remove 的方式新增和刪除轉換器。
  • GenericConversionService:ConversionService 介面的基礎實現,適用於大部分條件下的轉換工作,透過 ConfigurableConversionService 介面間接地將 ConverterRegistry 實現為註冊 API 。
  • DefaultConversionService:ConversionService 介面的預設實現,適用於大部分條件下的轉換工作。

回歸到 convertIfNecessary(),在該方法中如果沒有自定義的屬性編輯器則呼叫 ConversionService 介面的 convert(),方法定義如下:

  1. Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
  • source:要轉換的源物件,可以為 null
  • sourceType:source 的型別的背景關係,如果 source 為 null,則可以為 null
  • targetType:source 要轉換的型別的背景關係。

convert() 將給定的源物件 source 轉換為指定的 targetType。TypeDescriptors 提供有關發生轉換的源位置和標的位置的附加背景關係,通常是物件欄位或屬性位置。方法由子類 GenericConversionService 實現:

  1.    public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
  2.        // 刪掉 if ,其實就是上面的 null 判斷
  3.        GenericConverter converter = getConverter(sourceType, targetType);
  4.        if (converter != null) {
  5.            Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
  6.            return handleResult(sourceType, targetType, result);
  7.        }
  8.        return handleConverterNotFound(source, sourceType, targetType);
  9.    }

首先根據 sourceType 和 targetType 呼叫 getConverter() 獲取 GenericConverter 物件 converter ,如果 converter 為 null,則呼叫 handleConverterNotFound(),否則呼叫 handleResult() 方法。 getConverter() 如下:

  1.    protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
  2.        ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
  3.        GenericConverter converter = this.converterCache.get(key);
  4.        if (converter != null) {
  5.            return (converter != NO_MATCH ? converter : null);
  6.        }
  7.  
  8.        converter = this.converters.find(sourceType, targetType);
  9.        if (converter == null) {
  10.            converter = getDefaultConverter(sourceType, targetType);
  11.        }
  12.  
  13.        if (converter != null) {
  14.            this.converterCache.put(key, converter);
  15.            return converter;
  16.        }
  17.  
  18.        this.converterCache.put(key, NO_MATCH);
  19.        return null;
  20.    }

這段程式碼意圖非常明確,從 converterCache 快取中獲取,如果存在傳回,否則從 converters 中獲取,然後加入到 converterCache 快取中。converterCache 和 converters 是 GenericConversionService 維護的兩個很重要的物件,其中 converterCache 用於儲存 GenericConverter ,converters 物件為 GenericConversionService 的內部類。

  1. private final Converters converters = new Converters();
  2. private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentReferenceHashMap<>(64);

Converters 用於管理所有註冊的轉換器,其內部維護一個 Set 和 Map 的資料結構用於管理轉換器,如下:

  1. private final Set<GenericConverter> globalConverters = new LinkedHashSet<>();
  2.  
  3. private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<>(36);

同時提供了相應的方法(如 add、remove)操作這兩個集合。在 getConverter() 中如果快取 converterCache 中 不存在,則呼叫 Converters 物件的 find() 方法獲取相應的 GenericConverter,如下:

  1. public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
  2.    // Search the full type hierarchy
  3.    List<Class>> sourceCandidates = getClassHierarchy(sourceType.getType());
  4.    List<Class>> targetCandidates = getClassHierarchy(targetType.getType());
  5.    for (Class> sourceCandidate : sourceCandidates) {
  6.        for (Class> targetCandidate : targetCandidates) {
  7.            ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
  8.            GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
  9.            if (converter != null) {
  10.                return converter;
  11.            }
  12.        }
  13.    }
  14.    return null;
  15. }
  16.  
  17. private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
  18.                TypeDescriptor targetType, ConvertiblePair convertiblePair) {
  19.  
  20.    // Check specifically registered converters
  21.    ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
  22.    if (convertersForPair != null) {
  23.        GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
  24.        if (converter != null) {
  25.            return converter;
  26.        }
  27.    }
  28.    // Check ConditionalConverters for a dynamic match
  29.    for (GenericConverter globalConverter : this.globalConverters) {
  30.        if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
  31.            return globalConverter;
  32.        }
  33.    }
  34.    return null;
  35. }

在 find() 中會根據 sourceType 和 targetType 去查詢 Converters 中維護的 Map 中是否包括支援的註冊型別,如果存在傳回 GenericConverter ,如果沒有存在傳回 null。

當得到 GenericConverter 後,則呼叫其 convert() 進行型別轉換。

  1. Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

到這裡我們就可以得到 bean 屬性定義的真正型別了。

GenericConverter 介面

GenericConverter 是一個轉換介面,一個用於在兩種或更多種型別之間轉換的通用型轉換器介面。它是 Converter SPI 體系中最靈活的,也是最複雜的介面,靈活性在於 GenericConverter 可以支援在多個源/標的型別對之間進行轉換,同時也可以在型別轉換過程中訪問源/標的欄位背景關係。由於該介面足夠複雜,所有當更簡單的 Converter 或 ConverterFactory 介面足夠使用時,通常不應使用此介面。其定義如下:

  1. public interface GenericConverter {
  2.  
  3.    @Nullable
  4.    Set<ConvertiblePair> getConvertibleTypes();
  5.  
  6.    @Nullable
  7.    Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
  8. }

GenericConverter 的子類有這麼多(看類名就知道是幹嘛的了):

我們看一個子類的實現 StringToArrayConverter,該子類將逗號分隔的 String 轉換為 Array。如下:

  1.    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
  2.        if (source == null) {
  3.            return null;
  4.        }
  5.        String string = (String) source;
  6.        String[] fields = StringUtils.commaDelimitedListToStringArray(string);
  7.        TypeDescriptor targetElementType = targetType.getElementTypeDescriptor();
  8.        Assert.state(targetElementType != null, "No target element type");
  9.        Object target = Array.newInstance(targetElementType.getType(), fields.length);
  10.        for (int i = 0; i < fields.length; i++) {
  11.            String sourceElement = fields[i];
  12.            Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetElementType);
  13.            Array.set(target, i, targetElement);
  14.        }
  15.        return target;
  16.    }

在型別轉換體系中,Spring 提供了非常多的型別轉換器,除了上面的 GenericConverter,還有 Converter、ConditionalConverter、ConverterFactory。

Converter

Converter 是一個將 S 型別的源物件轉換為 T 型別的標的物件的轉換器。該介面是執行緒安全的,所以可以共享。

  1. public interface Converter<S, T> {
  2.    @Nullable
  3.    T convert(S source);
  4. }

子類如下:

ConditionalConverter

ConditionalConverter 介面用於表示有條件的型別轉換,透過轉入的sourceType 與 targetType 判斷轉換能否匹配,只有可匹配的轉換才會呼叫convert 方法進行轉換,如下:

  1. public interface ConditionalConverter {
  2.    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
  3. }

ConditionalConverter 的子類如下:

ConverterFactory

一個用於“遠端”轉換的轉換工廠,可以將物件從 S 轉換為 R 的子型別。

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

子類如下:

四種不同的轉換器承載著不同的轉換過程:

  • Converter:用於 1:1 的 source -> target 型別轉換
  • ConverterFactory:用於 1:N 的 source -> target 型別轉換
  • GenericConverter用於 N:N 的 source -> target 型別轉換
  • ConditionalConverter:有條件的 source -> target 型別轉換

GenericConversionService

轉換器介紹完了,我們再次回歸到 ConversionService 介面中去,該介面定義了兩類方法 canConvert() 和 convert(),其中 canConvert() 用於判 sourceType 能否轉成 targetType ,而 convert() 用於將 source 轉成轉入的 TargetType 型別實體。這兩類方法都是在 GenericConversionService 中實現。類 GenericConversionService 實現 ConfigurableConversionService 介面,而 ConfigurableConversionService 介面繼承 ConversionService 和 ConverterRegistry。ConverterRegistry 提供了型別轉換器的管理功能,他提供了四個 add 和 一個 remove 方法,支援註冊/刪除相應的型別轉換器。GenericConversionService 作為一個基礎實現類,它即支援了不同型別之間的轉換,也對各型別轉換器進行管理,主要是透過一個 Map 型別的 converterCache 和一個內部類 Converters。在上面已經分析了 GenericConversionService 執行型別轉換的過程 cover(),下麵我們就一個 addConverter() 來看看它是如何完成轉換器的註入工作的。

  1.    public void addConverter(Converter, ?> converter) {
  2.        ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
  3.        if (typeInfo == null && converter instanceof DecoratingProxy) {
  4.            typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class);
  5.        }
  6.        if (typeInfo == null) {
  7.            throw new IllegalArgumentException("Unable to determine source type and target type for your " +
  8.                    "Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
  9.        }
  10.        addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
  11.    }

首先根據 converter 獲取 ResolvableType,然後將其與 converter 封裝成一個 ConverterAdapter 實體,最後呼叫 addConverter()。ResolvableType 用於封裝 Java 的型別。ConverterAdapter 則是 Converter 的一個配接器, 它實現了 GenericConverter 和 ConditionalConverter 兩個型別轉換器。

addConverter() 如下:

  1. public void addConverter(GenericConverter converter) {
  2.    this.converters.add(converter);
  3.    invalidateCache();
  4. }

直接呼叫內部類 Converters 的 add() 方法,如下:

  1.        public void add(GenericConverter converter) {
  2.            Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
  3.            if (convertibleTypes == null) {
  4.                Assert.state(converter instanceof ConditionalConverter,
  5.                        "Only conditional converters may return null convertible types");
  6.                this.globalConverters.add(converter);
  7.            }
  8.            else {
  9.                for (ConvertiblePair convertiblePair : convertibleTypes) {
  10.                    ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
  11.                    convertersForPair.add(converter);
  12.                }
  13.            }
  14.        }

首先呼叫 getConvertibleTypes() 獲取 ConvertiblePair 集合,如果為空,則加入到 globalConverters 集合中,否則透過迭代的方式依次新增。ConvertiblePair 為 source-to-targer 的持有者,它持有 source 和 target 的 class 型別,如下:

  1. final class ConvertiblePair {
  2.    private final Class> sourceType;
  3.    private final Class> targetType;
  4.  
  5.    // 其他程式碼  
  6. }

在迭代過程中會根據 ConvertiblePair 獲取相應的 ConvertersForPair ,然後 converter 轉換器加入其中,ConvertiblePair 用於管理使用特定GenericConverter.ConvertiblePair 註冊的轉換器。如下:

  1.    private static class ConvertersForPair {
  2.  
  3.       private final LinkedList<GenericConverter> converters = new LinkedList<>();
  4.  
  5.        public void add(GenericConverter converter) {
  6.            this.converters.addFirst(converter);
  7.        }
  8.  
  9.        @Nullable
  10.        public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
  11.            for (GenericConverter converter : this.converters) {
  12.                if (!(converter instanceof ConditionalGenericConverter) ||
  13.                        ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) {
  14.                    return converter;
  15.                }
  16.            }
  17.            return null;
  18.        }
  19.    }

其實內部就是維護一個 LinkedList 集合。他內部有兩個方法: add() 和 getConverter(),實現較為簡單,這裡就不多介紹了。

DefaultConversionService

DefaultConversionService 是 ConversionService 的預設實現,它繼承 GenericConversionService,GenericConversionService 主要用於轉換器的註冊和呼叫,DefaultConversionService 則是為 ConversionService 體系提供一些預設的轉換器。在 DefaultConversionService 構造方法中就會新增預設的 Converter ,如下:

  1.    public DefaultConversionService() {
  2.        addDefaultConverters(this);
  3.    }
  4.  
  5.        public static void addDefaultConverters(ConverterRegistry converterRegistry) {
  6.        addScalarConverters(converterRegistry);
  7.        addCollectionConverters(converterRegistry);
  8.  
  9.        converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
  10.        if (jsr310Available) {
  11.            Jsr310ConverterRegistrar.registerJsr310Converters(converterRegistry);
  12.        }
  13.  
  14.        converterRegistry.addConverter(new ObjectToObjectConverter());
  15.        converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
  16.        converterRegistry.addConverter(new FallbackObjectToStringConverter());
  17.        if (javaUtilOptionalClassAvailable) {
  18.            converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
  19.        }
  20.    }

當然它還提供了一些其他的方法如 addCollectionConverters()、 addScalarConverters() 用於註冊其他型別的轉換器。

至此,從 bean 屬性的轉換,到 Spring ConversionService 體系的轉換器 Converter 以及轉換器的管理都介紹完畢了,下篇我們將分析如何利用 ConversionService 實現自定義型別轉換器。

贊(0)

分享創造快樂