In my Spring MVC 3 based application I had recently implemented a few Converters for some of my JPA based data objects. It started with one, then another, and so on. By the time I got around to adding my fourth converter to the spring configuration file I knew it was time to pull it out and abstract it a bit. Thankfully, Spring allows you to implement a ConverterFactory that is responsible for creating the converters for some types.
Each of my entities extend an abstract base class that looks basically like this
@MappedSuperclass public abstract class DataObjectAbstract<K extends Serializable> implements DataObject<K> { protected transient String[] excludedEqualsFields = new String[] { "key", "version" }; @Version protected int version; @Override public boolean equals( Object that ) { return EqualsBuilder.reflectionEquals( this, that, excludedEqualsFields ); } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode( this, excludedEqualsFields ); } @Override public String toString() { return ToStringBuilder.reflectionToString( this, ToStringStyle.MULTI_LINE_STYLE ); } }
The DataObject interface simply declares a getKey and setKey method.
So, in my Spring MVC Controller methods I was originally accepting a String or Long, then using my own data access objects to lookup the entities I needed. The next iteration in my implementation was to implement the Converters as I mentioned above. That was very simple and worked well, but having many data objects I didn’t want to copy that implementation over and over again. This is where the ConverterFactory comes in. Here’s my implementation:
@Component public class DataObjectConverterFactory implements ConverterFactory<String, DataObject<Long>> { @PersistenceContext EntityManager em; @Override public <T extends DataObject<Long>> Converter<String, T> getConverter( Class<T> type ) { return new GenericLongKeyedDataObjectConverter<T>( type, em ); } }
The ConverterFactory interface is basically as simple as the Converter interface. The Class<T> type parameter to the getConverter method tells us what type we are going to convert to. One option from here is to have a big nasty if/else statement with a bunch of instanceof methods that create a new Converter. I thought about doing this and passing in the appropriate data access object and performing the lookup. That would be only two classes and then I could convert all of my DataObjects, but I didn’t like the idea of a bajillion instanceof statements. So you can see I implemented a GenericLongKeyedDataObjectConverter which takes the target type and the EntityManager as a parameters. Here’s the implementation of the generic converter class:
/** * A generic converter used for converting from a string representation of an entity key to the DataObject itself. * * @param <T> The type that is to be converted to. */ public class GenericLongKeyedDataObjectConverter<T extends DataObject<Long>> implements Converter<String, T> { private Class<T> type; private EntityManager em; /** * * @param type An instance of Class for the type being converted to * @param em EntityManager used to perform the lookup. */ public GenericLongKeyedDataObjectConverter( Class<T> type, EntityManager em ) { this.type = type; this.em = em; } @Override public T convert( String stringKey ) { Long key = Long.parseLong( stringKey ); return em.find( type, key ); } }
An extremely simple parameterized class implementation of the Converter interface. Here, with no use of instanceof, I’m creating the appropriate converter implementation for all of my persisted classes. If you have a group of objects that you want converted and they all inherit from a base class, a ConverterFactory may be a better solution than implementing a bunch of converters manually.
Finally, here’s the bean xml configuration:
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <ref bean="dataObjectConverterFactory" /> </list> </property> </bean>
Notice that we reference the dataObjectConverterFactory bean, but I never defined it in my xml config. That’s because I used the @Component annotation on my implementation class.