JavaRush/Java курси/Модуль 5. Spring/Перетворення типу на Spring

Перетворення типу на Spring

Відкрита

У 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, якщо виходити з того, що стандартна обробка колекцій буде доречною.

Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.