首页 > 代码库 > 6.5 Spring3 类型转换

6.5 Spring3 类型转换

6.5 Spring3 类型转换

Spring3引入了core.convert包,提供了一个通用的类型转换系统。这个系统定义了一个SPI来实现类型转换逻辑。SPI就像API一样,用来在运行时执行类型转换。在Spring容器中,这个系统可以用来替代PropertyEditors,因为它也可以将bean的string属性值转换成所需的属性类型。在你的应用程序中,任何需要类型转换的地方都可以使用这些公共API。(下文中的Converter将被翻译成转换器)


Converter SPI

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

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

想要创建你自己的Converter,只需要实现上面的接口。参数S是转换前的类型,而T则是转换后的类型。每次调用convert(S)时,参数(原值)不能为null。你的转换器可能会在转换失败时抛出异常。若是一个无效的原值,那么应该抛出一个IllegalArgumentException。并且确保你的转换器实现是线程安全的。

为了方便起见,在core.convert.support包中提供了一些converter的实现类。其中包括了String和Number以及其他常见类型之间的转换。可以将下面这个Converter的实现类——StringToInteger当成一个小例子来学习:

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


转换器工厂

当你需要集中一个完整的类结构的转换逻辑时,比如,从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>),其中T是R的子类。

考虑下面的ConverterFactory ——StringToEnum:

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

GenericConverter(泛型转换器)

当你需要一个复杂的转换器实现时,可以考虑GenericConverter接口。它拥有一个相对弱类型但却更加灵活的方法头,GenericConverter支持多个原类型和目标类型的转换。另外,当你在实现你的转换逻辑时,GenericConverter还能够让你使用原字段和目标字段的上下文。这个上下文允许被属性(field)注解驱动的类型转换,或者是被泛型信息定义的类型转换:

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

实现一个GenericConverter接口时,getConvertibleTypes()需要返回所支持的源类型和目标类型的配对(译者注:这个方法在ConversionServiceFactory的registerConverters调用,之后会被缓存),convert(Object,TypeDescriptor, TypeDescriptor)方法用来定义你的转换逻辑。源TypeDescriptor提供了对源字段的访问且包含了需要转换的原值。而目标TypeDescriptor则提供了对目标字段的访问且用于放置转换后的值。

关于GenericConverter一个比较典型的例子就是Java数组类型和集合类型之间的转换。这个ArrayToCollectionConverter通过内省目标集合类型的字段来决定集合的元素类型。这允许源数组中的每一个元素在集合被设置到目标字段之前被转换成集合元素类型。

注意:由于GenericConverter是一个更为复杂的SPI接口,只有当你需要的时候再去使用它。对于基本的类型转换,优先使用Converter或者是ConverterFactory。


带条件的GenericConverter

有时你只想在满足某个特定条件的时候才执行一个Converter。比如,只有当目标属性上有一个特定的注解时才去执行一个Converter,或者当目标类中定义了一个特定的方法,比如一个静态的valueOf方法时,才去执行一个Converter。那么你可以使用ConditionalGenericConverter,它是GenericConverter的子接口,允许你定义刚才提到的那些匹配条件:

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

关于ConditionalGenericConverter的一个典型的例子是EntityConverter。它提供了一个持久的实体标识符和一个实体引用之间的转换。这样的EntityConverter可能只会匹配那些定义了一个静态finder方法(比如findAccount(Long))的目标实体。你需要在实现matches(TypeDescriptor,TypeDescriptor)方法时来检查是否存在这样的finder方法。

(译者注:关于matches方法,是在第一次需要类型转换的时候会被调用,之后就会被缓存下来)


ConversionService API

ConversionService为运行时执行类型转换逻辑定义了一个统一的API。Converters通常在这层门面接口之后运行:

	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接口。ConverterRegistry为注册converters提供了一个SPI。实际上,在ConversionService实现里会将类型转换的逻辑委托给注册了的converters。

core.convert.support包里提供了一个健壮的ConversionService实现。GenericConversionService作为一个通用的实现类,可以满足大多数情况。ConversionServiceFactory提供了一个方便的创建常用的ConversionService配置的工厂。

配置一个ConversionService

ConversionService是一个无状态的对象,且被设计成在应用启动时就被实例化,并在多个线程间共享。在Spring应用中一个典型的情况,为每个Spring容器(或是ApplicationContext)配置一个ConversionService实例。这个ConversionService将会生存在Spring中,并且在需要类型转换的时候被使用。你也可以将它注入到你的beans中并直接地调用它。

注意:如果Spring中没有注册ConversionService,那么它将会使用原始的基于PropertyEditor的系统。

为了在Spring中注册一个默认的ConversionService,添加下面的bean定义并且将它的id设置为conversionService:

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

默认的ConversionServcice能够转换strings、number、enums、collections、maps和其他常见类型。通过设置converters属性,可以用你自定义的converters来补充或覆盖默认的converters。这个converters属性的值必须要实现Converter、ConverterFactory或是GenericConverter接口。

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

在Spring MVC应用中使用ConversionService也是很常见的。阅读“ConfiguringFormatting in Spring MVC”这一节获得更多关于如何使用<mvc:annotation-driven/>的详细信息。

在某些场景下你可能希望在类型转换中应用格式化。阅读“FormatterRegistry SPI”这一节可以获得使用FormattingConversionServiceFactoryBean.的详细信息。


使用编程式的ConversionService

使用编程式的ConversionService实例,只需要注入一个ConversionService的引用即可,就像你注入其他bean一样:

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


6.5 Spring3 类型转换