Як говорилося в попередньому розділі, 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:
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;
}
}
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);
}
Щоб створити реалізацію:
Параметризуй A як
annotationTypeполя, з яким ти хочеш пов'язати логіку форматування — наприклад,org.springframework.format.annotation.DateTimeFormat.Нехай
getFieldTypes()повертає типи полів, для яких може бути використана анотація.Нехай
getPrinter()повертаєPrinterдля виведення значення анотованого поля.Нехай
getParser()повертаєParserдля синтаксичного аналізуclientValueдля анотованого поля.
Наступний приклад реалізації AnnotationFormatterFactory прив'язує анотацію @NumberFormat до форматувальника, щоб можна було вказати стиль або шаблон нумерації:
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();
}
}
}
}
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, як показано в наступному прикладі:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
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:
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
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.DateTimeFormatterRegistrarorg.springframework.format.datetime.DateFormatterRegistrar
Наприклад, наступна конфігурація 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;
}
}
@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>
Зверни увагу, що при налаштуванні форматів дати та часу у вебдодатках необхідно враховувати додаткові фактори.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ