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 接口。与 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,请添加以下 Bean 定义,并将其id设置为conversionService

<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方法,但它不适用于更复杂的类型,例如参数化元素的集合。例如,如果要以编程方式将IntegerList转换为StringList,则需要提供源类型和目标类型的正式定义。

幸运的是,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中注册相同的转换器。

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