Spring Type Conversion(Spring类型转换)

Spring Type Conversion(Spring类型转换)

1:概述:

Spring3引入了core.convert包,提供了通用类型转换系统,定义了实现类型转换和运行时执行类型的SPI

Spring3.0之前,提供的PropertyEditor来将外部化bean属性值字符串转换成必需的实现类型。

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,否则转换器可能抛出异常如果转换失败。具体

说,应该会抛出IllegalArgumentException报告不合理的转换源。确保Converter实现是线程安全

core.convert.support包下,注册了常见了类型转换器。例如:

/**
 * 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,

该接口定义如下,当你需要范围转换器,可以转换这些对象从S类型转换成R的子类型。使用该接口

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

原文地址:https://www.cnblogs.com/liuenyuan1996/p/11066202.html