Как говорилось в предыдущем разделе, 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<? extends Annotation> 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 регистрирует глобальный формат вида ггггММдд
:
@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>
Обратите внимание, что при настройке форматов даты и времени в веб-приложениях необходимо учитывать дополнительные факторы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ