annotation driven number formatting with default number formatting rules

This commit is contained in:
Keith Donald 2009-11-11 18:53:02 +00:00
parent bfffb51257
commit b56a47da97
12 changed files with 335 additions and 225 deletions

View File

@ -0,0 +1,80 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.format.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Declares that a field should be formatted as a number.
* Supports formatting by style or custom pattern string.
* Can be applied to any JDK <code>java.lang.Number</code> type.
* <p>
* For style-based formatting, set the {@link #style()} attribute to be the desired {@link Style}.
* For custom formatting, set the {@link #pattern()} attribute to be the number pattern, such as <code>#,###.##</code>.
* <p>
* Each attribute is mutually exclusive, so only set one attribute per annotation instance (the one most convenient one for your formatting needs).
* When the pattern attribute is specified, it takes precedence over the style attribute.
* When no annotation attributes are specified, the default format applied is style-based with a style of {@link Style#NUMBER}.
*
* @author Keith Donald
* @since 3.0
* @see org.joda.time.format.DateTimeFormat
*/
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface NumberFormat {
/**
* The style pattern to use to format the field.
* Defaults to {@link Style#NUMBER} for general-purpose number formatter.
* Set this attribute when you wish to format your field in accordance with a common style other than the default style.
*/
Style style() default Style.NUMBER;
/**
* The custom pattern to use to format the field.
* Defaults to empty String, indicating no custom pattern String has been specified.
* Set this attribute when you wish to format your field in accordance with a custom number pattern not represented by a style.
*/
String pattern() default "";
/**
* Common number format styles.
* @author Keith Donald
* @since 3.0
*/
public enum Style {
/**
* General-purpose number format for the current locale.
*/
NUMBER,
/**
* A currency format for the current locale.
*/
CURRENCY,
/**
* A percent format for the current locale.
*/
PERCENT
}
}

View File

@ -35,7 +35,6 @@ public abstract class AbstractNumberFormatter implements Formatter<Number> {
private boolean lenient = false;
/**
* Specify whether or not parsing is to be lenient. Default is false.
* <p>With lenient parsing, the parser may allow inputs that do not precisely match the format.
@ -45,7 +44,6 @@ public abstract class AbstractNumberFormatter implements Formatter<Number> {
this.lenient = lenient;
}
public String print(Number integer, Locale locale) {
return getNumberFormat(locale).format(integer);
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.format.number;
import java.text.NumberFormat;
import java.util.Locale;
/**
* A Number formatter for whole integer values.
*
* <p>Delegates to {@link NumberFormat#getIntegerInstance(Locale)}.
* The {@link #parse(String, Locale)} routine always returns a Long.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see #setLenient
*/
public final class IntegerFormatter extends AbstractNumberFormatter {
protected NumberFormat getNumberFormat(Locale locale) {
return NumberFormat.getIntegerInstance(locale);
}
}

View File

@ -21,7 +21,7 @@ import java.text.NumberFormat;
import java.util.Locale;
/**
* A Number formatter for decimal values.
* A general-purpose Number formatter.
*
* <p>Delegates to {@link NumberFormat#getInstance(Locale)}.
* Configures BigDecimal parsing so there is no loss in precision.
@ -34,10 +34,17 @@ import java.util.Locale;
* @see #setPattern
* @see #setLenient
*/
public final class DecimalFormatter extends AbstractNumberFormatter {
public final class NumberFormatter extends AbstractNumberFormatter {
private String pattern;
public NumberFormatter() {
}
public NumberFormatter(String pattern) {
this.pattern = pattern;
}
/**
* Sets the pattern to use to format number values.

View File

@ -19,6 +19,8 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.datetime.joda.JodaTimeFormattingConfigurer;
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
import org.springframework.format.number.NumberFormatter;
import org.springframework.util.ClassUtils;
/**
@ -40,6 +42,7 @@ public class FormattingConversionServiceFactoryBean implements FactoryBean<Conve
public void afterPropertiesSet() {
initConversionService();
installNumberFormatting();
installJodaTimeFormattingIfPresent();
}
@ -67,6 +70,11 @@ public class FormattingConversionServiceFactoryBean implements FactoryBean<Conve
}
}
private void installNumberFormatting() {
this.conversionService.addFormatterForFieldType(Number.class, new NumberFormatter());
this.conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
}
private void installJodaTimeFormattingIfPresent() {
if (ClassUtils.isPresent("org.joda.time.DateTime", FormattingConversionService.class.getClassLoader())) {
new JodaTimeFormattingConfigurer().installJodaTimeFormatting(this.conversionService);

View File

@ -1,54 +0,0 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.format.number;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import java.util.Locale;
import org.junit.Test;
import org.springframework.format.number.IntegerFormatter;
/**
* @author Keith Donald
*/
public class IntegerFormatterTests {
private IntegerFormatter formatter = new IntegerFormatter();
@Test
public void formatValue() {
assertEquals("23", formatter.print(23L, Locale.US));
}
@Test
public void parseValue() throws ParseException {
assertEquals((Long) 2356L, formatter.parse("2356", Locale.US));
}
@Test(expected = ParseException.class)
public void parseBogusValue() throws ParseException {
formatter.parse("bogus", Locale.US);
}
@Test(expected = ParseException.class)
public void parsePercentValueNotLenientFailure() throws ParseException {
formatter.parse("23.56", Locale.US);
}
}

View File

@ -23,14 +23,14 @@ import java.text.ParseException;
import java.util.Locale;
import org.junit.Test;
import org.springframework.format.number.DecimalFormatter;
import org.springframework.format.number.NumberFormatter;
/**
* @author Keith Donald
*/
public class DecimalFormatterTests {
public class NumberFormatterTests {
private DecimalFormatter formatter = new DecimalFormatter();
private NumberFormatter formatter = new NumberFormatter();
@Test
public void formatValue() {

View File

@ -0,0 +1,136 @@
package org.springframework.format.number;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.validation.DataBinder;
public class NumberFormattingTests {
private FormattingConversionService conversionService = new FormattingConversionService();
private DataBinder binder;
@Before
public void setUp() {
conversionService.addFormatterForFieldType(Number.class, new NumberFormatter());
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
LocaleContextHolder.setLocale(Locale.US);
binder = new DataBinder(new TestBean());
binder.setConversionService(conversionService);
}
@After
public void tearDown() {
LocaleContextHolder.setLocale(null);
}
@Test
public void testDefaultNumberFormatting() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("numberDefault", "3,339.12");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("3,339", binder.getBindingResult().getFieldValue("numberDefault"));
}
@Test
public void testDefaultNumberFormattingAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("numberDefaultAnnotated", "3,339.12");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("3,339.12", binder.getBindingResult().getFieldValue("numberDefaultAnnotated"));
}
@Test
public void testCurrencyFormatting() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("currency", "$3,339.12");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("$3,339.12", binder.getBindingResult().getFieldValue("currency"));
}
@Test
public void testPercentFormatting() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue("percent", "53%");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("53%", binder.getBindingResult().getFieldValue("percent"));
}
@Test
public void testPatternFormatting() {
}
public static class TestBean {
private Integer numberDefault;
@NumberFormat
private Double numberDefaultAnnotated;
@NumberFormat(style=Style.CURRENCY)
private BigDecimal currency;
@NumberFormat(style=Style.PERCENT)
private BigDecimal percent;
@NumberFormat(pattern="#,##.00")
private BigDecimal pattern;
public Integer getNumberDefault() {
return numberDefault;
}
public void setNumberDefault(Integer numberDefault) {
this.numberDefault = numberDefault;
}
public Double getNumberDefaultAnnotated() {
return numberDefaultAnnotated;
}
public void setNumberDefaultAnnotated(Double numberDefaultAnnotated) {
this.numberDefaultAnnotated = numberDefaultAnnotated;
}
public BigDecimal getCurrency() {
return currency;
}
public void setCurrency(BigDecimal currency) {
this.currency = currency;
}
public BigDecimal getPercent() {
return percent;
}
public void setPercent(BigDecimal percent) {
this.percent = percent;
}
public BigDecimal getPattern() {
return pattern;
}
public void setPattern(BigDecimal pattern) {
this.pattern = pattern;
}
}
}

View File

@ -0,0 +1,43 @@
package org.springframework.format.support;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Locale;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.ConversionService;
public class FormattingConversionServiceFactoryBeanTests {
private ConversionService conversionService;
@Before
public void setUp() {
FormattingConversionServiceFactoryBean factory = new FormattingConversionServiceFactoryBean();
factory.afterPropertiesSet();
this.conversionService = factory.getObject();
LocaleContextHolder.setLocale(Locale.US);
}
@After
public void tearDown() {
LocaleContextHolder.setLocale(null);
}
@Test
public void testFormatNumber() {
BigDecimal value = conversionService.convert("3,000.25", BigDecimal.class);
assertEquals("3,000.25", conversionService.convert(value, String.class));
}
@Test
public void testFormatDate() {
Date value = conversionService.convert("10/29/09 12:00 PM", Date.class);
assertEquals("10/29/09 12:00 PM", conversionService.convert(value, String.class));
}
}

View File

@ -34,7 +34,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.format.datetime.joda.DateTimeFormatAnnotationFormatterFactory;
import org.springframework.format.datetime.joda.DateTimeParser;
import org.springframework.format.datetime.joda.ReadablePartialPrinter;
import org.springframework.format.number.IntegerFormatter;
import org.springframework.format.number.NumberFormatter;
/**
* @author Keith Donald
@ -57,7 +57,7 @@ public class FormattingConversionServiceTests {
@Test
public void testFormatFieldForTypeWithFormatter() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new IntegerFormatter());
formattingService.addFormatterForFieldType(Number.class, new NumberFormatter());
String formatted = formattingService.convert(new Integer(3), String.class);
assertEquals("3", formatted);
Integer i = (Integer) formattingService.convert("3", Integer.class);

View File

@ -44,7 +44,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.number.DecimalFormatter;
import org.springframework.format.number.NumberFormatter;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.StringUtils;
@ -300,7 +300,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
conversionService.addFormatterForFieldType(Float.class, new DecimalFormatter());
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("myFloat", "1,2");

View File

@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="validation">
<title>Validation, Data-binding, the <interfacename>BeanWrapper</interfacename>, and <literal>PropertyEditors</literal></title>
<title>Validation, Data Binding, and Type Conversion</title>
<section id="validation-introduction">
<title>Introduction</title>
@ -345,7 +345,7 @@ Float salary = (Float) company.getPropertyValue("managingDirector.salary");]]></
<section id="beans-beans-conversion">
<title>Built-in <interface>PropertyEditor</interface> implementations</title>
<para>Spring heavily uses the concept of <literal>PropertyEditors</literal> to effect the conversion
<para>Spring uses the concept of <literal>PropertyEditors</literal> to effect the conversion
between an <classname>Object</classname> and a <classname>String</classname>. If you think about it,
it sometimes might be handy to be able to represent properties in a different way than the object itself.
For example, a <classname>Date</classname> can be represented in a human readable way (as the
@ -910,65 +910,64 @@ public class MyService {
</section>
<section id="ui.format">
<title>Spring 3 UI Field Formatting</title>
<title>Spring 3 Field Formatting</title>
<para>
<link linkend="core.convert"><filename>core.convert</filename></link> is a simple, general-purpose type conversion system.
It addresses <emphasis>one-way</emphasis> conversion from one type to another and is not limited to just converting Strings.
It provides a strongly-typed Converter SPI for implementing <emphasis>one-way</emphasis> conversion logic from one type to another and is not limited to just converting Strings.
As discussed in the previous section, a Spring Container can be configured to use this system to bind bean property values.
In addition, the Spring Expression Language (SpEL) uses this system to coerce Expression values.
For example, when SpEL needs to coerce a <classname>Short</classname> to a <classname>Long</classname> to fulfill an
<function>expression.setValue()</function> attempt, the core.convert system performs the coercion.
In addition, both the Spring Expression Language (SpEL) and DataBinder can use this system to coerce values.
For example, when SpEL needs to coerce a <classname>Short</classname> to a <classname>Long</classname> to complete an <function>expression.setValue(Object bean, Object value)</function> attempt, the core.convert system performs the coercion.
</para>
<para>
Now consider the type conversion requirements of a typical UI environment such as a web or desktop application.
In such environments, you typically convert <emphasis>from String</emphasis> to support the form postback process, as well as back <emphasis>to String</emphasis> to support the rendering process.
The more general <emphasis>core.convert</emphasis> system does not address this specific scenario directly.
To directly address this, Spring 3 introduces a new <emphasis>ui.format</emphasis> system that provides a simple and robust alternative to PropertyEditors in a UI environment.
Now consider the type conversion requirements of a typical client environment such as a web or desktop application.
In such environments, you typically convert <emphasis>from String</emphasis> to support the client postback process, as well as back <emphasis>to String</emphasis> to support the rendering process.
The more general <emphasis>core.convert</emphasis> Converter SPI does not address this specific common scenario directly.
To directly address this, Spring 3 introduces a conveient <emphasis>format</emphasis> SPI that provides a simple and robust alternative to PropertyEditors in a client environment.
</para>
<para>
In general, use Converters when you need to implement general-purpose type
conversion logic, logic that may be invoked by the Spring Container, SpEL,
or your own code as part of a <emphasis>one-way</emphasis> binding process.
Use Formatters when you're working in a UI environment, such as an HTML form
of a web application and need to apply <emphasis>two-way</emphasis> parsing,
formatting, and localization logic to form field values.
In general, use the Converter SPI when you need to implement general-purpose type conversion logic.
Use Formatters when you're working in a client environment, such as an HTML form of a web application and need to apply String parsing, printing, and localization logic to form field values.
</para>
<section id="ui-format-Formatter-SPI">
<title>Formatter SPI</title>
<para>
The Formatter SPI to implement UI formatting logic is simple and strongly typed:
The <literal>org.springframework.format</literal> SPI to implement field formatting logic is simple and strongly typed:
</para>
<programlisting language="java"><![CDATA[
package org.springframework.ui.format;
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}]]>
</programlisting>
<programlisting language="java"><![CDATA[
import java.text.ParseException;
public interface Formatter<T> {
String format(T object, Locale locale);
T parse(String formatted, Locale locale) throws ParseException;
}]]></programlisting>
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}]]>
</programlisting>
<programlisting language="java"><![CDATA[
public interface Formatter<T> extends Printer<T>, Parser<T> {
}]]>
</programlisting>
<para>
To create your own Formatter, simply implement the interface above.
To create your own Formatter, simply implement the Formatter interface above.
Parameterize T to be the type of object you are formatting, for example, <classname>java.lang.BigDecimal</classname>.
Implement the <methodname>format</methodname> operation to format an instance of T for display in the client locale.
Implement the <methodname>print</methodname> operation to print an instance of T for display in the client locale.
Implement the <methodname>parse</methodname> operation to parse an instance of T from the formatted representation returned from the client locale.
Your Formatter should throw a ParseException if a parse attempt fails.
Your Formatter should throw a ParseException or IllegalArgumentException if a parse attempt fails.
Take care to ensure your Formatter implementation is thread-safe.
</para>
<para>
Several Formatter implementations are provided in subpackages of <filename>ui.format</filename> as a convenience.
The <filename>date</filename> package provides a DateFormatter to format java.util.Date objects with a java.text.DateFormat.
Several Formatter implementations are provided in <filename>format</filename>subpackages as a convenience.
The <filename>datetime</filename> package provides a DateFormatter to format java.util.Date objects with a java.text.DateFormat.
The <filename>number</filename> package provides a DecimalFormatter, IntegerFormatter, CurrencyFormatter, and PercentFormatter to format java.lang.Number objects using a java.text.NumberFormat.
The <filename>jodatime</filename> package provides a DateTimeFormatter to format Joda DateTime objects, a popular alternative to java.util.Date/Calendar.
The <filename>datetime.joda</filename> package provides comprehensive datetime formatting support based on the <ulink url="http://joda-time.sourceforge.net">Joda Time library</ulink>.
</para>
<para>
Consider <classname>DateFormatter</classname> as an example <interfacename>Formatter</interfacename> implementation:
</para>
<programlisting language="java"><![CDATA[
package org.springframework.ui.format.date;
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
@ -978,7 +977,7 @@ public final class DateFormatter implements Formatter<Date> {
this.pattern = pattern;
}
public String format(Date date, Locale locale) {
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
@ -1003,72 +1002,31 @@ public final class DateFormatter implements Formatter<Date> {
The Spring team welcomes community-driven Formatter contributions; see <ulink url="http://jira.springframework.org">http://jira.springframework.org</ulink> to contribute.
</para>
</section>
<section id="ui-format-Formatted-Annotation">
<title>@Formatted</title>
<para>
The @Formatted annotation allows you to easily associate a Formatter implementation with one of your classes.
To use this feature, simply annotate your class as @Formatted and specify the Formatter implementation to use as the annotation value:
</para>
<programlisting language="java"><![CDATA[
@Formatted(MoneyFormatter.class)
public class Money {
...
}]]></programlisting>
<para>
The example above says <emphasis>"Money objects should be formatted by a MoneyFormatter"</emphasis>.
With this configuation, whenever a field is of type Money, MoneyFormatter will format the field value.
</para>
</section>
<section id="ui-format-CustomFormatAnnotations">
<title>Custom Format Annotations</title>
<para>
Field-specific formatting can be triggered by annotating model properties.
To bind a custom annotation to a Formatter instance, simply annotate the annotation as @Formatted:
Field formatting can be triggered by annotating model properties.
To bind a custom annotation to a Formatter instance, implement AnnotationFormatterFactory:
</para>
<programlisting language="java"><![CDATA[
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Formatted(CurrencyFormatter.class)
public @interface Currency {
}]]></programlisting>
<para>
Then, to trigger formatting, simply annotate a model property with the annotation:
</para>
<programlisting language="java"><![CDATA[
public class MyModel {
@Currency
private BigDecimal amount;
}]]></programlisting>
<para>
Custom annotations like @Currency can also be annotated with JSR-303 constraint annotations to specify declarative validation constraints.
For example:
</para>
<programlisting language="java"><![CDATA[
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Formatted(CurrencyFormatter.class)
@Constraint(validatedBy = CurrencyValidator.class)
public @interface Currency {
}]]></programlisting>
<para>
Given the example above, on form postback any @Currency properties will first be parsed by CurrencyFormatter, then validated by CurrencyValidator.
</para>
<section id="ui-format-AnnotationFormatterFactory">
<title>AnnotationFormatterFactory</title>
<para>
If your custom annotation has attributes that configure Formatter
instance behavior by property, implement an AnnotationFormatterFactory:
</para>
<programlisting language="java"><![CDATA[
package org.springframework.ui.format;
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation, T> {
public interface AnnotationFormatterFactory<A extends Annotation> {
Formatter<T> getFormatter(A annotation);
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}]]></programlisting>
<para>
Parameterize A to be the field annotationType you wish to associate formatting logic with, for example <code>org.springframework.format.annotation.DateTimeFormat</code>.
Implement the <methodname>getFieldTypes</methodname> operation return the types of fields the annotation may be used on.
Implement the <methodname>getPrinter</methodname> operation to return the Printer to print the value of an annotated field.
Implement the <methodname>getParser</methodname> operation to return the Parser to parse the value of an annotated field.
Take care to ensure your AnnotationFormatterFactory implementation is thread-safe.
</para>
<para>
The example implementation below binds a @DecimalFormat instance to a Formatter instance.
This particular annotation allows the NumberFormat pattern to be configured.
@ -1107,7 +1065,7 @@ public class MyModel {
Review the FormatterRegistry SPI below:
</para>
<programlisting language="java"><![CDATA[
package org.springframework.ui.format;
package org.springframework.format;
public interface FormatterRegistry {
@ -1145,7 +1103,7 @@ public interface FormatterRegistry {
<!-- Configures Spring MVC DataBinder instances -->
<bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="formatterRegistry">
<bean class="org.springframework.ui.format.support.GenericFormatterRegistry">
<bean class="org.springframework.format.support.GenericFormatterRegistry">
<property name="formatters">
<list>
<!-- Register Formatter beans here -->
@ -1167,33 +1125,6 @@ public interface FormatterRegistry {
See the JavaDocs for GenericFormatterRegistry for more configuration options.
</para>
</section>
<section id="ui-format-registering-field-specific-Formatters">
<title>Registering field-specific Formatters</title>
<para>
In most cases, configuring a shared FormatterRegistry that selects Formatters based on model property type or annotation is sufficient.
When sufficient, no special @Controller @InitBinder callbacks are needed to apply custom formatting logic.
However, there are cases where field-specific formatting should be configured on a Controller-by-Controller basis.
For example, you may need to format a specific Date field in a way that differs from all the other Date fields in your application.
</para>
<para>
To apply a Formatter to a single field, create an @InitBinder callback on your @Controller, then call binder.registerFormatter(String, Formatter):
</para>
<programlisting language="java"><![CDATA[
@Controller
public class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerFormatter("myFieldName", new MyCustomFieldFormatter());
}
...
}]]></programlisting>
<para>
This applies the Formatter to the field and overrides any Formatter
that would have been applied by field type or annotation.
</para>
</section>
</section>
<section id="validation.beanvalidation">
<title>Spring 3 Validation</title>