В Spring 3 появился пакет core.convert, который предоставляет общую систему преобразования типов. Система определяет SPI (интерфейс поставщика служб) для реализации логики преобразования типов и API-интерфейса для осуществления преобразования типов во время выполнения программы. В контейнере Spring можно использовать эту систему как альтернативу реализации PropertyEditor для преобразования извлечённых строк значений свойств бина в требуемые типы свойств. Вы также можете использовать публичный API-интерфейс в любом месте вашего приложения, где требуется преобразование типов.

SPI-интерфейс Converter

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. 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 производит самоанализ поля, объявляющего тип целевой коллекции, чтобы определить тип элемента коллекции. Это позволяет преобразовать каждый элемент исходного массива в тип элемента коллекции до того, как коллекция будет установлена в целевое поле.

Поскольку GenericConverter является более сложным SPI-интерфейсом, его следует использовать только в случае необходимости. Для базовых нужд преобразования типов используйте Converter или ConverterFactory.

Использование ConditionalGenericConverter

Иногда требуется, чтобы Converter запускался только при выполнении определенного условия. Например, вам может потребоваться запустить Converter, только если в целевом поле присутствует определенная аннотация, или запустить Converter, только если в целевом классе определен конкретный метод (например, метод static 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).

API ConversionService

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 делегирует своим зарегистрированным преобразователям выполнение логики преобразования типов.

Надежная реализация ConversionService предоставляется в пакете core.convert.support. GenericConversionService – это реализация общего назначения, подходящая для использования в большинстве сред. ConversionServiceFactory предоставляет удобную фабрику для создания общих конфигураций ConversionService.

Конфигурирование ConversionService

ConversionService – это объект, не сохраняющий состояние, экземпляр которого создается при запуске приложения и последующего совместного использования несколькими потоками. В приложении на Spring экземпляр ConversionService обычно конфигурируется для каждого контейнера Spring (или ApplicationContext). Spring определяет этот ConversionService и использует его всякий раз, когда фреймворку нужно выполнить преобразование типа. Также можно внедрить этот ConversionService в любой из ваших бинов и вызывать его напрямую.

Если в Spring не зарегистрирован ConversionService, то используется оригинальная система на основе PropertyEditor.

Чтобы зарегистрировать в Spring стандартный ConversionService, добавьте следующее определение бина с указанием id в виде conversionService:

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

Стандартный ConversionService может преобразовывать строки, числа, перечисляемые типы, коллекции, ассоциативные массивы и другие распространенные типы. Чтобы дополнить или заменить преобразователи по умолчанию своими собственными специальными, установите свойство converters. Значения свойств могут реализовывать любой из интерфейсов Converter, ConverterFactory или GenericConverter.

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

Также в приложении на Spring MVC часто используется ConversionService.

В некоторых ситуациях можно применять форматирование во время преобразования.

Программное использование ConversionService

Чтобы работать с экземпляром ConversionService программно, вы можете внедрить ссылку на него, как это делается для любого другого бина. В следующем примере показано, как это сделать:

Java
@Service
public class MyService {
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }
    public void doIt() {
        this.conversionService.convert(...)
    }
}
Kotlin
@Service
class MyService(private val conversionService: ConversionService) {
    fun doIt() {
        conversionService.convert(...)
    }
}

Для большинства случаев использования можно задействовать метод convert, указывающий targetType, но он не сработает с более сложными типами, такими как коллекция параметризованного элемента. Например, если вам потребуется программно преобразовать List типа Integer в List типа String, то необходимо предоставить формальное определение исходного и целевого типов.

К счастью, TypeDescriptor предоставляет различные опции, позволяющие сделать это так же просто, как показано в следующем примере:

Java
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
    TypeDescriptor.forObject(input), // Дескриптор типа List<Integer>
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
Kotlin
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
        TypeDescriptor.forObject(input), // Дескриптор типа List<Integer>
        TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))

Обратите внимание, что DefaultConversionService автоматически регистрирует преобразователи, которые подходят для большинства сред. К ним принадлежат преобразователи коллекций, скалярные преобразователи и базовые преобразователи Object-to-String. Вы можете зарегистрировать те же преобразователи в любом ConverterRegistry с помощью статического метода addDefaultConverters класса DefaultConversionService.

Преобразователи для типов значений повторно используются для массивов и коллекций, поэтому нет необходимости создавать специализированный преобразователь для преобразования из Collection типа S в Collection типа T, если исходить из того, что стандартная обработка коллекций будет уместной.