In a previous Developer.com article, I wrote about the new formatting annotations provided in Spring 3. The formatting annotations, as I described in that article, enable you to format numbers, dates and times quickly and easily. Those annotations actually are part of Spring 3’s Formatter SPI, which allows you to define custom parse and print capabilities for just about any type.
In this follow-up article, I explain how the new Formatter SPI in Spring 3 gives you the power to build custom formatting capabilities, demonstrating how you can create parsing/printing annotations such as @NumberFormat
and @DateTimeFormat
.
Spring 3 Custom Formatting: When, Why and Where to Use
You use field formatting to render bean property data. In particular, you can use the rendering capability in user interfaces and report displays — like those found in Web applications. Figure 1 shows an example of a field formatter.
You also use field formatting to help parse and convert string data (e.g. as provided in a Web form) into a variety of bean field types. In this way, the field formatting offers an alternative to the type conversion system (added to Spring 3) and PropertyEditors found in the Spring Framework. Figure 2 shows a Web form being parsed and converted to the Phone object of a Customer using a formatter.
There is a bit overlap between Spring 3’s type conversion system and the Formatter SPI. So when should you use the type conversion system (also known as the Converter SPI) and when should you use the formatting system? The Spring documentation provides a recommendation:
“In general, use the Converter SPI when you need to implement general-purpose type conversion logic; for example, for converting between a java.util.Date and a java.lang.Long. Use the Formatter SPI when you’re working in a client environment, such as a Web application, and need to parse and print localized field values.”
Spring 3 Custom Formatting: What You Need
In Spring 3, the Formatter SPI interfaces and classes are located in org.springframework.context-3.0.X.RELEASE.jar. You may need additional JAR files depending on your application and how you use the Formatter SPI.
The rest of this article demonstrates the Formatter SPI. The code shown is available in a small demonstration application provided with this article. The following are the JAR files used in the application, beyond the context JAR:
- org.springframework.beans-3.0.4.RELEASE.jar
- org.springframework.core-3.0.4.RELEASE.jar
- org.springframework.web-3.0.4.RELEASE.jar
- org.springframework.web.servlet-3.0.4.RELEASE.jar
- com.springsource.org.apache.commons.logging-1.1.1.jar
- org.springframework.asm-3.0.4.RELEASE.jar
- org.springframework.expression-3.0.4.RELEASE.jar
The Formatter Interface
If you need additional formatting beyond the formatting annotations, or if do not like the formatting that the annotations provide, you can add your own custom formatting. At the heart of the formatting system is the formatter interface. The formatter interface is typed to allow you to display or parse strings from your designated target type.
This interface requires all implementers to provide parse( )
and print( )
methods. As the names of these methods suggest, the parse method takes a string and produces an instance of the target type. The print method, on the other hand, takes an instance of the target type and produces a string representation of the target type object. Both methods take a locale parameter so that the string (whether input or output) is localized to the user’s preference.
To learn how to use the formatter interface, suppose you defined your own CustomerSSN (Social Security Number) type, which you use to define a SSN property in a Customer class as shown in Listing 1.
Listing 1. Pertinent Parts of the Customer and CustomerSSN Classes
public class Customer {
...
private CustomerSSN ssn = new CustomerSSN();
...
}
public class CustomerSSN {
private int area; //first 3
private int group; //middle 2
private int serial; // last 4
...
}
Customer objects have a social security number property as defined by the CustomerSSN type.
Listing 2 shows an example custom formatter for CustomerSSN. CustomerSSN serves as the example formatter’s target type.
Listing 2. SSNFormatter is a Formatter Implementation
public class SSNFormatter implements Formatter<CustomerSSN> {
public String print(CustomerSSN ssn, Locale locale) {
return ssn.getArea() + "-" +
ssn.getGroup() + "-" + ssn.getSerial();
}
public CustomerSSN parse(String source, Locale locale)
throws ParseException {
int area = Integer.parseInt(source.substring(0, 3));
int group = Integer.parseInt(source.substring(4, 6));
int serial = Integer.parseInt(source.substring(7, 11));
return new CustomerSSN(area, group, serial);
}
}
SSNFormatter parses and converts strings to CustomerSSN objects and prints or displays CustomerSSN objects to a “999-99-9999” string.
Now, in order to use a formatter implementation, register the formatter with a FormatterRegistry (org.springframework.forma). Extend the FormattingConversionServiceFactoryBean (org.springframework.format.support) to register your formatters with a FormatterRegistry. In Listing 3 below, a new class extending FormattingConversionServiceFactoryBean registers the SSNFormatter with the FormatterRegistry.
Listing 3. MyFormattingFactory Adds the Custom Formatter to the Formatter Registry
public class MyFormattingFactory extends
FormattingConversionServiceFactoryBean {
public void installFormatters(FormatterRegistry registry) {
super.installFormatters(registry);
registry.addFormatterForFieldType(CustomerSSN.class,
new SSNFormatter());
}
}
The SSNFormatter is registered with the formatter in order to parse and display any encountered CustomerSSN objects.
By default, the <mvc:annotation-driven />
Spring XML configuration element automatically registers Spring formatters and converters for common types. This is what allows the @NumberFormat
and @DateTimeFormat
formatting annotations to work. You need to alert Spring to use your FormattingConversionServiceFactoryBean to register your custom formatters. To do this, add a conversion-service attribute to the <mvc:annotation-driven />
element as follows.
<mvc:annotation-driven conversion-service="myFormattingFactory" />
<bean id="myFormattingFactory"
class="com.intertech.formatting.MyFormattingFactory" />
With the formatter in place, CustomerSSN objects can be displayed/parsed appropriately in an HTML form. In Figure 3 below, you see CustomerSSN data displayed in a form without (above) and with (below) the custom formatter in place.
The SSNFormatter allows CustomerSSN data to be displayed appropriately in a form. While not shown, the same formatter allows string data from the form to be parsed as a CustomerSSN object.
Spring 3 Custom Formatting Annotations
While the formatter allows you to create a means to parse and print field values, the convenience of annotations (like those provided by @NumberFormat
and @DateTimeFormat
) is hard to beat. Wouldn’t it be nice to have that same convenience with your custom formatter? Well, you can! The Formatter SPI allows you to create annotations for your custom formatter.
You first need to define an annotation to mark a field for custom formatting. The code in Listing 4 below defines a new annotation that you will use on CustomerSSN fields to trigger formatting.
Listing 4. SSNFormat Defines the Custom Formatting Annotation
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface SSNFormat {
}
SSNFormat defines the annotation to use on CustomerSSN fields to designate them for custom parsing and printing.
Next, implement an AnnotationFormatterFactory (org.springframework.format) rather than implementing the formatter interface. Listing 5 shows the same SSN formatting logic (from the SSNFormatter example above) baked into a new AnnotationFormatterFactory.
Listing 5. SSNAnnotationFormatterFactory Implements the AnnotationFormatterFactory Interface
public final class SSNAnnotationFormatterFactory implements
AnnotationFormatterFactory<SSNFormat> {
Set<Class<?>> getFieldTypes() {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(CustomerSSN.class);
return set;
}
public Parser<CustomerSSN> getParser(SSNFormat arg0, Class<?> arg1) {
return new SSNFormatter();
}
public Printer<CustomerSSN> getPrinter(SSNFormat annotation, Class<?> fieldType) {
return new SSNFormatter();
}
}
This factory will be registered with the FormatterRegistry.
As before, you still need to register your AnnotationFormatterFactory with the FormatterRegistry. You can again use the FormattingConversionServiceFactoryBean. However, this time register the annotation-based formatter. The FormattingConversionServiceFactoryBean has an addFormatterForAnnotation()
method for this purpose.
Listing 6. MyFormattingFactory Again Adds the Custom Formatter to the Formatter Registry
public class MyFormattingFactory extends
FormattingConversionServiceFactoryBean {
public void installFormatters(FormatterRegistry registry) {
super.installFormatters(registry);
registry.addFormatterForFieldType(CustomerSSN.class,
new SSNFormatter());
}
}
Now, simply use the annotation on the Customer class to have strings in the display converted to CustomerSSN and vice versa.
Listing 7. The CustomerSSN Property Is Now Annotated
public class Customer {
...
private CustomerSSN ssn = new CustomerSSN();
...
}
public class CustomerSSN {
private int area; //first 3
private int group; //middle 2
private int serial; // last 4
...
}
The field is parsed and printed appropriately per the SSNFormatter.
Wrap Up
While the new Spring 3 formatting annotations are simple to use and very helpful, most applications call for formatting beyond simple numbers and date/times. As shown here, the new Formatter SPI in Spring 3 gives you the power to build custom formatting capability. With just a little more work, you can also create parsing/printing annotations like @NumberFormat
and @DateTimeFormat
.
Code Download
About the Author
Jim White is the Director of Training and partner with Intertech Training. Reach him by e-mail at jwhite@intertech.com.