3.4Spring类型转换

Spring3 引入了一个core.convert包,提供通用的type转换方法。系统定义了SPI来实现类型转换逻辑和一个API来在运行时实现类型转换。

在Spring容器中,可以使用此系统作为PropertyEditor实现的替代方法,将外部化的bean属性值字符串转换为所需的属性类型。你还可以在应用程序中需要类型转换的任何地方使用公共API。

3.4.1 转换SPI

实现类型转换逻辑的SPI是简单且强类型的,如下接口定义所示:

package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}

要创建你自己的converter,只需要实现Converter接口,其中S表示要转换的类型,T表示要转换到的类型。

如果需要将S的集合或数组转换为T的数组或集合,也可以透明地应用此类转换器,前提是已注册了委托数组或集合转换器(默认情况下,DefaultConversionService会这样做)。

对于每次调用convert(S), S默认是非空的。如果S为空,Converter则会抛出unchecked 异常,具体来说,它会抛出IllegalArgumentException异常,确保你的Converter实现是线程安全的。

为了方便起见,core.convert.support包中提供了几个转换器实现。其中包括从字符串到数字的转换器和其他常见类型。下面的列表显示了StringToInteger类,这是一个典型的Converter实现:

package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}

3.4.2 ConverterFactory

当需要集中管理整个类层次结构的转换逻辑时(例如,从字符串转换为枚举对象时),可以实现ConverterFactory,如下例所示:

package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

参数S是需要转换的类型,R是要转换到的类的基类。然后实现getConverter(Class)方法,其中T是R的子类。

考虑StringToEnumConverterFactory的例子:

package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}

3.4.3 使用GenericConverter

当需要复杂的Converter实现时,请考虑使用GenericConverter接口。与Converter相比,GenericConverter具有更灵活但不太强类型的签名,它支持在多个源类型和目标类型之间进行转换。此外,GenericConverter提供了可用的源和目标字段上下文,你可以在实现转换逻辑时使用它们。这样的上下文允许类型转换由字段注解或字段签名上声明的一般信息驱动。下面的列表显示了GenericConverter的接口定义:

package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

要实现GenericConverter,只需要getConvertibleTypes()返回支持的source→targe, 然后在convert(Object, TypeDescriptor, TypeDescriptor)实现具体的业务。sourceType提供了要被转换的源字段的访问,targetType提供了要转换到的目标字段的访问。

GenericConverter的一个很好的例子是在Java数组和集合之间转换的转换器。例如ArrayToCollectionConverter自省声明目标集合类型的字段以解析集合的元素类型。这样,在目标字段上设置集合之前,源数组中的每个元素都可以转换为集合元素类型。

因为GenericConverter是一个更复杂的SPI接口,只在需要的使用它。你可以使用Converter或者ConverterFactory来进行基本的类型转换。

使用ConditionalGenericConverter

有时,你只希望在特定条件成立时运行Converter。例如,你可能只希望在目标字段上存在特定批注时运行Converter,也可能只希望在目标类上定义特定方法(如静态valueof方法)时运行Converter。ConditionalGenericConverter是GenericConverter和ConditionalConverter接口的联合,可用于定义此类自定义匹配条件:

public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

一个ConditionalGenericConverter的很好的例子是EntityConverter,它在持久实体标识符和实体引用之间进行转换。只有当目标实体类型声明静态finder方法(例如findAccount(long))时,这样的EntityConverter才可能匹配。你可以在匹配项(typescriptor、typescriptor)的实现中执行这样的finder方法检查。

3.4.4 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用来注册转换器。在内部,ConversionService实现委托其注册的转换器来执行类型转换逻辑。

core.convert.support包中提供了一个健壮的conversionService实现。GenericConversionService是适用于大多数环境的通用实现。ConversionServiceFactory为创建通用ConversionService配置提供了方便的工厂。

3.4.5 配置ConversionService

ConversionService是一个无状态对象,设计为在应用程序启动时实例化,然后在多个线程之间共享。在Spring应用程序中,通常为每个Spring容器(或ApplicationContext)配置ConversionService实例。Spring接收转换服务,并在框架需要执行类型转换时使用它。你还可以将此ConversionService注入到任何bean中,并直接调用它。

如果没有ConversionService注册到Spring,那么原始的PropertyEditor将会被使用。

要注册一个默认的ConversionService到Spring,添加下面的bean定义,并将id命名为conversionService。

<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>

一个默认的ConversionService可以转换trings, numbers, enums, collections, maps, 和其他通用类型. 要用自己的自定义转换器补充或重写默认转换器,请设置Converters属性。属性值可以实现任何Converter、ConverterFactory或GenericConverter接口。

<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>

通常ConversionService也被使用在Spring MVC中。

在特定的环境中,你可能希望在转换中使用格式化,可以参照FormattingConversionServiceFactoryBean的FormatterRegistry SPI。

3.4.6 编程方式使用ConversionService

要编程的使用ConversionService,你可以像注入其他Bean一样将其注入,如下所示:

@Service
public class MyService {
@Autowired
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}

对于大多数用例,可以使用指定TargetType的Convert方法,但它不适用于更复杂的类型,例如参数化元素的集合。例如,如果要以编程方式将整数列表转换为字符串列表,则需要提供源类型和目标类型的正式定义。

幸运的是,TypeDescriptor提供了各种选项,使操作变得简单易懂,如下面的示例所示:

DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ....
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

注意DefaultConversionService将会自动注册适用于大多数环境的转换器。 这包括collection converters、scalar converters和基本Object-to-String converters。使用DefaultConversionService的静态addDefaultConverters方法,可以将同一个converters注册到任何ConverterRegistry。

value types Converters被用于arrays和collections,因此无需创建一个特定的converter来从一个 Collection S到一个T Collection,假设标准的Collection处理是适当的。