5.5 Spring类型转换 {#toc_9}

Spring 3引入了core.convert包来提供一个一般类型的转换系统。这个系统定义了实现类型转换逻辑的服务提供接口(SPI)以及在运行时执行类型转换的API。在Spring容器内,这个系统可以当作是PropertyEditor的替代选择,用于将外部bean的属性值字符串转换成所需的属性类型。这个公共的API也可以在你的应用程序中任何需要类型转换的地方使用。

5.5.1 Converter SPI {#toc_10}

实现类型转换逻辑的SPI是简单并且强类型的:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);

}

要创建属于你自己的转换器,只需要简单的实现以上接口即可。泛型参数S表示你想要进行转换的源类型,而泛型参数T表示你想要转换的目标类型。如果一个包含S类型元素的集合或数组需要转换为一个包含T类型的数组或集合,那么这个转换器也可以被透明地应用,前提是已经注册了一个委托数组或集合的转换器(默认情况下会是DefaultConversionService处理)。

对每次方法convert(S)的调用,source参数值必须确保不为空。如果转换失败,你的转换器可以抛出任何非受检异常(unchecked exception);具体来说,为了报告一个非法的source参数值,应该抛出一个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);
    }

}

5.5.2 ConverterFactory {#toc_11}

当你需要集中整个类层次结构的转换逻辑时,例如,碰到将String转换到java.lang.Enum对象的时候,请实现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的一个子类。

考虑将StringToEnum作为ConverterFactory的一个示例:

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());
        }
    }
}

5.5.3 GenericConverter {#toc_12}

当你需要一个复杂的转换器实现时,请考虑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()方法要返回支持的源-目标类型对,然后实现convert(Object,TypeDescriptor,TypeDescriptor)方法来实现你的转换逻辑。源TypeDescriptor提供了对持有被转换值的源字段的访问,目标TypeDescriptor提供了对设置转换值的目标字段的访问。

一个很好的GenericConverter的示例是一个在Java数组和集合之间进行转换的转换器。这样一个ArrayToCollectionConverter可以通过内省声明了目标集合类型的字段以解析集合元素的类型,这将允许原数组中每个元素可以在集合被设置到目标字段之前转换成集合元素的类型。

由于GenericConverter是一个更复杂的SPI接口,所以对基本类型的转换需求优先使用Converter或者ConverterFactory。

ConditionalGenericConverter {#toc_13}

有时候你只想要在特定条件成立的情况下Converter才执行,例如,你可能只想要在目标字段存在特定注解的情况下才执行Converter,或者你可能只想要在目标类中定义了特定方法,比如staticvalueOf方法,才执行ConverterConditionalGenericConverterGenericConverterConditionalConveter接口的联合,允许你定义这样的自定义匹配条件:

public interface ConditionalGenericConverter
        extends GenericConverter, ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

}

ConditionalGenericConverter的一个很好的例子是一个在持久化实体标识和实体引用之间进行转换的实体转换器。这个实体转换器可能只匹配这样的条件–目标实体类声明了一个静态的查找方法,例如findAccount(Long),你将在matches(TypeDescriptor,TypeDescriptor)方法实现里执行这样的查找方法的检测。

5.5.4 ConversionService API {#toc_14}

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配置提供了便利。

5.5.5 配置ConversionService {#toc_15}

ConversionService是一个被设计成在应用程序启动时会进行实例化的无状态对象,随后可以在多个线程之间共享。在一个Spring应用程序中,你通常会为每一个Spring容器(或者应用程序上下文ApplicationContext)配置一个ConversionService实例,它会被Spring接收并在框架需要执行一个类型转换时使用。你也可以将这个ConversionService直接注入到你任何的Bean中并直接调用。

如果Spring没有注册ConversionService,则会使用原始的基于PropertyEditor的系统。

要向Spring注册默认的ConversionService,可以用conversionService作为id来添加如下的bean定义:

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

默认的ConversionService可以在字符串、数字、枚举、映射和其他常见类型之间进行转换。为了使用你自己的自定义转换器来补充或者覆盖默认的转换器,可以设置converters属性,该属性值可以是Converter、ConverterFactory或者GenericConverter之中任何一个的接口实现。

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

在一个Spring MVC应用程序中使用ConversionService也是比较常见的,可以去看Spring MVC章节的Section 18.16.3 “Conversion and Formatting”

在某些情况下,你可能希望在转换期间应用格式化,可以看[5.6.3 “FormatterRegistry SPI”](http://ifeve.com/spring-5-validation/#5.6.3 FormatterRegistry SPI)获取使用FormattingConversionServiceFactoryBean的细节。

5.5.6 编程方式使用ConversionService {#toc_16}

要以编程方式使用ConversionService,你只需要像处理其他bean一样注入一个引用即可:

@Service
public class MyService {

    @Autowired
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}

对大多数用例来说,convert方法指定了可以使用的目标类型,但是它不适用于更复杂的类型比如参数化元素的集合。例如,如果你想要以编程方式将一个IntegerList转换成一个StringList,就需要为原类型和目标类型提供一个正式的定义。

幸运的是,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会自动注册对大部分环境都适用的转换器,这其中包括了集合转换器、标量转换器还有基本的ObjectString的转换器。可以通过调用DefaultConversionService类上的静态方法addDefaultConverters来向任意的ConverterRegistry注册相同的转换器。

因为值类型的转换器可以被数组和集合重用,所以假设标准集合处理是恰当的,就没有必要创建将一个SCollection转换成一个TCollection的特定转换器。


书籍推荐