|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
Spring 类型转换
core.convert 包提供了一个通用的类型转换系统。该系统定义了一个 SPI(服务提供者接口)用于实现类型转换逻辑,以及一个 API 用于在运行时执行类型转换。在 Spring 容器中,你可以使用此系统作为 PropertyEditor 实现的替代方案,将外部化的 Bean 属性值字符串转换为所需的属性类型。你也可以在应用程序中任何需要类型转换的地方使用该公共 API。
转换器 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 包中提供了多个转换器(Converter)实现。这些实现包括从字符串到数字以及其他常见类型的转换器。
以下代码清单展示了 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 接口。与 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()方法返回所支持的源类型→目标类型对。然后实现convert(Object, TypeDescriptor,
TypeDescriptor)方法,在其中编写你的转换逻辑。源TypeDescriptor提供了对包含待转换值的源字段的访问,而目标TypeDescriptor则提供了对将要设置转换后值的目标字段的访问。
GenericConverter 的一个很好的示例是用于在 Java 数组和集合之间进行转换的转换器。这种 ArrayToCollectionConverter 会内省声明目标集合类型的字段,以解析集合的元素类型。这样,在将集合设置到目标字段之前,源数组中的每个元素都可以被转换为集合的元素类型。
由于 GenericConverter 是一个更复杂的 SPI 接口,因此仅在确实需要时才应使用它。对于基本的类型转换需求,应优先选择 Converter 或 ConverterFactory。 |
使用ConditionalGenericConverter
有时,你希望仅在特定条件成立时才运行某个 Converter。例如,你可能希望仅当目标字段上存在某个特定注解时才运行该 Converter,或者仅当目标类中定义了某个特定方法(例如 Converter 方法)时才运行该 static valueOf。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) 的实现中执行此类查找方法的检查。
这ConversionServiceAPI
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 容器(或 ConversionService)配置一个 ApplicationContext 实例。Spring 会自动获取该 ConversionService,并在框架需要执行类型转换时使用它。你也可以将此 ConversionService 注入到任意 Bean 中,并直接调用它。
如果 Spring 中未注册 ConversionService,则使用基于原始 PropertyEditor 的系统。 |
要向 Spring 注册一个默认的 ConversionService,请添加以下 bean 定义,并将其 id 设为 conversionService:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的 ConversionService 可以在字符串、数字、枚举、集合、映射(map)以及其他常见类型之间进行转换。若要使用您自己的自定义转换器来补充或覆盖默认的转换器,请设置 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 章节中的转换与格式化。
在某些情况下,您可能希望在转换过程中应用格式化。请参阅
FormatterRegistry SPI 以了解如何使用 FormattingConversionServiceFactoryBean 的详细信息。
使用一个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(...)
}
}
对于大多数使用场景,你可以使用指定 convert 的 targetType 方法,但它无法处理更复杂的类型,例如包含参数化元素的集合。
例如,如果你想以编程方式将一个 List 的 Integer 转换为 List 的 String,
就需要提供源类型和目标类型的正式定义。
幸运的是,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 的转换器。你可以通过 ConverterRegistry 类的静态方法 addDefaultConverters,将相同的转换器注册到任意 DefaultConversionService 中。
值类型的转换器可被复用于数组和集合,因此无需专门创建一个从 Collection 的 S 转换为 Collection 的 T 的转换器,前提是标准的集合处理方式适用。