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属性。 有关详细信息,请参阅转换和格式。

相关文章
相关标签/搜索