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 包中提供了多个转换器实现,以方便使用。其中包括从字符串到数字和其他常见类型的转换器。以下清单显示了 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 接口,因此您应仅在需要时使用它。对于基本类型转换需求,请优先使用 ConverterConverterFactory

使用 ConditionalGenericConverter

有时,您希望仅在特定条件为真时才运行 Converter。例如,您可能希望仅在目标字段上存在特定注释时才运行 Converter,或者您可能希望仅在目标类上定义特定方法(例如 static valueOf 方法)时才运行 ConverterConditionalGenericConverterGenericConverterConditionalConverter 接口的并集,它允许您定义此类自定义匹配条件

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 实现委托给其注册的转换器来执行类型转换逻辑。

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

配置 ConversionService

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

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

要向 Spring 注册默认 ConversionService,请添加 idconversionService 的以下 bean 定义

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

默认的 ConversionService 可以转换字符串、数字、枚举、集合、映射和其他常见类型。要使用自己的自定义转换器补充或覆盖默认转换器,请设置 converters 属性。属性值可以实现任何 ConverterConverterFactoryGenericConverter 接口。

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

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

幸运的是,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 会自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器和基本的 ObjectString 转换器。您可以使用 DefaultConversionService 类上的静态 addDefaultConverters 方法将相同的转换器注册到任何 ConverterRegistry

值类型的转换器会重复用于数组和集合,因此无需创建特定转换器来从 CollectionS 转换为 CollectionT,假设标准集合处理是合适的。