Spring3.* 引入了更加通用的类型转换系统,其定义了SPI接口(Converter等)和相应的运行时执行类型转换的API(ConversionService等),在Spring中它和PropertyEditor功能类似,可以替代PropertyEditor来转换外部Bean属性的值到Bean属性需要的类型。
该类型转换系统是Spring通用的,其定义在org.springframework.core.convert包中,不仅仅在Spring Web MVC场景下。目标是完全替换PropertyEditor,提供无状态、强类型且可以在任意类型之间转换的类型转换系统,可以用于任何需要的地方,如SpEL、数据绑定。
Converter SPI完成通用的类型转换逻辑,如java.util.Date<---->java.lang.Long或java.lang.String---->PhoneNumberModel等。
1、类型转换器:提供类型转换的实现支持。
一个有如下三种接口:
(1、Converter:类型转换器,用于转换S类型到T类型,此接口的实现必须是线程安全的且可以被共享。
package org.springframework.core.convert.converter;
/**
* A converter converts a source object of type S to a target of type T.
* Implementations of this interface are thread-safe and can be shared.
*
* @author Keith Donald
* @since 3.0
*/
public interface Converter<S, T> {
/**
* Convert the source of type S to target type T.
* @param source the source object to convert, which must be an instance of S
* @return the converted object, which must be an instance of T
* @throws IllegalArgumentException if the source could not be converted to the desired target type
*/
T convert(S source);
}
示例:请参考cn.javass.chapter7.converter.support.StringToPhoneNumberConverter转换器,用于将String--->PhoneNumberModel。
此处我们可以看到Converter接口实现只能转换一种类型到另一种类型,不能进行多类型转换,如将一个数组转换成集合,如(String[] ----> List<String>、String[]----->List<PhoneNumberModel>等)。
(2、GenericConverter和ConditionalGenericConverter:GenericConverter接口实现能在多种类型之间进行转换,ConditionalGenericConverter是有条件的在多种类型之间进行转换。
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.converter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
import java.util.Set;
/**
* Generic converter interface for converting between two or more types.
*
* <p>This is the most flexible of the Converter SPI interfaces, but also the most complex.
* It is flexible in that a GenericConverter may support converting between multiple source/target
* type pairs (see {@link #getConvertibleTypes()}. In addition, GenericConverter implementations
* have access to source/target {@link TypeDescriptor field context} during the type conversion process.
* This allows for resolving source and target field metadata such as annotations and generics
* information, which can be used influence the conversion logic.
*
* <p>This interface should generally not be used when the simpler {@link Converter} or
* {@link ConverterFactory} interfaces are sufficient.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see TypeDescriptor
* @see Converter
* @see ConverterFactory
*/
public interface GenericConverter {
/**
* Return the source and target types which this converter can convert between.
* <p>Each entry is a convertible source-to-target type pair.
*/
Set<ConvertiblePair> getConvertibleTypes();
/**
* Convert the source to the targetType described by the TypeDescriptor.
* @param source the source object to convert (may be 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);
/**
* Holder for a source-to-target class pair.
*/
public static final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
/**
* Create a new source-to-target pair.
* @param sourceType the source type
* @param targetType the target type
*/
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(sourceType, "Source type must not be null");
Assert.notNull(targetType, "Target type must not be null");
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return this.sourceType;
}
public Class<?> getTargetType() {
return this.targetType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || obj.getClass() != ConvertiblePair.class) {
return false;
}
ConvertiblePair other = (ConvertiblePair) obj;
return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);
}
@Override
public int hashCode() {
return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
}
}
}
getConvertibleTypes:指定了可以转换的目标类型对;
convert:在sourceType和targetType类型之间进行转换。
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.converter;
import org.springframework.core.convert.TypeDescriptor;
/**
* A generic converter that conditionally executes.
*
* <p>Applies a rule that determines if a converter between a set of
* {@link #getConvertibleTypes() convertible types} matches given a client request to
* convert between a source field of convertible type S and a target field of convertible type T.
*
* <p>Often used to selectively match custom conversion logic based on the presence of
* a field or class-level characteristic, such as an annotation or method. For example,
* when converting from a String field to a Date field, an implementation might return
* <code>true</code> if the target field has also been annotated with <code>@DateTimeFormat</code>.
*
* <p>As another example, when converting from a String field to an Account field,
* an implementation might return true if the target Account class defines a
* <code>public static findAccount(String)</code> method.
*
* @author Keith Donald
* @since 3.0
*/
public interface ConditionalGenericConverter extends GenericConverter {
/**
* Should the converter from <code>sourceType</code> to <code>targetType</code>
* currently under consideration be selected?
* @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 true if conversion should be performed, false otherwise
*/
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
matches:用于判断sourceType和targetType类型之间能否进行类型转换。
示例:如org.springframework.core.convert.support.ArrayToCollectionConverter和CollectionToArrayConverter用于在数组和集合间进行转换的ConditionalGenericConverter实现,如在String[]<---->List<String>、String[]<---->List<PhoneNumberModel>等之间进行类型转换。
对于我们大部分用户来说一般不需要自定义GenericConverter, 如果需要可以参考内置的GenericConverter来实现自己的。
(3、ConverterFactory:工厂模式的实现,用于选择将一种S源类型转换为R类型的子类型T的转换器的工厂接口。
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.converter;
/**
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
*
* @author Keith Donald
* @since 3.0
* @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);
}
S:源类型;R目标类型的父类型;T:目标类型,且是R类型的子类型;
getConverter:得到目标类型的对应的转换器。
示例:如org.springframework.core.convert.support.NumberToNumberConverterFactory用于在Number类型子类型之间进行转换,如Integer--->Double, Byte---->Integer, Float--->Double等。
对于我们大部分用户来说一般不需要自定义ConverterFactory,如果需要可以参考内置的ConverterFactory来实现自己的。
2、类型转换器注册器、类型转换服务:提供类型转换器注册支持,运行时类型转换API支持。
一共有如下两种接口:
(1、ConverterRegistry:类型转换器注册支持,可以注册/删除相应的类型转换器。
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.converter;
/**
* For registering converters with a type conversion system.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
*/
public interface ConverterRegistry {
/**
* Add a plain converter to this registry.
* The convertible sourceType/targetType pair is derived from the Converter's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved
*/
void addConverter(Converter<?, ?> converter);
/**
* Add a plain converter to this registry.
* The convertible sourceType/targetType pair is specified explicitly.
* Allows for a Converter to be reused for multiple distinct pairs without having to create a Converter class for each pair.
* @since 3.1
*/
void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
/**
* Add a generic converter to this registry.
*/
void addConverter(GenericConverter converter);
/**
* Add a ranged converter factory to this registry.
* The convertible sourceType/rangeType 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);
}
可以注册:Converter实现,GenericConverter实现,ConverterFactory实现。
(2、ConversionService:运行时类型转换服务接口,提供运行期类型转换的支持。
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert;
/**
* A service interface for type conversion. This is the entry point into the convert system.
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
*
* @author Keith Donald
* @since 3.0
*/
public interface ConversionService {
/**
* Returns true if objects of sourceType can be converted to targetType.
* If this method returns true, it means {@link #convert(Object, Class)} is capable of converting an instance of sourceType to targetType.
* Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return '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 null if source is null)
* @param targetType the target type to convert to (required)
* @return true if a conversion can be performed, false if not
* @throws IllegalArgumentException if targetType is null
*/
boolean canConvert(Class<?> sourceType, Class<?> targetType);
/**
* Returns true if objects of sourceType can be converted to the targetType.
* The TypeDescriptors provide additional context about the source and target locations where conversion would occur, often object fields or property locations.
* If this method returns true, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)} is capable of converting an instance of sourceType to targetType.
* Special note on collections, arrays, and maps types:
* For conversion between collection, array, and map types, this method will return '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 null if source is null)
* @param targetType context about the target type to convert to (required)
* @return true if a conversion can be performed between the source and target types, false if not
* @throws IllegalArgumentException if targetType is null
*/
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Convert the source to targetType.
* @param source the source object to convert (may be 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 null
*/
<T> T convert(Object source, Class<T> targetType);
/**
* Convert the source to 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 null)
* @param sourceType context about the source type converting from (may be null if source is null)
* @param targetType context about the target type to convert to (required)
* @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}</code>
* @throws ConversionException if a conversion exception occurred
* @throws IllegalArgumentException if targetType is null
* @throws IllegalArgumentException if sourceType is null but source is not null
*/
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
convert:将源对象转换为目标类型的目标对象。
Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):
DefaultConversionService:默认的类型转换服务实现;
DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。
7.2.2.2、Spring内建的类型转换器如下所示:
类名 | 说明 |
第一组:标量转换器 | |
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类型 通过Enum.valueOf将字符串转换为需要的enum类型 |
EnumToStringConverter | enum类型----->String 返回enum对象的name()值 |
StringToLocaleConverter | String----->java.util.Local |
PropertiesToStringConverter | java.util.Properties----->String 默认通过ISO-8859-1解码 |
StringToPropertiesConverter | String----->java.util.Properties 默认使用ISO-8859-1编码 |
第二组:集合、数组相关转换器 | |
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的转换 (如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换) |
ObjectToArrayConverter | Object----->单元素数组 |
CollectionToStringConverter | 任意T集合(List、Set)---->String类型 |
StringToCollectionConverter | String----->集合(List、Set) 默认通过“,”分割,且去除字符串的两边空格(trim) |
CollectionToObjectConverter | 任意T集合---->任意Object的转换 (如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换) |
ObjectToCollectionConverter | Object----->单元素集合 |
第三组:默认(fallback)转换器:之前的转换器不能转换时调用 | |
ObjectToObjectConverter | Object(S)----->Object(T) 首先尝试valueOf进行转换、没有则尝试new 构造器(S) |
IdToEntityConverter | Id(S)----->Entity(T) 查找并调用public static T find[EntityName](S)获取目标对象,EntityName是T类型的简单类型 |
FallbackObjectToStringConverter | Object----->String ConversionService作为恢复使用,即其他转换器不能转换时调用(执行对象的toString()方法) |
S:代表源类型,T:代表目标类型
如上的转换器在使用转换服务实现DefaultConversionService和DefaultFormattingConversionService时会自动注册。
7.2.2.3、示例
(1、自定义String----->PhoneNumberModel的转换器
- package cn.javass.chapter7.web.controller.support.converter;
- //省略import
- public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {
- Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
- @Override
- public PhoneNumberModel convert(String source) {
- if(!StringUtils.hasLength(source)) {
- //①如果source为空 返回null
- return null;
- }
- Matcher matcher = pattern.matcher(source);
- if(matcher.matches()) {
- //②如果匹配 进行转换
- PhoneNumberModel phoneNumber = new PhoneNumberModel();
- phoneNumber.setAreaCode(matcher.group(1));
- phoneNumber.setPhoneNumber(matcher.group(2));
- return phoneNumber;
- } else {
- //③如果不匹配 转换失败
- throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", source));
- }
- }
- }
String转换为Date的类型转换器,请参考cn.javass.chapter7.web.controller.support.converter.StringToDateConverter。
(2、测试用例(cn.javass.chapter7.web.controller.support.converter.ConverterTest)
- @Test
- public void testStringToPhoneNumberConvert() {
- DefaultConversionService conversionService = new DefaultConversionService();
- conversionService.addConverter(new StringToPhoneNumberConverter());
- String phoneNumberStr = "010-12345678";
- PhoneNumberModel phoneNumber = conversionService.convert(phoneNumberStr, PhoneNumberModel.class);
- Assert.assertEquals("010", phoneNumber.getAreaCode());
- }
类似于PhoneNumberEditor将字符串“010-12345678”转换为PhoneNumberModel。
- @Test
- public void testOtherConvert() {
- DefaultConversionService conversionService = new DefaultConversionService();
- //"1"--->true(字符串“1”可以转换为布尔值true)
- Assert.assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class));
- //"1,2,3,4"--->List(转换完毕的集合大小为4)
- Assert.assertEquals(4, conversionService.convert("1,2,3,4", List.class).size());
- }
其他类型转换器使用也是类似的,此处不再重复。
7.2.2.4、集成到Spring Web MVC环境
(1、注册ConversionService实现和自定义的类型转换器
- <!-- ①注册ConversionService -->
- <bean id="conversionService" class="org.springframework.format.support.
- FormattingConversionServiceFactoryBean">
- <property name="converters">
- <list>
- <bean class="cn.javass.chapter7.web.controller.support.
- converter.StringToPhoneNumberConverter"/>
- <bean class="cn.javass.chapter7.web.controller.support.
- converter.StringToDateConverter">
- <constructor-arg value="yyyy-MM-dd"/>
- </bean>
- </list>
- </property>
- </bean>
FormattingConversionServiceFactoryBean:是FactoryBean实现,默认使用DefaultFormattingConversionService转换器服务实现;
converters:注册我们自定义的类型转换器,此处注册了String--->PhoneNumberModel和String--->Date的类型转换器。
(2、通过ConfigurableWebBindingInitializer注册ConversionService
- <!-- ②使用ConfigurableWebBindingInitializer注册conversionService -->
- <bean id="webBindingInitializer" class="org.springframework.web.bind.support.
- ConfigurableWebBindingInitializer">
- <property name="conversionService" ref="conversionService"/>
- </bean>
此处我们通过ConfigurableWebBindingInitializer绑定初始化器进行ConversionService的注册;
3、注册ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"><property name="webBindingInitializer" ref="webBindingInitializer"/></bean>
通过如上配置,我们就完成了Spring3.0的类型转换系统与Spring Web MVC的集成。此时可以启动服务器输入之前的URL测试了。
此时可能有人会问,如果我同时使用PropertyEditor和ConversionService,执行顺序是什么呢?内部首先查找PropertyEditor进行类型转换,如果没有找到相应的PropertyEditor再通过ConversionService进行转换。