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}.
*
* Implementations of this interface are thread-safe and can be shared.
*
*
Implementations may additionally implement {@link ConditionalConverter}.
*
* @author Keith Donald
* @since 3.0
* @param the source type
* @param the target type
*/
@FunctionalInterface
public interface Converter {
/**
* 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.
*
* 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 {
@Override
public Converter getConverter(Class targetType) {
return new StringToNumber<>(targetType);
}
private static final class StringToNumber implements Converter {
private final Class targetType;
public StringToNumber(Class 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.
*
* Implementations may additionally implement {@link ConditionalConverter}.
*
* @author Keith Donald
* @since 3.0
* @see ConditionalConverter
* @param the source type converters created by this factory can convert from
* @param 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 {
/**
* Get the converter to convert from S to target type T, where T is also an instance of R.
* @param the target type
* @param targetType the target type to convert to
* @return a converter from S to T
*/
Converter getConverter(Class targetType);
}
/**
* Converts from a String any JDK-standard Number implementation.
*
* 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 {
@Override
public Converter getConverter(Class targetType) {
return new StringToNumber<>(targetType);
}
private static final class StringToNumber implements Converter {
private final Class targetType;
public StringToNumber(Class 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 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
该接口是一个带有判断条件的类型转换器。该接口是GenericConverter和ConditionalConverter的组合。
/**
* A {@link GenericConverter} that may conditionally execute based on attributes
* of the {@code source} and {@code target} {@link TypeDescriptor}.
*
* 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 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
6:ConversionService API
ConversionService定义了一个统一的API,用于在运行时执行类型转换逻辑. 转换器通常在以下Facade接口后面执行。
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class sourceType, Class targetType);
T convert(Object source, Class 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和
DataBinder。Spring3引入了Formatter SPI来实现格式化属性值。ConversionService为两个SPI提供统一的类型转换API。
(1):Formatter SPI
/**
* Formats objects of type T.
* A Formatter is both a Printer and a Parser for an object type.
*
* @author Keith Donald
* @since 3.0
* @param the type of object this Formatter formats
*/
public interface Formatter extends Printer, Parser {
}
/**
* Parses text strings to produce instances of T.
*
* @author Keith Donald
* @since 3.0
* @param the type of object this Parser produces
*/
@FunctionalInterface
public interface Parser {
/**
* 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 the type of object this Printer prints
*/
@FunctionalInterface
public interface Printer {
/**
* 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}.
*
* 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 the annotation type that should trigger formatting
*/
public interface AnnotationFormatterFactory {
Set> getFieldTypes();
Printer getPrinter(A annotation, Class fieldType);
Parser getParser(A annotation, Class fieldType);
}
例如实现NumberFormatAnnotationFormatterFactory,绑定@NumberFormat注解到Formatter。
public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory {
@Override
public Set> getFieldTypes() {
return NumberUtils.STANDARD_NUMBER_TYPES;
}
@Override
public Printer getPrinter(NumberFormat annotation, Class fieldType) {
return configureFormatterFrom(annotation);
}
@Override
public Parser getParser(NumberFormat annotation, Class fieldType) {
return configureFormatterFrom(annotation);
}
private Formatter 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
DateTimeFormat和NumberFormat。
(4):FormatterRegistry SPI
FormatterRegistry是用来注册formatters 和 converters的SPI。FormattingConversionService
是FormatterRegistry 一个实现,可以支持大多数环境。可以通过FormattingConversionServiceFactoryBean
来配置。也可以通过Spring’s DataBinder和SpEL。
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
JodaTimeFormatterRegistrar和DateFormatterRegistrar,使用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属性。 有关详细信息,请参阅转换和格式。