1.前言
在前一篇WebDataBinder中讲述了了一个PropertyEditor,它通过setAsText满足了字符串到指定类型的转换,但是它实现不了从任意类型转换到目标类型,所以在spring3.x之后引入了Converter,它实现了上述需求的转换。
2.原理
2.1 工作原理图
2.2 原理介绍
①:类型转换:内部的ConversionService会根据S源类型/T目标类型自动选择相应的Converter SPI进行类型转换,而且是强类型的,能在任意类型数据之间进行转换;
②:数据验证:支持JSR-303验证框架,如将@Valid放在需要验证的目标类型上即可;
③:格式化显示:其实就是任意目标类型---->String的转换,完全可以使用Converter SPI完成。
Spring为了更好的诠释格式化/解析功能提供了Formatter SPI,支持根据Locale信息进行格式化/解析,而且该套SPI可以支持字段/参数级别的细粒度格式化/解析,流程如下:
①:类型解析(转换):String---->T类型目标对象的解析,和PropertyEditor类似;
②:数据验证:支持JSR-303验证框架,如将@Valid放在需要验证的目标类型上即可;
③:格式化显示:任意目标类型---->String的转换,和PropertyEditor类似。
3.Converter
3.1 接口介绍
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
*/
T convert(S source);
}
Converter只提供了一个convert方法,其中S代表源类型,T代表目标类型
3.2 实例
3.2.1 TelephoneConverter
public class TelephoneConverter implements Converter<String, Telephone> {
@Override
public Telephone convert(String source) {
if (source.matches("\\d{3,4}-\\d{7,8}")) {
String[] telephoneArray = source.split("-");
return new Telephone(telephoneArray[0], telephoneArray[1]);
}
return null;
}
}
3.2.2 控制器
@RequestMapping (value="/converter/1",method= RequestMethod.GET)
@ResponseBody
public Person demo1(Person p) {
return p;
}
3.2.3 配置
如果我们同时配置了PropertyEditor和Converter,spring默认先作用PropertyEditor,再作用Converter,但是最好不要这样子2个都上。
<mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
<bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.kings.template.mvc.TelephoneConverter"></bean>
</set>
</property>
</bean>
3.3 spring提供的转换器
在spring-core下的org.springframework.core.convert包下提供了许多内置的converter
标量转换器
转换器类 | 功能 |
---|---|
StringToBooleanConverter | String----->Boolean true: true/on/yes/1;false: false/off/no/0 |
ObjectToStringConverter | Object----->String 调用toString方法转换 |
StringToNumberConverterFactory | String----->Number(如Integer、Long等) |
NumberToNumberConverterFactory | Number子类型(Integer、Long、Double等)-----> Number子类型(Integer、Long、Double等) |
StringToCharacterConverter | String----->java.lang.Character 取字符串第一个字符 |
NumberToCharacterConverter | Number子类型(Integer、Long、Double等)-----> java.lang.Character |
CharacterToNumberFactory | java.lang.Character ----->Number子类型(Integer、Long、Double等) |
StringToEnumConverterFactory | String----->enum类型 |
EnumToStringConverter | enum类型----->String 返回enum对象的name()值 |
StringToLocaleConverter | String----->java.util.Local |
PropertiesToStringConverter | java.util.Properties----->String |
StringToPropertiesConverter | String----->java.util.Properties |
集合、数组相关转换器
转换器类 | 功能 |
---|---|
ArrayToCollectionConverter | 任意S数组---->任意T集合(List、Set) |
CollectionToArrayConverter | 任意T集合(List、Set)---->任意S数组 |
ArrayToArrayConverter | 任意S数组<---->任意T数组 |
CollectionToCollectionConverter | 任意T集合(List、Set)<---->任意T集合(List、Set) |
MapToMapConverter | Map<---->Map之间的转换 |
ArrayToStringConverter | 任意S数组---->String类型 |
StringToArrayConverter | String----->数组 默认通过“,”分割,且去除字符串的两边空格(trim) |
ArrayToObjectConverter | 任意S数组---->任意Object的转换 |
ObjectToArrayConverter | Object----->单元素数组 |
CollectionToStringConverter | 任意T集合(List、Set)---->String类型 |
StringToCollectionConverter | String----->集合(List、Set) |
CollectionToObjectConverter | 任意T集合---->任意Object的转换 |
ObjectToCollectionConverter | Object----->单元素集合 |
默认(fallback)转换器之前的转换器不能转换时调用
转换器类 | 功能 |
---|---|
ObjectToObjectConverter | Object(S)----->Object(T)首先尝试valueOf进行转换、没有则尝试new 构造器(S) |
IdToEntityConverter | Id(S)----->Entity(T) |
FallbackObjectToStringConverter | Object----->String 最终转换方法,调用toString() |
4.ConverterFactory
需求:需要将一个类中的String转换成Enum,而且是有多个,如:
@Data
public class Person {
private String name;
private Telephone telephone;
private Sex sex;
private Race race;
那么我们再通过写一个SexConverter和RaceConverter是可以实现,但是我们当然有更懒的实现方法。
通过工厂方法,抽象出将String转化成枚举的过程,省去每次再去定义一个方法去实现类似业务的转化器。
4.1 接口介绍
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);
}
4.2 实例
我们来一个覆盖spring默认的StringConvertorFactory
4.2.1 工具类
/**
* <p class="detail">
* 功能:将枚举的数字转化成枚举列
* </p>
* @param <T> the type parameter
*
* @author Kings
* @ClassName Convent tag num 2 emum list.
* @Version V1.0.
* @date 2016.03.24 18:46:21
*/
public class ConventNum2Emum<T extends Enum<T>> {
/**
* <p class="detail">
* 功能:与或转化
* </p>
* @param status :状态
* @param enumType :枚举类型
*
* @return list
* @author Kings
* @date 2016.03.24 18:46:21
*/
public List<T> convent(Long status,Class enumType) {
List<T> tags = new ArrayList<T>();
if(status != null){
Field[] fields = enumType.getFields();
for (Field f : fields) {
Enum<T> e = Enum.valueOf(enumType,f.getName());
Long eValue = Long.parseLong(e.toString());
if((eValue & status) == eValue){
tags.add((T) e);
}
}
}
return tags;
}
/**
* <p class="detail">
* 功能:非与或转换
* </p>
* @param status :状态
* @param enumType :枚举类型
*
* @return list
* @author Kings
* @date 2016.06.15 10:46:35
*/
public T conventNormal(Long status, Class enumType) {
if (status != null) {
Field[] fields = enumType.getFields();
for (Field f : fields) {
Enum<T> e = Enum.valueOf(enumType, f.getName());
Long eVlue = Long.parseLong(e.toString());
if (status.longValue() == eVlue.longValue()) {
return (T) e;
}
}
}
return null;
}
}
4.2.1 自定义ConverterFactory
public final class MyStringConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new LongToEnum(targetType);
}
private class LongToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
public LongToEnum(Class<T> enumType) {
this.enumType = enumType;
}
@Override
public T convert(String source) {
return (T) new ConventNum2Emum().conventNormal(Long.parseLong(source), enumType);
}
}
}
4.2.2 配置
<mvc:annotation-driven conversion-service="myConverterService"></mvc:annotation-driven>
<bean id="myConverterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.kings.template.mvc.MyStringConverterFactory"></bean>
</set>
</property>
</bean>
5. ConditionalGenericConverter
ConditionalGenericConverter继承了2个重要的接口GenericConverter和ConditionConverter
5.1 接口说明
GenericConverter
/**
* Return the source and target types that this converter can convert between.
* <p>Each entry is a convertible source-to-target type pair.
* <p>For {@link ConditionalConverter conditional converters} this method may return
* {@code null} to indicate all source-to-target pairs should be considered.
*/
Set<ConvertiblePair> getConvertibleTypes();
/**
* Convert the source object to the targetType described by the {@code TypeDescriptor}.
* @param source the source object to convert (may be {@code null})
* @param sourceType the type descriptor of the field we are converting from
* @param targetType the type descriptor of the field we are converting to
* @return the converted object
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
GenericConverter接口是所有的Converter接口中最灵活也是最复杂的一个类型转换接口。像我们之前介绍的Converter接口只支持从一个原类型转换为一个目标类型;ConverterFactory接口只支持从一个原类型转换为一个目标类型对应的子类型;而GenericConverter接口支持在多个不同的原类型和目标类型之间进行转换,这也就是GenericConverter接口灵活和复杂的地方。GenericConverter接口中一共定义了两个方法,getConvertibleTypes()和convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes方法用于返回这个GenericConverter能够转换的原类型和目标类型的这么一个组合;convert方法则是用于进行类型转换的,我们可以在这个方法里面实现我们自己的转换逻辑。
5.2 实例
5.2.1 实体类
为了方便我就直接用User和Person
@Data
public class Person {
private String name;
private Telephone telephone;
private Sex sex;
private Race race;
private User u;
}
5.2.1 转化器
public class MyGenericConverter implements GenericConverter {
@Autowired
private UserService userService;
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> pairs = new HashSet<ConvertiblePair>();
pairs.add(new ConvertiblePair(String.class, User.class));
//受web层的request.getParamater()的影响,在web层Long作用不了
pairs.add(new ConvertiblePair(Long.class, User.class));
return pairs;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
if(sourceType.getType() == Long.class){
return userService.selectByPrimaryKey(source);
} else if(sourceType.getType() == String.class){
User u4q = new User();
u4q.setName(source+"");
return userService.selectOne(u4q);
}
return null;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
5.2.3 控制器
@RequestMapping (value="/converter/2",method= RequestMethod.GET)
@ResponseBody
public Person demo2(Person p) {
return p;
}
直接访问:http://localhost:8080/kingstemplate/converter/2?u=ws
得到Json结果如下:
{"name":null,"telephone":null,"sex":null,"race":null,"u":{"id":1,"name":"ws","age":26}}
5.2.4 非web项目
在5.2.3里面我们用不了Long转User,因为受web项目的影响,这次我们来个非web项目的
首先注册一个DefaultConversionService,因为它里面有个addConverter方便我么自己加入容器中的converter
<bean id="defaultConversionService" class="org.springframework.core.convert.support.DefaultConversionService"></bean>
再上测试demo
public class ConverterDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config/spring.xml");
UserService userService = (UserService) context.getBean("userServiceImpl");
MyGenericConverter myGenericConverter = new MyGenericConverter();
//注入接口
myGenericConverter.setUserService(userService);
DefaultConversionService defaultConversionService = (DefaultConversionService) context.getBean("defaultConversionService");
defaultConversionService.addConverter(myGenericConverter);
User u = defaultConversionService.convert(1L, User.class);//Long->User
System.out.println(u.getAge());//age输出不告诉你
User u1 = defaultConversionService.convert("ws", User.class);//String->User
System.out.println(u1.getAge());//age输出不告诉你
}
}
6.ConversionService
一般的ConversionService最底层都会继承ConverterRegistry和ConversionService
6.1 ConverterRegistry
6.1.1 接口说明
类型转换器注册支持,可以注册/删除相应的类型转换器
注册的时候添加了:Converter、GenericConverter、ConverterFactory以及移除某些类型的转换
void addConverter(Converter<?, ?> converter);
/**
* Add a plain converter to this registry.
* The convertible source/target type pair is specified explicitly.
* <p>Allows for a Converter to be reused for multiple distinct pairs without
* having to create a Converter class for each pair.
* @since 3.1
*/
<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
/**
* Add a generic converter to this registry.
*/
void addConverter(GenericConverter converter);
/**
* Add a ranged converter factory to this registry.
* The convertible source/target type pair is derived from the ConverterFactory's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved.
*/
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
/**
* Remove any converters from sourceType to targetType.
* @param sourceType the source type
* @param targetType the target type
*/
void removeConvertible(Class<?> sourceType, Class<?> targetType);
6.2 ConversionService
6.2.1 接口说明
提供运行期类型转换的支持
/**
* Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
* <p>If this method returns {@code true}, it means {@link #convert(Object, Class)} is capable
* of converting an instance of {@code sourceType} to {@code targetType}.
* <p>Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return {@code true}
* even though a convert invocation may still generate a {@link ConversionException} if the
* underlying elements are not convertible. Callers are expected to handle this exceptional case
* when working with collections and maps.
* @param sourceType the source type to convert from (may be {@code null} if source is {@code null})
* @param targetType the target type to convert to (required)
* @return {@code true} if a conversion can be performed, {@code false} if not
* @throws IllegalArgumentException if {@code targetType} is {@code null}
*/
boolean canConvert(Class<?> sourceType, Class<?> targetType);
/**
* Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
* The TypeDescriptors provide additional context about the source and target locations
* where conversion would occur, often object fields or property locations.
* <p>If this method returns {@code true}, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)}
* is capable of converting an instance of {@code sourceType} to {@code targetType}.
* <p>Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return {@code true}
* even though a convert invocation may still generate a {@link ConversionException} if the
* underlying elements are not convertible. Callers are expected to handle this exceptional case
* when working with collections and maps.
* @param sourceType context about the source type to convert from
* (may be {@code null} if source is {@code null})
* @param targetType context about the target type to convert to (required)
* @return {@code true} if a conversion can be performed between the source and target types,
* {@code false} if not
* @throws IllegalArgumentException if {@code targetType} is {@code null}
*/
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Convert the given {@code source} to the specified {@code targetType}.
* @param source the source object to convert (may be {@code null})
* @param targetType the target type to convert to (required)
* @return the converted object, an instance of targetType
* @throws ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is {@code null}
*/
<T> T convert(Object source, Class<T> targetType);
/**
* Convert the given {@code source} to the specified {@code targetType}.
* The TypeDescriptors provide additional context about the source and target locations
* where conversion will occur, often object fields or property locations.
* @param source the source object to convert (may be {@code null})
* @param sourceType context about the source type to convert from
* (may be {@code null} if source is {@code null})
* @param targetType context about the target type to convert to (required)
* @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}
* @throws ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is {@code null},
* or {@code sourceType} is {@code null} but source is not {@code null}
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):
DefaultConversionService:默认的类型转换服务实现;
DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。