As discussed in the previous section, core.convert
is a general-purpose type conversion system. It provides a unified ConversionService
API as well as a strongly typed SPI Converter
interface for implementing conversion logic from one type to another. The Spring container uses this system to bind bean property values. Additionally, both Spring Expression Language (SpEL) and DataBinder
use this system to bind field values. For example, if SpEL needs to convert Short
to Long
to complete the expression.setValue(Object bean, Object value)
attempt, the core.convert
performs this conversion.
Now consider the type conversion requirements in a typical client environment, such as a web or desktop application. In such environments, conversion from String
is typically done to support the client-side postback process, and also back to String
to support the view rendering process. In addition, localization of String
values is often required. The more general SPI interface core.convert Converter
does not address such formatting requirements directly. To address these directly, Spring 3 introduced a convenient SPI interface, Formatter
, which provides a simple and robust alternative to implementing PropertyEditor
for client environments.
In general, You can use the Converter
SPI interface if you need to implement general purpose type conversion logic - for example, to convert between java.util.Date
and Long
. You can use the SPI Formatter
interface if you are working in a client environment (such as a web application) and need to parse and output localized field values. ConversionService
provides a single type conversion API for both SPI interfaces.
SPI Interface Formatter
SPI Interface Formatter
for implementing field formatting logic is simple and strongly typed. The following listing shows the Formatter
interface definition:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
extends from the Printer
and Parser
building block interfaces. The following listing shows the definitions of these two interfaces:
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;
}
To create your own Formatter
, implement the Formatter
interface shown earlier. Parameterize T
as the type of object you want to format - for example, java.util.Date
. Implement the print()
operation to print an instance of T
for display in the client locale. Implement a parse()
operation to parse an instance of T
from the formatted representation returned from the client locale. Your Formatter
should throw ParseException
or IllegalArgumentException
exceptions when a parse attempt fails. Make sure your Formatter
implementation is thread-safe.
The format
subpackages contain multiple Formatter
implementations for convenience. The Number
package contains NumberStyleFormatter
, CurrencyStyleFormatter
, and PercentStyleFormatter
for formatting Number
objects that use java.text.NumberFormat
. The datetime
package contains a DateFormatter
for formatting java.util.Date
objects with java.text.DateFormat
.
The following DateFormatter
is an example implementation of 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
}
}
The Spring team welcomes community participation in the development of Formatter
. To make your suggestions, see "GitHub Issues".
Annotation Driven Formatting
Field formatting can be configured by field or annotation type. To bind an annotation to Formatter
, implement AnnotationFormatterFactory
. The following listing shows the definition of the AnnotationFormatterFactory
interface:
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);
}
To create an implementation:
Parameterize A as
annotationType
fields, with which you want to associate formatting logic - for example,org.springframework.format.annotation.DateTimeFormat
.Let
getFieldTypes()
returns the field types for which the annotation can be used.Let
getPrinter()
returnPrinter
to print the value the annotated field.Let
getParser()
return aParser
to parse theclientValue
for the annotated field fields.
The following example implementation of AnnotationFormatterFactory
binds the @NumberFormat
annotation to the formatter so that you can specify a numbering style or pattern :
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()
}
}
}
}
To force formatting, you can annotate fields with @NumberFormat, as shown in the following example:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
Format Annotation API
In package org.springframework.format.annotation
there is Portable Formats Annotation API. You can use @NumberFormat
to format Number
fields such as Double
and Long
, and @DateTimeFormat
to format java.util.Date
, java.util.Calendar
, Long
(for millisecond timestamps), and java.time
from JSR-310.
The following example formats java.util.Date
as an ISO date (yyyy-MM-dd) @DateTimeFormat
is used:
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
class MyModel(
@DateTimeFormat(iso=ISO.DATE) private val date: Date
)
SPI interface FormatterRegistry
FormatterRegistry
is an SPI interface to register formatters and converters. FormattingConversionService
is an implementation of FormatterRegistry
suitable for most environments. You can programmatically or declaratively configure this option as a Spring bean, for example using FormattingConversionServiceFactoryBean
. Because this implementation also implements ConversionService
, you can directly configure it for use with Spring's DataBinder
and Spring Expression Language (SpEL).
As follows: The listing shows the SPI interface 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);
}
As follows from the previous listing, you can register formatters by field type or by annotation.
SPI interface FormatterRegistry
allows you to configure formatting rules centrally, instead of duplicating such configuration for all controllers. For example, you can force all date fields to be formatted in a certain way, or that fields with a certain annotation be formatted in a certain way. With a generic FormatterRegistry
you define these rules once, and they are applied whenever formatting is required.
SPI interface FormatterRegistrar
FormatterRegistrar
is an SPI interface for registering formatters and converters via FormatterRegistry. The following listing shows its interface definition:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
FormatterRegistrar
is useful when registering multiple related converters and formatters for a specific category of formatting, such as date formatting. It can also be useful if declarative registration is not sufficient - for example, if the formatter needs to be indexed under a specific field type other than its own <T>
, or if a Printer
pair is registered /Parser
. The next section provides more information about registering converters and formatters.
Configuring formatting in Spring MVC
Configuring the global date and time format
Default date and time fields , not annotated with @DateTimeFormat
, are converted from strings using the DateFormat.SHORT
style. If you wish, you can change this by defining your own global format.
To do this, make sure that Spring does not register default formatters. Instead, register formatters manually with:
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
org.springframework.format.datetime.DateFormatterRegistrar
For example, the following Java configuration registers a global format of the form yyyyMMdd
:
@Configuration
public class AppConfig {
@Bean
public FormattingConversionService conversionService() {
// Use DefaultFormattingConversionService, but don't register default values
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
// Make sure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register the JSR-310 date conversion in a specific global format
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
registrar.registerFormatters(conversionService);
// Register the date conversion in a specific global format
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
registrar.registerFormatters(conversionService);
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): FormattingConversionService {
// Use DefaultFormattingConversionService, but don't register default values
return DefaultFormattingConversionService(false).apply {
// Make sure @NumberFormat is still supported
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
// Register JSR-310 date conversion in a specific global format
val registrar = DateTimeFormatterRegistrar ()
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
registrar.registerFormatters(this)
// Register the date conversion in a specific global format
val registrar = DateFormatterRegistrar()
registrar.setFormatter(DateFormatter("yyyyMMdd"))
registrar.registerFormatters (this)
}
}
}
If you prefer XML-based configuration, you can use FormattingConversionServiceFactoryBean
. The following example shows , how to do this:
<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>
Please note that there are additional factors to consider when configuring date and time formats in web applications.
GO TO FULL VERSION