JavaRush /Курси /Модуль 5. Spring /Форматування поля в Spring

Форматування поля в Spring

Модуль 5. Spring
Рівень 4 , Лекція 10
Відкрита

Як говорилося в попередньому розділі, core.convert — це система перетворення типів загального призначення. Вона надає уніфікований API-інтерфейс ConversionService, а також строго типізований SPI-інтерфейс Converter для реалізації логіки перетворення з одного типу на інший. Контейнер Spring використовує цю систему для прив'язування значень властивостей біна. До того ж, як мова виразів Spring Expression Language (SpEL), так і DataBinder використовують цю систему для прив'язування значень полів. Наприклад, якщо SpEL необхідно перетворити Short на Long для завершення спроби expression.setValue(Object bean, Object value), система core. convert виконує це перетворення.

Тепер розглянемо вимоги до перетворення типів у типовому клієнтському середовищі, такому як вебдодаток або програма для настільних систем. У таких середовищах перетворення із String зазвичай здійснюється для забезпечення процесу зворотної передачі клієнтської частини, а також назад у String для забезпечення процесу візуалізації подання. Крім того, часто потрібна локалізація значень String. Більш загальний інтерфейс SPI core.convert Converter не задовольняє такі вимоги до форматування безпосередньо. Для їхнього безпосереднього задоволення у Spring 3 з'явився зручний SPI-інтерфейс Formatter, який забезпечує просту та надійну альтернативу реалізації PropertyEditor для клієнтських середовищ.

Загалом, можна використовувати SPI-інтерфейс Converter, якщо тобі потрібно реалізувати логіку перетворення типів загального призначення — наприклад, для здійснення перетворення між java.util.Date та Long. Можна використовувати інтерфейс SPI Formatter, якщо ти працюєш у клієнтському середовищі (наприклад, у вебдодатку) і необхідно провести синтаксичний аналіз і вивести локалізовані значення полів. ConversionService надає єдиний API-інтерфейс перетворення типів для обох SPI-інтерфейсів.

SPI-інтерфейс Formatter

SPI-інтерфейс Formatter для реалізації логіки форматування полів є простим та строго типізованим. У наступному лістингу показано визначення інтерфейсу Formatter:


package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter розширюється з інтерфейсів конструктивних блоків Printer та Parser. У наступному лістингу показано визначення цих двох інтерфейсів:


public interface Printer<T> {
    String print(T fieldValue, Locale locale);
}

import java.text.ParseException;
public interface Parser<T> {
    T parse(String clientValue, Locale locale) throws ParseException;
}

Щоб створити свій власний Formatter, реалізуй інтерфейс Formatter, показаний раніше. Параметризуй T як тип об'єкта, який потрібно відформатувати, наприклад, java.util.Date. Реалізуй операцію print(), щоб вивести екземпляр T для відображення в локалі клієнта. Реалізуй операцію parse(), щоб здійснити синтаксичний розбір екземпляра T з форматованого представлення, що повертається з клієнтської локалі. Твій Formatter повинен генерувати винятки ParseException або IllegalArgumentException у разі невдалої спроби синтаксичного аналізу. Подбай, щоб твоя реалізація Formatter була потокобезпечною.

Підпакети format для зручності містять кілька реалізацій Formatter. Пакет Number містить NumberStyleFormatter, CurrencyStyleFormatter та PercentStyleFormatter для форматування об'єктів Number, які використовують java.text.NumberFormat. Пакет datetime містить DateFormatter для форматування об'єктів java.util.Date за допомогою java.text.DateFormat.

Наступний DateFormatter є прикладом реалізації Formatter:

Java

package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
    private String pattern;
    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }
    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }
    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }
    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
Kotlin

class DateFormatter(private val pattern: String) : Formatter<Date> {
    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)
    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)
    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}

Команда Spring вітає участь спільноти у розробці Formatter. Для внесення своїх пропозицій див. "GitHub Issues".

Форматування, кероване анотаціями

Форматування полів можна налаштувати за типом поля або анотації. Щоб прив'язати анотацію до Formatter, реалізуй AnnotationFormatterFactory. У наступному лістингу показано визначення інтерфейсу AnnotationFormatterFactory:


package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
    Set<Class<?>> getFieldTypes();
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

Щоб створити реалізацію:

  1. Параметризуй A як annotationType поля, з яким ти хочеш пов'язати логіку форматування — наприклад, org.springframework.format.annotation.DateTimeFormat.

  2. Нехай getFieldTypes() повертає типи полів, для яких може бути використана анотація.

  3. Нехай getPrinter() повертає Printer для виведення значення анотованого поля.

  4. Нехай getParser() повертає Parser для синтаксичного аналізу clientValue для анотованого поля.

Наступний приклад реалізації AnnotationFormatterFactory прив'язує анотацію @NumberFormat до форматувальника, щоб можна було вказати стиль або шаблон нумерації:

Java

public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {
    public Set<Class
            <?>> getFieldTypes() {
        return new HashSet<Class
            <?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }
    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }
    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
Kotlin

class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }
    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }
    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
}

Щоб викликати форматування, можна анотувати поля за допомогою @NumberFormat, як показано в наступному прикладі:

Java

public class MyModel {
    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
Kotlin

class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)

API-інтерфейс анотації формату

У пакеті org.springframework.format.annotation існує API анотацій форматів, що переносяться. Можна використовувати @NumberFormat для форматування Number полів, таких як Double та Long, та @DateTimeFormat для форматування java.util.Date, java.util.Calendar, Long (для мілісекундних міток часу), а також java.time з JSR-310.

У наступному прикладі для форматування java.util.Date як дати за стандартом ISO (рррр-ММ-дд) використовується @DateTimeFormat:

Java
public class MyModel {
    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
Kotlin
class MyModel(
    @DateTimeFormat(iso=ISO.DATE) private val date: Date
)

SPI-інтерфейс FormatterRegistry

FormatterRegistry — це SPI-інтерфейс для реєстрації форматувальників та перетворювачів. FormattingConversionService — це реалізація FormatterRegistry, яка підходить для більшості оточень. Ти можеш програмно або декларативно налаштувати цей варіант як бін Spring, наприклад, за допомогою FormattingConversionServiceFactoryBean. Оскільки ця реалізація також реалізує ConversionService, можна безпосередньо налаштувати її для використання з DataBinder зі Spring та мовою виразів Spring Expression Language (SpEL).

У наступному лістингу показаний SPI-інтерфейс FormatterRegistry:


package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
    void addPrinter(Printer
        
    
        
    
        
     printer);
    void addParser(Parser
        
    
        
    
        
     parser);
    void addFormatter(Formatter
        
    
        
    
        
     formatter);
    void addFormatterForFieldType(Class
        
    
        
    
        
     fieldType, Formatter
        
    
        
    
        
     formatter);
    void addFormatterForFieldType(Class
        
    
        
    
        
     fieldType, Printer
        
    
        
    
        
     printer, Parser
        
    
        
    
        
     parser);
    void addFormatterForFieldAnnotation(AnnotationFormatterFactory
        
    
        
    
        
     annotationFormatterFactory);
}

Як випливає з попереднього лістингу, можна реєструвати форматувальники за типом поля або анотацією.

SPI-інтерфейс FormatterRegistry дозволяє налаштовувати правила форматування централізовано замість того, щоб дублювати таку конфігурацію для всіх контролерів. Наприклад, можна примусово встановити, щоб усі поля дати були відформатовані певним чином або щоб поля з певною анотацією були відформатовані певним чином. За допомогою спільного FormatterRegistry ти один раз визначаєш ці правила, а вони застосовуються щоразу, коли потрібно форматування.

SPI-інтерфейс FormatterRegistrar

FormatterRegistrar — це SPI-інтерфейс для реєстрації форматувальників та перетворювачів через FormatterRegistry. У наступному лістингу показано визначення його інтерфейсу:

package org.springframework.format;
public interface FormatterRegistrar {
    void registerFormatters(FormatterRegistry registry);
}

FormatterRegistrar корисний при реєстрації декількох пов'язаних перетворювачів і форматувальників для певної категорії форматування, наприклад форматування дати. Він також може бути корисним, якщо декларативної реєстрації недостатньо — наприклад, якщо форматувальник потрібно проіндексувати під певний тип поля, відмінний від його власного <T>, або якщо реєструється пара Printer /Parser. У наступному розділі наведено додаткову інформацію про реєстрацію перетворювачів та форматувальників.

Конфігурування форматування у Spring MVC

Конфігурування глобального формату дати та часу

За замовчуванням поля дати та часу, не анотовані @DateTimeFormat, перетворюються з рядків за допомогою стилю DateFormat.SHORT. За бажанням можна змінити це, визначивши свій власний глобальний формат.

Для цього переконайся, щоб Spring не реєстрував форматувальники за замовчуванням. Натомість реєструй форматувальники вручну за допомогою:

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar

  • org.springframework.format.datetime.DateFormatterRegistrar

Наприклад, наступна конфігурація Java реєструє глобальний формат вигляду ррррММдд:

Java
@Configuration
public class AppConfig {
    @Bean
    public FormattingConversionService conversionService() {
        // Використовуємо DefaultFormattingConversionService, але не реєструємо значення за замовчуванням
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
        // Перевіримо, що @NumberFormat все ще підтримується
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
        // Реєструємо перетворення дати за стандартом JSR-310 у певному глобальному форматі
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        // Реєструємо перетворення дати у певному глобальному форматі
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);
        return conversionService;
    }
}
Kotlin

@Configuration
class AppConfig {
    @Bean
    fun conversionService(): FormattingConversionService {
        // Використовуємо DefaultFormattingConversionService, але не реєструємо значення за замовчуванням
        return DefaultFormattingConversionService(false).apply {
            // Перевіримо, що @NumberFormat все ще підтримується
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
            // Реєструємо перетворення дати за стандартом JSR-310 у певному глобальному форматі
            val registrar = DateTimeFormatterRegistrar()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)
            // Реєструємо перетворення дати у певному глобальному форматі
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters(this)
        }
    }
}

Якщо ти надаєш перевагу конфігурації на основі XML, то можеш використовувати FormattingConversionServiceFactoryBean.У наступному прикладі показано як це зробити:


<?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd>
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
            </bean>
            </beans>

Зверни увагу, що при налаштуванні форматів дати та часу у вебдодатках необхідно враховувати додаткові фактори.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ