# 3.4. spring类型转换

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

**3.4.1. 转换器 SPI**

实现类型转换逻辑的 SPI 很简单，而且是强类型的，如下面的接口定义所示：

```java
package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}
```

要创建您自己的转换器，请实现 `Converter` 接口并将 `S` 参数化为要转换的类型，将`T`参数化为要转换的目标类型。如果需要将 `S` 的集合或数组转换为`T`的数组或集合，您还可以透明地应用此类转换器，前提是委托数组或集合转换器也已注册（默认情况下 `DefaultConversionService` 会注册）。

对于每次调用`convert(S)`，源参数保证不为空。如果转换失败， `Converter`可能会抛出任何未经检查的异常。具体来说，它应该抛出一个 `IllegalArgumentException`报告无效的源值。注意确保您的`Converter`实现是线程安全的。

为方便起见，软件包`core.convert.support`中提供了几个转换器实现。这些包括从字符串到数字和其他常见类型的转换器。下面的清单显示了这个`StringToInteger`类，它是一个典型的`Converter`实现：

```java
package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

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

**3.4.2. 使用`ConverterFactory`**

当您需要集中整个类层次结构的转换逻辑时（例如，从转换`String`为`Enum`对象时），您可以实现 `ConverterFactory`，如以下示例所示：

```java
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`作为一个例子：

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

**3.4.3. 使用`GenericConverter`**

当您需要复杂的`Converter`实现时，请考虑使用 `GenericConverter`接口。与`Converter` 相比，`GenericConverter` 具有更灵活但类型更弱的签名，支持在多个源类型和目标类型之间进行转换。此外，`GenericConverter`提供了可用的源和目标字段上下文，您可以在实现转换逻辑时使用它们。这样的上下文允许类型转换由字段注解或字段签名上声明的通用信息驱动。以下清单显示了 `GenericConverter`的接口定义：

```java
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`，或者仅当目标类上定义了特定方法（例如静态`valueOf`方法）时您可能希望运行 `Converter`。 `ConditionalGenericConverter` 是 `GenericConverter` 和`ConditionalConverter`接口的联合，可让您定义此类自定义匹配条件：

```java
public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
```

`ConditionalGenericConverter`很好的例子是`IdToEntityConverter`在持久实体标识符和实体引用之间进行转换。`IdToEntityConverter` 只有当目标实体类型声明了静态查找器方法（例如， `findAccount(Long)`）时，这样的才可能匹配。您可以在`matches(TypeDescriptor, TypeDescriptor)`的实现中执行此类查找器方法检查。

**3.4.4. `ConversionService`API**

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

```java
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` 配置。

**3.4.5. 配置`ConversionService`**

`ConversionService`是一个无状态对象，旨在在应用程序启动时实例化，然后在多个线程之间共享。在 Spring 应用程序中，您通常为每个 Spring 容器（或`ApplicationContext`）配置一个`ConversionService`实例。每当框架需要执行类型转换时，Spring 就会选择并使用`ConversionService`。您还可以将`ConversionService` 注入任何 bean 并直接调用。

如果没有 Spring 向注册`ConversionService`，则使用默认的`PropertyEditor`。

要使用 Spring 注册默认值，添加以下 `id` 为 `conversionService` 的 `bean` 定义：

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

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

```xml
<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 章节中的[转换和格式化](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-config-conversion)。

在某些情况下，您可能希望在转换期间应用格式。有关使用参考 [The `FormatterRegistry` SPI](https://docs.spring.io/spring-framework/reference/core/validation/format.html#format-FormatterRegistry-SPI) for details on using `FormattingConversionServiceFactoryBean`.

**3.4.6. 以编程方式使用`ConversionService`**

要以编程方式使用`ConversionService`实例，您可以像对任何其他 bean 一样注入对它的引用。以下示例显示了如何执行此操作：

```java
@Service
public class MyService {

    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}
```

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

幸运的是，`TypeDescriptor`提供了各种选项来使操作变得简单，如以下示例所示：

```java
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)));
```

请注意，`DefaultConversionService`的自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器和基本`Object`到`String`转换器。您可以使用`DefaultConversionService`类上的静态`addDefaultConverters` 方法向`ConverterRegistry` 注册相同的转换器。

值类型的转换器可用于数组和集合，因此无需创建特定的转换器来从 `Collection <S>`转换为 `Collection <T>`，假设标准集合处理是合适的。
