I’ve been playing around with Spring Web MVC a bit and was looking for something similar to Jersey’s Parameter Classes that would provide conversion to custom types. I liked how with Jersey, you could encapsulate the conversion logic in a single class and have that reused across multiple methods with minimal configuration.
Here’s how I achieved a similar result in Spring Web MVC. (Note: the following examples were done with Spring 3.2.1)
Built-in?
Spring does provide some build-in support for conversion to specific types. For example, you can convert to Date and various numeric types. But, what if you want to convert a request parameter to some other custom type or a type from a third party? (Such as the date-time classes from Joda Time)
Minimize configuration
I searched for a bit and found various solutions, but they all seemed to require too much configuration: In addition to writing a converter class, you’d have to manually the converter with a ConversionService (either in code or in XML configuration). I didn’t like the idea of having to register the converter class; instead, I wanted it to be registered automatically based on some annotations.
Solution
Here’s what I came up with, based on this answer on Stack Overflow.
-
Define a custom annotation for your converters so they can be automatically found
@Target({ ElementType.TYPE, ElementType.FIELD }) public @interface MyConverter { }
-
Create a converter class, annotated with your custom annotation and
@Component
This is so it will be available for injection via
@Resource
. (In this example, we convert to a Joda TimeLocalDate
)The converter class must implement Spring’s
Converter
interface.@Component @MyConverter public class LocalDateConverter implements Converter<String, LocalDate> { public static final String DATE_FORMAT = "YYYY-MM-dd"; @Override public LocalDate convert(final String source) { return LocalDate.parse(source, DateTimeFormat.forPattern(DATE_FORMAT)); } }
-
Create a bean that extends
FormattingConversionServiceFactoryBean
that will auto-register all convertersHere is where all the converters annotated with your custom annotation (and
@Resource
) are injected.public class MyConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean { @Resource @MyConverter private List<Converter<?, ?>> myConverters; @Override protected void installFormatters(final FormatterRegistry registry) { // NOTE: This method call is deprecated. super.installFormatters(registry); for (final Converter<?, ?> converter : this.myConverters) { registry.addConverter(converter); } } }
-
Edit Spring configuration so that the auto-registering bean is loaded
Ok, I lied: There is one piece of configuration you need, but once it’s made it need never be changed, even when you define additional converters.
<mvc:annotation-driven conversion-service="applicationConversionService" /> <bean class="com.myapplication.spring.mvc.controller.converters.MyConversionServiceFactoryBean" id="applicationConversionService"/>
-
Modify controller method to use the proper type
Note that if conversion fails (via an uncaught exception from the
convert()
method) then the client will see a 400 Bad Request response.@RequestMapping(value = "widget/{date}", method = RequestMethod.GET) @ResponseBody public String getWidgetDate(@PathVariable("date") final LocalDate date) { // We get auto-conversion to a LocalDate type... // Just spits back the date to the client. return date.toString(); }
Summary
I hope you found this useful. With just a little bit of work, we have a bean that will auto-register and make available any new converters you define, so as long as you annotate the converter properly.
Thanks a lot of … very useful tutorial …