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.
What to do with controller? will you please see this post http://stackoverflow.com/questions/8875681/inner-object-id-save-in-spring-3
Comment by M Mahmoud — 2012/01/20 @ 10:23 pm