Spring 3 引入了 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);
}
要创建你自己的转换器,请实现转换器接口并将 S 作为你要转换的类型,T 作为你要转换的目标类型。如果需要将 S 的集合或数组转换为 T 的数组或集合,你也可以透明地应用这样一个转换器,前提是已经注册了一个委托数组或集合转换器(DefaultConversionService 默认如此)。
对于每个对 convert(S)
的调用,source 参数被保证不为空。如果转换失败,你的转换器可以抛出任何未经检查的异常。特别是,它应该抛出一个 IllegalArgumentException 来报告一个无效的 source 值。请注意确保你的转换器实现是线程安全的。
core.convert.support
包中提供了几个转换器的实现,作为一种便利。这些包括从字符串到数字和其他常见类型的转换器。下面的列表显示了 StringToInteger 类,它是一个典型的转换器实现:
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
当你需要一个复杂的转换器实现时,可以考虑使用 GenericConverter 接口。GenericConverter 具有比 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 会对声明目标集合类型的字段进行反省,以解决集合的元素类型。这让源数组中的每个元素在集合被设置在目标字段上之前被转换为集合元素类型。
:::info 因为 GenericConverter 是一个更复杂的 SPI 接口,你应该只在需要时使用它。对于基本的类型转换需求,请青睐 Converter 或 ConverterFactory。 :::
使用 ConditionalGenericConverter
有时,你想让一个转换器只在一个特定的条件成立的情况下运行。例如,你可能想只在目标字段上存在特定注释的情况下运行一个转换器,或者你可能想只在目标类上定义了特定的方法(比如静态 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)
的实现中执行这样的查找方法检查。
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 会拾取该转换服务并使用它。你也可以将这个转换服务注入到你的任何 Bean 中并直接调用它。
:::info 如果没有向 Spring 注册转换服务,就会使用基于 PropertyEditor 的原始系统。 :::
要向 Spring 注册一个默认的 ConversionService,请添加以下 bean 定义,其 id 为 conversionService。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的 ConversionService 可以在字符串、数字、枚举、集合、地图和其他常见类型之间转换。要用你自己的自定义转换器来补充或覆盖默认的转换器,请设置转换器属性。属性值可以实现 Converter、ConverterFactory 或 GenericConverter 的任何接口:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
可以看到如下图所示:使用默认的 ConversionServiceFactoryBean,会注册 52 个转换器
在 Spring MVC 应用程序中使用 ConversionService 也很常见。参见 Spring MVC 章节中的转换和格式化。
在某些情况下,你可能希望在转换过程中应用格式化。关于使用 FormattingConversionServiceFactoryBean 的细节,请参见 The FormatterRegistry SPI。
以编程方式使用 ConversionService
要以编程方式处理 ConversionService 实例,你可以像处理其他 Bean 一样注入对它的引用。下面的例子展示了如何做到这一点:
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
对于大多数用例,你可以使用指定 targetType 的转换方法,但对于更复杂的类型,如一个参数化元素的集合,它是不起作用的。例如,如果你想以编程方式将 List of Integer 转换成 List of String,你需要提供源和目标类型的正式定义。
幸运的是,TypeDescriptor 提供了各种选项,使得这样做很直接,正如下面的例子所示:
package cn.mrcode.study.springdocsread.data;
import com.fasterxml.jackson.databind.util.Converter;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.PropertyValue;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import java.util.Arrays;
import java.util.List;
/**
* @author mrcode
*/
public class DemoTest {
public static void main(String[] args) {
// 使用默认的转换服务
DefaultConversionService cs = new DefaultConversionService();
// 将 List<Integer> 转换为 List<String>
List<Integer> input = Arrays.asList(1, 2, 3);
final List<String> dest = (List<String>) cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
System.out.println(dest);
}
}
注意,DefaultConversionService 会自动注册适合大多数环境的转换器。这包括集合转换器、标量转换器和基本的对象到字符串的转换器。你可以通过使用 DefaultConversionService 类的静态 addDefaultConverters 方法在任何 ConverterRegistry 中注册相同的转换器。
值类型的转换器被重复用于数组和集合,所以没有必要创建一个特定的转换器来将 S 的集合转换为 T 的集合,假设标准的集合处理是合适的。