Spring Type Conversion(Spring類型轉換)

Spring Type Conversion(Spring類型轉換)

1:概述:

Spring3引入了core.convert包,提供了通用類型轉換系統,定義了實現類型轉換和運行時執行類型的SPIjava

Spring3.0以前,提供的PropertyEditor來將外部化bean屬性值字符串轉換成必需的實現類型。spring

2:Converter SPI

  

 
/**
 * A converter converts a source object of type {@code S} to a target of type {@code T}.
 *
 * <p>Implementations of this interface are thread-safe and can be shared.
 *
 * <p>Implementations may additionally implement {@link ConditionalConverter}.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <S> the source type
 * @param <T> the target type
 */
@FunctionalInterface
public interface Converter<S, T> {
​
    /**
     * Convert the source object of type {@code S} to target type {@code T}.
     * @param source the source object to convert, which must be an instance of {@code S} (never {@code null})
     * @return the converted object, which must be an instance of {@code T} (potentially {@code null})
     * @throws IllegalArgumentException if the source cannot be converted to the desired target type
     */
    @Nullable
    T convert(S source);
​
}

 

實現自定義的類型轉換能夠實現Converter接口。可是若是S是集合或者數組轉換爲T的集合或者數組,數組

建議參考諸如ArrayToCollectionConverter實現。前提是已經註冊了委託數組或集合轉換器。例如,安全

DefaultConversionService實現。多線程

Converter.convert(S source)中source確保不能爲null,不然轉換器可能拋出異常若是轉換失敗。具體mvc

說,應該會拋出IllegalArgumentException報告不合理的轉換源。確保Converter實現是線程安全框架

core.convert.support包下,註冊了常見了類型轉換器。例如:ide

/**
 * Converts from a String any JDK-standard Number implementation.
 *
 * <p>Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
 * delegates to {@link NumberUtils#parseNumber(String, Class)} to perform the conversion.
 *
 * @author Keith Donald
 * @since 3.0
 * @see java.lang.Byte
 * @see java.lang.Short
 * @see java.lang.Integer
 * @see java.lang.Long
 * @see java.math.BigInteger
 * @see java.lang.Float
 * @see java.lang.Double
 * @see java.math.BigDecimal
 * @see NumberUtils
 */
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
​
    @Override
    public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToNumber<>(targetType);
    }
​
​
    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
        public T convert(String source) {
            if (source.isEmpty()) {
                return null;
            }
            return NumberUtils.parseNumber(source, this.targetType);
        }
    }
​
}
​

 

3:ConverterFactory

當你須要集中整理類層次結構的類型轉換器,可使用ConverterFactory。例如StringToNumberConverterFactory,this

該接口定義以下,當你須要範圍轉換器,能夠轉換這些對象從S類型轉換成R的子類型。使用該接口spa

/**
 * A factory for "ranged" converters that can convert objects from S to subtypes of R.
 *
 * <p>Implementations may additionally implement {@link ConditionalConverter}.
 *
 * @author Keith Donald
 * @since 3.0
 * @see ConditionalConverter
 * @param <S> the source type converters created by this factory can convert from
 * @param <R> the target range (or base) type converters created by this factory can convert to;
 * for example {@link Number} for a set of number subtypes.
 */
public interface ConverterFactory<S, R> {
​
    /**
     * Get the converter to convert from S to target type T, where T is also an instance of R.
     * @param <T> the target type
     * @param targetType the target type to convert to
     * @return a converter from S to T
     */
    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
​
}
/**
 * Converts from a String any JDK-standard Number implementation.
 *
 * <p>Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
 * delegates to {@link NumberUtils#parseNumber(String, Class)} to perform the conversion.
 *
 * @author Keith Donald
 * @since 3.0
 * @see java.lang.Byte
 * @see java.lang.Short
 * @see java.lang.Integer
 * @see java.lang.Long
 * @see java.math.BigInteger
 * @see java.lang.Float
 * @see java.lang.Double
 * @see java.math.BigDecimal
 * @see NumberUtils
 */
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
​
    @Override
    public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToNumber<>(targetType);
    }
​
​
    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
        public T convert(String source) {
            if (source.isEmpty()) {
                return null;
            }
            return NumberUtils.parseNumber(source, this.targetType);
        }
    }
​
}

4:GenericConverter

GenericConverter提供多種源和目標類型之間轉換,比Converter更靈活可是對類型要求不高。它提供了實現

轉換邏輯的源和目標上下文。 這樣的上下文容許類型轉換由字段註釋或在字段簽名上聲明的通用信息驅動。接口

以下:

package org.springframework.core.convert.converter;
​
public interface GenericConverter {
​
    public Set<ConvertiblePair> getConvertibleTypes();
​
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConvertiblePair持有轉換源和目標類型對convert(Object, TypeDescriptor, TypeDescriptor)

源TypeDescriptor提供對保存正在轉換的值的源字段的訪問。 目標TypeDescriptor提供對要設置轉換值的目標字段的訪問。TypeDescriptor類是關於要轉換類型的上下文

一個好的實例是GenericConverter在Java數組和集合之間轉換。例如ArrayToCollectionConverter

注意

由於GenericConverter是一個更復雜的SPI接口,因此只有在須要時才應該使用它.喜歡Converter或ConverterFactory以知足基本的類型轉換需求。

5:ConditionalGenericConverter

該接口是一個帶有判斷條件的類型轉換器。該接口是GenericConverterConditionalConverter的組合。

/**
 * A {@link GenericConverter} that may conditionally execute based on attributes
 * of the {@code source} and {@code target} {@link TypeDescriptor}.
 *
 * <p>See {@link ConditionalConverter} for details.
 *
 * @author Keith Donald
 * @author Phillip Webb
 * @since 3.0
 * @see GenericConverter
 * @see ConditionalConverter
 */
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
​
}/**
 * A {@link GenericConverter} that may conditionally execute based on attributes
 * of the {@code source} and {@code target} {@link TypeDescriptor}.
 *
 * <p>See {@link ConditionalConverter} for details.
 *
 * @author Keith Donald
 * @author Phillip Webb
 * @since 3.0
 * @see GenericConverter
 * @see ConditionalConverter
 */
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
​
}

ConditionalGenericConverter 的一個好示例是StringToCollectionConverter

/**
 * Converts a comma-delimited String to a Collection.
 * If the target collection element type is declared, only matches if
 * {@code String.class} can be converted to it.
 *
 * @author Keith Donald
 * @author Juergen Hoeller
 * @since 3.0
 */
final class StringToCollectionConverter implements ConditionalGenericConverter {
​
    private final ConversionService conversionService;
​
​
    public StringToCollectionConverter(ConversionService conversionService) {
        this.conversionService = conversionService;
    }
​
​
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, Collection.class));
    }
​
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return (targetType.getElementTypeDescriptor() == null ||
                this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()));
    }
​
    @Override
    @Nullable
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (source == null) {
            return null;
        }
        String string = (String) source;
​
        String[] fields = StringUtils.commaDelimitedListToStringArray(string);
        TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
        Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
                (elementDesc != null ? elementDesc.getType() : null), fields.length);
​
        if (elementDesc == null) {
            for (String field : fields) {
                target.add(field.trim());
            }
        }
        else {
            for (String field : fields) {
                Object targetElement = this.conversionService.convert(field.trim(), sourceType, elementDesc);
                target.add(targetElement);
            }
        }
        return target;
    }
​
}

6:ConversionService API

ConversionService定義了一個統一的API,用於在運行時執行類型轉換邏輯. 轉換器一般在如下Facade接口後面執行。

package org.springframework.core.convert;
​
public interface ConversionService {
​
    boolean canConvert(Class<?> sourceType, Class<?> targetType);
​
    <T> T convert(Object source, Class<T> targetType);
​
    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
​
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
​
}

大多數ConversionService實現,一樣也實現了ConverterRegistry,該接口提供了SPI來註冊Converters.

在內部,ConversionService的實現,容器委託它來註冊轉換器來執行轉換邏輯。

core.convert.support提供一個強大的ConversionService實現,該實現是GenericConversionSer

,它適用於大多數轉換器環境實現。ConversionServiceFactory 來建立普通的ConversionService

配置。

7:配置ConversionService

ConversionService被設計成無狀態對象,在容器啓動時被實例化,在多線程間進行共享(線程安全)。

在Spring應用中,能夠自定義類型轉換器。當須要框架進行類型轉換時,Spring會選擇合適的類型轉換器

使用。你也能夠注入ConversionService到beans或者直接調用。

注意

若是沒有ConversionService註冊到Spring容器,基於的PropertyEditor實現的類型轉換會被使用。

使用以下的方式,註冊默認ConversionService進Spring容器中:

public class ConvertersConfiguration {
​
    @Bean(name = "conversionService")
    public ConversionServiceFactoryBean conversionServiceFactory() {
        ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        return conversionServiceFactoryBean;
    }
}
 

默認的ConversionService能夠在字符串,數字,枚舉,集合,映射和其餘常見類型之間進行轉換。要使用您本身的自定義轉換器補充或覆蓋默認轉換器,請設置converter屬性.屬性值能夠實現任何Converter,ConverterFactory或GenericConverter接口。默認ConversionService實現是DefaultConversionService

 
public class ConvertersConfiguration {
​
    @Bean(name = "conversionService")
    public ConversionServiceFactoryBean conversionServiceFactory() {
        ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        //實現自定義的類型轉換器
        conversionServiceFactoryBean.setConverters(Collections.singleton(new StringToDateConverter()));
        return conversionServiceFactoryBean;
    }
}
​

也可使用ConversionService在Spring MVC應用中,參考WebMvcConfigurationSupport類,該類方法

addFormatters(FormatterRegistry registry)能夠註冊自定義的converters

在某些狀況,但願在類型轉換期間須要格式化,參考FormatterRegistry

在程序中使用ConversionService

@Service
public class MyService {
​
    @Autowired
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }
​
    public void doIt() {
        this.conversionService.convert(...)
    }
}

8:Spring域屬性格式化

core.convert是一個通用的類型轉換系統.它提供了統一的ConversionService API以及強類型轉換器SPI,用於實現從一種類型到另外一種類型的轉換邏輯.Spring容器使用這個系統來綁定bean屬性值。額外的,還要SpEL

DataBinderSpring3引入了Formatter SPI來實現格式化屬性值。ConversionService爲兩個SPI提供統一的類型轉換API。

(1):Formatter SPI
/**
 * Formats objects of type T.
 * A Formatter is both a Printer <i>and</i> a Parser for an object type.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Formatter formats
 */
public interface Formatter<T> extends Printer<T>, Parser<T> {
​
}
​
/**
 * Parses text strings to produce instances of T.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Parser produces
 */
@FunctionalInterface
public interface Parser<T> {
​
    /**
     * Parse a text String to produce a T.
     * @param text the text string
     * @param locale the current user locale
     * @return an instance of T
     * @throws ParseException when a parse exception occurs in a java.text parsing library
     * @throws IllegalArgumentException when a parse exception occurs
     */
    T parse(String text, Locale locale) throws ParseException;
​
}
​
​
​
/**
 * Prints objects of type T for display.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <T> the type of object this Printer prints
 */
@FunctionalInterface
public interface Printer<T> {
​
    /**
     * Print the object of type T for display.
     * @param object the instance to print
     * @param locale the current user locale
     * @return the printed text string
     */
    String print(T object, Locale locale);
​
}
(2):Annotation-Driven Formatting

域格式化能夠經過域類型或者註解配置.爲了綁定註解在一個Formatter,實現AnnotationFormatterFactory.

 
package org.springframework.format;
​
/**
 * A factory that creates formatters to format values of fields annotated with a particular
 * {@link Annotation}.
 *
 * <p>For example, a {@code DateTimeFormatAnnotationFormatterFactory} might create a formatter
 * that formats {@code Date} values set on fields annotated with {@code @DateTimeFormat}.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <A> the annotation type that should trigger formatting
 */
public interface AnnotationFormatterFactory<A extends Annotation> {
​
    Set<Class<?>> getFieldTypes();
​
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
​
    Parser<?> getParser(A annotation, Class<?> fieldType);
}
例如實現NumberFormatAnnotationFormatterFactory,綁定@NumberFormat註解到Formatter。


public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
        implements AnnotationFormatterFactory<NumberFormat> {
​
    @Override
    public Set<Class<?>> getFieldTypes() {
        return NumberUtils.STANDARD_NUMBER_TYPES;
    }
​
    @Override
    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation);
    }
​
    @Override
    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation);
    }
​
​
    private Formatter<Number> configureFormatterFrom(NumberFormat annotation) {
        String pattern = resolveEmbeddedValue(annotation.pattern());
        if (StringUtils.hasLength(pattern)) {
            return new NumberStyleFormatter(pattern);
        }
        else {
            Style style = annotation.style();
            if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            }
            else if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            }
            else {
                return new NumberStyleFormatter();
            }
        }
    }
}
(3):格式化註解API

DateTimeFormatNumberFormat

(4):FormatterRegistry SPI

FormatterRegistry是用來註冊formatters 和 convertersSPIFormattingConversionService

FormatterRegistry 一個實現,能夠支持大多數環境。能夠經過FormattingConversionServiceFactoryBean

來配置。也能夠經過Spring's DataBinderSpEL

package org.springframework.format;
​
public interface FormatterRegistry extends ConverterRegistry {
​
    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
​
    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
​
    void addFormatterForFieldType(Formatter<?> formatter);
​
    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}
(5):FormatterRegistrar SPI

FormatterRegistrar是經過FormatterRegistry註冊formatters和converters的SPI

package org.springframework.format;
​
public interface FormatterRegistrar {
​
    void registerFormatters(FormatterRegistry registry);
}

9:在Spring MVC配置Formatting

Configuration
@Slf4j
public class WebConfiguration extends WebMvcConfigurationSupport {
​
​
    @Override
    protected void addFormatters(FormatterRegistry registry) {
       registry.addConverter(new StringToDateConverter());
    }
}

10:配置全局的Date和時間Format

JodaTimeFormatterRegistrarDateFormatterRegistrar,使用Joda須要引入joda庫

配置以下:

@Configuration
public class AppConfig {
​
    @Bean
    public FormattingConversionService conversionService() {
​
        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
​
        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
​
        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
​
        return conversionService;
    }
}

注意

Joda-Time提供不一樣類型表示日期date,time,datetime,須要經過JodaTimeFormatterRegistrar進行

註冊。或者使用DateTimeFormatterFactoryBean來進行建立formatters。

若是您使用Spring MVC,請記住明確配置使用的轉換服務.對於基於Java的@Configuration,這意味着擴展WebMvcConfigurationSupport類並覆蓋mvcConversionService()方法.對於XML,您應該使用mvc:annotation-driven元素的conversion-service屬性。 有關詳細信息,請參閱轉換和格式。

相關文章
相關標籤/搜索