ConversionService是Spring类型转换体系的核心接口,可以利用conversionServiceFactoryBean在Spring工厂容器中定义一个conversionService。
Spring将自动识别出ConversionService,并在bean属性配置及SpringMVC处理方法入参绑定等场合使用它进行数据转换。
SpringMVC上下文中内建了很多转换器,可以完成大多数Java类型的转换工作。
【1】常见转换器接口
Spring定义了三种类型的转换器接口,实现任意一个接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中。需要特别注意的是,这里的Convert接口与HttpMessageConverter接口截然不同,后者是用于从HTTP请求和响应转换为HTTP请求和响应的策略接口。
① Converter
:将S类型对象转换为T类型对象,该接口的实现是线程安全的、可以共享的。
@FunctionalInterface public interface Converter<S, T> { //返回T对象 @Nullable T convert(S var1); }
默认内置了如下转换器
ObjectToStringConverter (org.springframework.core.convert.support) LocalDateTimeToLocalTimeConverter in JodaTimeConverters (org.springframework.format.datetime.joda) StringToCharsetConverter (org.springframework.core.convert.support) StringToPropertiesConverter (org.springframework.core.convert.support) ConversionServiceConverter in ConvertingComparator (org.springframework.core.convert.converter) CalendarToLocalDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) OffsetDateTimeToLocalTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToCharacterConverter (org.springframework.core.convert.support) ZonedDateTimeToLocalDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) ZonedDateTimeToInstantConverter in DateTimeConverters (org.springframework.format.datetime.standard) LongToDateConverter in DateFormatterRegistrar (org.springframework.format.datetime) NumberToNumber in NumberToNumberConverterFactory (org.springframework.core.convert.support) ZonedDateTimeToLocalDateConverter in DateTimeConverters (org.springframework.format.datetime.standard) DateToLongConverter in DateFormatterRegistrar (org.springframework.format.datetime) StringToTimeZoneConverter (org.springframework.core.convert.support) DateTimeToLocalDateConverter in JodaTimeConverters (org.springframework.format.datetime.joda) ZoneIdToTimeZoneConverter (org.springframework.core.convert.support) OffsetDateTimeToInstantConverter in DateTimeConverters (org.springframework.format.datetime.standard) LocalDateTimeToLocalDateConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToBooleanConverter (org.springframework.core.convert.support) InstantToLongConverter in DateTimeConverters (org.springframework.format.datetime.standard) CalendarToLocalDateConverter in DateTimeConverters (org.springframework.format.datetime.standard) PropertiesToStringConverter (org.springframework.core.convert.support) DateTimeToDateMidnightConverter in JodaTimeConverters (org.springframework.format.datetime.joda) OffsetDateTimeToLocalDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) CalendarToReadableInstantConverter in JodaTimeConverters (org.springframework.format.datetime.joda) EnumToStringConverter (org.springframework.core.convert.support) NumberToCharacterConverter (org.springframework.core.convert.support) IntegerToEnum in IntegerToEnumConverterFactory (org.springframework.core.convert.support) LocalDateTimeToLocalDateConverter in JodaTimeConverters (org.springframework.format.datetime.joda) StringToUUIDConverter (org.springframework.core.convert.support) DateTimeToInstantConverter in JodaTimeConverters (org.springframework.format.datetime.joda) LongToReadableInstantConverter in JodaTimeConverters (org.springframework.format.datetime.joda) ZonedDateTimeToLocalTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) OffsetDateTimeToLocalDateConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToCurrencyConverter (org.springframework.core.convert.support) CalendarToInstantConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToLocaleConverter (org.springframework.core.convert.support) DateTimeToMutableDateTimeConverter in JodaTimeConverters (org.springframework.format.datetime.joda) DateTimeToDateConverter in JodaTimeConverters (org.springframework.format.datetime.joda) ZonedDateTimeToOffsetDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToNumber in StringToNumberConverterFactory (org.springframework.core.convert.support) DeserializingConverter (org.springframework.core.serializer.support) LongToCalendarConverter in DateFormatterRegistrar (org.springframework.format.datetime) DateToCalendarConverter in DateFormatterRegistrar (org.springframework.format.datetime) DateTimeToLongConverter in JodaTimeConverters (org.springframework.format.datetime.joda) DateTimeToLocalTimeConverter in JodaTimeConverters (org.springframework.format.datetime.joda) LongToInstantConverter in DateTimeConverters (org.springframework.format.datetime.standard) CharacterToNumber in CharacterToNumberFactory (org.springframework.core.convert.support) CalendarToLongConverter in DateFormatterRegistrar (org.springframework.format.datetime) EnumToIntegerConverter (org.springframework.core.convert.support) ZonedDateTimeToCalendarConverter (org.springframework.core.convert.support) OffsetDateTimeToZonedDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) CalendarToLocalTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) CalendarToOffsetDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) StringToEnum in StringToEnumConverterFactory (org.springframework.core.convert.support) SerializingConverter (org.springframework.core.serializer.support) DateTimeToLocalDateTimeConverter in JodaTimeConverters (org.springframework.format.datetime.joda) DateTimeToCalendarConverter in JodaTimeConverters (org.springframework.format.datetime.joda) CalendarToDateConverter in DateFormatterRegistrar (org.springframework.format.datetime) DateToReadableInstantConverter in JodaTimeConverters (org.springframework.format.datetime.joda) CalendarToZonedDateTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard) LocalDateTimeToLocalTimeConverter in DateTimeConverters (org.springframework.format.datetime.standard)
② ConverterFactory
将相同系列多个"同质" Converter
封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number的子类(Integer、Long、Double等
)对象)。注解源码如下
public interface ConverterFactory<S, R> { //获取转换器,该转换器可以将S转换为目标T类型(T extends R) <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
其是典型的工厂模式,子类具体工厂(都是final不可继承)负责生产对应的Convert:
CharacterToNumberFactory:static final class CharacterToNumber,Character–Number;
StringToEnumConverterFactory :static class StringToEnum,String–Enum;
IntegerToEnumConverterFactory:static class IntegerToEnum,Integer–Enum;
NumberToNumberConverterFactory:static final class NumberToNumber,Number–Number;
StringToNumberConverterFactory:static final class StringToNumber,String–Number
final修饰类时表示该类不允许被继承,final类中的成员方法都会被隐式的指定为final方法。
static修饰类时只能修饰内部类,此时内部类可以直接创建实例、可以有静态成员变量、方法(普通内部类不行)和非静态成员变量、方法。
③ GenericConverter
用于在两个或多个类型之间转换的通用转换器接口。这是最灵活的转换器SPI接口,也是最复杂的。
它的灵活性在于,GenericConverter 可能支持在多个源/目标类型对之间进行转换。此外,GenericConverter 实现在类型转换过程中可以访问源/目标。这允许解析可用于影响转换逻辑的源和目标字段元数据,如注解和泛型信息。
当更简单的 Converter或ConverterFactory接口足够时,通常不应使用此接口。会根据源类对象及目标类对象
所在的宿主类中的上下文信息进行类型转换。
public interface GenericConverter { //返回此转换器可以转换的源类型和目标类型。每一个实体都是一个可以转换的 source-to-target类型对 @Nullable Set<ConvertiblePair> getConvertibleTypes(); //将源对象转换为TypeDescriptor 描述的类型,返回转换后的对象 @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType); /** * Holder for a source-to-target class pair. */ final class ConvertiblePair { private final Class<?> sourceType; private final Class<?> targetType; //... } }
【2】自定义转换器
如果想把一个字符串转换成其它实体类型,spring没有提供这样默认的功能,我们需要自定义类型转换器。
① 自定义类型转换器
需求如下:这里有个实体类Employee,将传输参数(String类型)转换为Employee。
//实现了Converter<String, Employee> 接口 @Component public class EmployeeConverter implements Converter<String, Employee> { @Override public Employee convert(String source) { if(source != null){ String [] vals = source.split("-"); //GG-gg@web.com-0-105 if(vals != null && vals.length == 4){ String lastName = vals[0]; String email = vals[1]; Integer gender = Integer.parseInt(vals[2]); Department department = new Department(); department.setId(Integer.parseInt(vals[3])); Employee employee = new Employee(null, lastName, email, gender, department); System.out.println(source + "--convert--" + employee); return employee; } } return null; } }
② 注册到容器
xml方式注册
可通过ConversionServiceFactoryBean
的converters
属性注册自定义的类型转换器。示例如下:
<!-- 配置 ConversionService 数据类型的转换和格式化 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="employeeConverter"/> <!-- 自定义转换器 以前的一样存在 --> </set> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"/> <!--将自定义的conversionService注册到Spring MVC的上下文中-->
上面配置文件中我们使用配置了一个FormattingConversionServiceFactoryBean
。虽然可以使用ConversionServiceFactoryBean
,但是不推荐。
SpringBoot下使用java config注册
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new EmployeeConverter()); // ... } }
【3】ConverterFactory使用实例
实践背景:根据code(字符串)转换为一个枚举对象。
① MyEnum
MyEnum实现了接口BaseEnum 。
public enum MyEnum implements BaseEnum { enum1(1,"enum1"), enum2(2,"enum2"); private int code; private String name; MyEnum(int code, String name) { this.code = code; this.name = name; } @Override public String toString() { return String.valueOf(code); } @Override public int code() { return code; } }
② MyConver
MyConver将String转换为BaseEnum的子类。
@Component public class MyConver<T extends BaseEnum> implements Converter<String, T> { @Override public T convert(String source) { switch (source){ case "1":return (T)MyEnum.enum1; case "2":return (T)MyEnum.enum2; } return (T)MyEnum.enum1; } }
③ MyConvertFactory
MyConvertFactory 的核心思想就是根据targetType获取对应的Converter。
@Component public class MyConvertFactory implements ConverterFactory<String,BaseEnum> { @Override public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) { if(targetType ==BaseEnum.class){ return new MyConver<>(); } return new MyConver<>(); } }
然后注册到Spring中:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new MyConvertFactory()); // ... } }
实际开发中,常常会遇到“数据格式化”的场景。那么如何格式化数据?其实就是通过使用转换器将数据转换为目标格式。
【4】FormattingConversionService
Spring 在格式化模块中定义了一个实现ConversionService
接口的FormattingConversionService
实现类。该实现类扩展了GenericConversionService
,因此它既具有类型转换的功能又具有格式化的功能。
FormattingConversionService
拥有一个FormattingConversionServiceFactoryBean
工厂类,后者用于在Spring上下文中构造前者。
FormattingConversionService内部注册了两个工厂类:
① NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解;
② JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解。
这下就明白了,为什么可以使用@NumberFormat和@DateTimeFormat注解进行数据格式化了
装配了FormattingConversionServiceFactoryBean后,就可以在SpringMVC入参绑定及模型数据输出时使用注解驱动了。FormattingConversionServiceFactoryBean的afterPropertiesSet方法很有意思,在bean实例化后会获取DefaultFormattingConversionService实例,然后注册类型转换器和格式化器Formatter
public void afterPropertiesSet() { this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters); ConversionServiceFactory.registerConverters(this.converters, this.conversionService); registerFormatters(this.conversionService); }
SpringMVC配置文件示例
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven> <!-- 配置 ConversionService 数据类型的转换和格式化 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <!--注意这里是FormattingConversionServiceFactoryBean--> <property name="converters"> <set> <ref bean="employeeConverter"/> <!-- 自定义转换器 以前的一样存在 --> </set> </property> </bean>
【5】日期格式化和数值格式化
示例代码如下图所示:
下面分别简要介绍一下上述两个注解
① @DateTimeFormat注解
该注解可对java.util.Date , java.util.Calendar , java.long.Long
时间类型进行标注。
① pattern属性:
类型为字符串。指定解析/格式化字段数据的模式,如“yyyy-MM-dd HH:mm:ss
”。
注意:如果是12小时制,使用 hh:mm:ss ; 如果是24小时制,使用HH:mm:ss。
② iso属性
类型为DateTimeFormat.ISO
。指定解析/格式化字段数据的ISO模式,包括四种:
ISO.NONE; ISO.DATE(yyyy-MM-dd); ISO.TIME(hh:mm:ss.SSSZ); ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)。
③ style属性
字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式。
- S:短日期/时间格式;
- M:中日期/时间格式;
- L:长日期/时间格式;
- F:完整日期/时间格式;
-
:忽略日期或时间格式。
② @NumberFormat注解
该注解可对类似数字类型的属性进行标注,它有两个互斥的属性:
① style
类型为NumberFormat.Style
。用于指定样式类型,包括三种:
Style.NUMBER(正常数字类型); Style.CURRENCY(货币类型); Style.PERCENT(百分数类型)。
② pattern
类型为String,自定义样式,如pattern="#,###"
。