Spring 类型转换
core.convert
包提供了一个通用的类型转换系统。该系统定义了一个 SPI 来实现类型转换逻辑,并提供了一个 API 在运行时执行类型转换。在 Spring 容器中,你可以使用该系统作为 PropertyEditor
实现的替代方案,将外部化的 bean 属性值字符串转换为所需的属性类型。你还可以在应用程序中任何需要类型转换的地方使用公共 API。
Converter SPI
要实现类型转换逻辑的 SPI 简单且强类型化,正如以下接口定义所示:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
要创建自己的转换器,实现 Converter
接口,并将 S
参数化为您要转换的类型,将 T
参数化为您要转换到的类型。如果需要将 S
的集合或数组转换为 T
的数组或集合,也可以透明地应用这样的转换器,前提是已经注册了一个委托数组或集合转换器(DefaultConversionService
默认会这样做)。
对于每次调用 convert(S)
,可以保证源参数不为 null。如果转换失败,你的 Converter
可以抛出任何未检查的异常。特别是,它应该抛出 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);
}
}
使用 ConverterFactory
当你需要将整个类层次结构的转换逻辑集中化时(例如,从 String
转换为 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 的子类。
以 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());
}
}
}
使用 GenericConverter
当您需要一个复杂的 Converter
实现时,可以考虑使用 GenericConverter
接口。GenericConverter
的签名比 Converter
更灵活,但类型检查不如 Converter
强,它支持在多个源类型和目标类型之间进行转换。此外,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
有时,你希望一个 Converter
仅在特定条件满足时运行。例如,你可能希望仅在目标字段上存在特定注解时运行一个 Converter
,或者你可能希望仅在目标类上定义了特定方法(例如 static valueOf
方法)时运行一个 Converter
。ConditionalGenericConverter
是 GenericConverter
和 ConditionalConverter
接口的结合,它允许你定义这样的自定义匹配条件:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
一个 ConditionalGenericConverter
的一个好例子是 IdToEntityConverter
,它在持久化实体标识符和实体引用之间进行转换。这样的 IdToEntityConverter
可能仅在目标实体类型声明了一个静态查找方法(例如,findAccount(Long)
)时匹配。你可以在 matches(TypeDescriptor, TypeDescriptor)
的实现中执行这样的查找方法检查。
ConversionService
API
ConversionService
定义了一个统一的 API,用于在运行时执行类型转换逻辑。转换器通常在以下外观接口后面运行:
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
实现委托给其注册的转换器来执行类型转换逻辑。
一个强大的 ConversionService
实现位于 core.convert.support
包中。GenericConversionService
是通用实现,适用于大多数环境。ConversionServiceFactory
提供了一个方便的工厂,用于创建常见的 ConversionService
配置。
配置 ConversionService
ConversionService
是一个无状态对象,旨在应用程序启动时实例化,然后在多个线程之间共享。在 Spring 应用程序中,通常为每个 Spring 容器(或 ApplicationContext
)配置一个 ConversionService
实例。Spring 会获取该 ConversionService
,并在框架需要执行类型转换时使用它。您还可以将此 ConversionService
注入到任何一个 bean 中并直接调用它。
如果没有 ConversionService
注册到 Spring,则使用基于原始 PropertyEditor
的系统。
要在 Spring 中注册一个默认的 ConversionService
,请添加以下 bean 定义,并将其 id
设置为 conversionService
:
<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 章节中的转换和格式化。
在某些情况下,您可能希望在转换过程中应用格式化。有关使用 FormattingConversionServiceFactoryBean
的详细信息,请参见 FormatterRegistry SPI。
程序化地使用 ConversionService
要以编程方式使用 ConversionService
实例,您可以像注入其他 bean 一样注入对它的引用。以下示例展示了如何实现:
- Java
- Kotlin
@Service
public class MyService {
private final ConversionService conversionService;
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
对于大多数用例,您可以使用指定 targetType
的 convert
方法,但它不适用于更复杂的类型,例如参数化元素的集合。例如,如果您想以编程方式将 Integer
的 List
转换为 String
的 List
,则需要提供源类型和目标类型的正式定义。
幸运的是,TypeDescriptor
提供了多种选项,使得这样做变得简单,如下例所示:
- Java
- Kotlin
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)));
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
请注意,DefaultConversionService
会自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器和基本的 Object
到 String
的转换器。您可以通过在 DefaultConversionService
类上使用静态方法 addDefaultConverters
,将相同的转换器注册到任何 ConverterRegistry
中。
值类型的转换器会被重用于数组和集合,因此不需要创建特定的转换器来将 Collection
的 S
转换为 Collection
的 T
,假设标准集合处理是合适的。