В 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
в любой из ваших бинов и вызывать его напрямую.
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
программно, вы можете внедрить ссылку на него, как это делается для любого другого бина. В следующем примере показано, как это сделать:
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
Для большинства случаев использования можно задействовать метод convert
, указывающий targetType
, но он не сработает с более сложными типами, такими как коллекция параметризованного элемента. Например, если вам потребуется программно преобразовать List
типа Integer
в List
типа String
, то необходимо предоставить формальное определение исходного и целевого типов.
К счастью, TypeDescriptor
предоставляет различные опции, позволяющие сделать это так же просто, как показано в следующем примере:
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // Дескриптор типа List<Integer>
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
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
, если исходить из того, что стандартная обработка коллекций будет уместной.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ