Spring Type Conversion

core.convert 包提供了一个通用类型转换系统。该系统定义了一个用于实现类型转换逻辑的 SPI 和一个用于在运行时执行类型转换的 API。在 Spring 容器中,你可以将此系统用作 PropertyEditor 实现的替代方案,以将外部化 Bean 属性值字符串转换为所需的属性类型。你还可以在你需要类型转换的应用程序中的任何地方使用公共 API。

Converter SPI

用于实现类型转换逻辑的 SPI 非常简单且类型化,如下面的接口定义所示:

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 实现:

final class StringToInteger implements Converter<String, Integer> {

	public Integer convert(String source) {
		return Integer.valueOf(source);
	}
}

Using ConverterFactory

当需要集中转换整个类层次结构的逻辑时(例如,从 String 转换为 Enum 对象时),你可以实现 ConverterFactory,如下面的示例所示:

public interface ConverterFactory<S, R> {

	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

将 S 参数化为你正在从中转换的类型,将 R 参数化为定义你可以转换为的类的 范围 的基类型。然后实现 getConverter(Class<T>),其中 T 是 R 的子类。

StringToEnumConverterFactory 为例:

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

Using GenericConverter

当你需要一个复杂的 Converter 实现时,请考虑使用 GenericConverter 接口。GenericConverter 具有比 Converter 更灵活但类型化程度较差的签名,支持在多个源和目标类型之间进行转换。此外,GenericConverter 提供了源和目标字段上下文,你可以在实现转换逻辑时使用它。此类上下文允许类型转换由字段注释或在字段签名上声明的泛型信息驱动。以下清单显示了 GenericConverter 的接口定义:

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

Using 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) 的实现中执行此类查找器方法检查。

The ConversionService API

ConversionService 定义了一个统一的 API,用于在运行时执行类型转换逻辑。转换器通常在以下 facades 接口后面运行:

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 配置的工厂。

Configuring a ConversionService

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

如果没有向 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 章节中的Conversion and Formatting

在某些情况下,你可能希望在转换期间应用格式。有关使用 FormattingConversionServiceFactoryBean 的详细信息,请参阅 The FormatterRegistry SPI

Using a ConversionService Programmatically

要以编程方式使用 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,假设标准集合处理是适当的。