It is usually not necessary to create new converter annotations to use new converters. Data binders annotations should allow a Converter class to be explicitly specified. This class can be any custom Converter implementation that is appropriate for the binding operation. The limitation is that this converter cannot itself be parameterized in the binding annotation. To achieve this, you need to create a custom converter annotation.
As with validators and data binders, there are three steps to creating custom converter annotations.
Write an implementation of Converter. Converter's form is as follows:
public interface Converter<S extends Object, T extends Object> { public T toTargetType(S toConvert) throws ConversionException; public S toSourceType(T toConvert) throws ConversionException; public void setTargetClass(Class clazz); }
Notice how the Converter
interface is parameterized using generics, making it possible
to implement a converter which converts back and forth from any two types. In practice,
converters are probably more likely to use String as a source type, and some other
object type as a target type.
The methods toTargetType()
and toSourceType()
correspond with the inward and outward
binding operations, that is, binding from form property to domain model, and vice versa.
The setTargetClass()
method can serve one of two purposes, depending on where it is used.
Converters can either be generic in the sense that they can convert to and from a wide range of types,
or type-specific. An example of a generic converter
is the converter used by default by the data binders, SafeBeanUtilsConverter
.
This converter is capable of converting to quite a range of types. The actual
conversion performed depends on the the class specified in the setTargetClass()
method.
The implementation of SafeBeanUtilsConverter
is shown below. Behind the scenes, it uses
Jakarta Commons BeanUtils.
public class SafeBeanUtilsConverter implements Converter{ private Class clazz; public void setTargetClass(Class clazz) { this.clazz = clazz; } public Object toTargetType(String toConvert) throws ConversionException { Assert.notNull(clazz); if (!StringUtils.notBlankOrNull(toConvert)) return null; Object convert = null; try { convert = BeanUtilsConverter.getInstance(). convert(toConvert, clazz); } catch (Exception e) { throw new ConversionException(e); } return convert; } public String toSourceType(Object toConvert) throws ConversionException { Assert.notNull(clazz); if (toConvert == null) return null; String convert = null; try { convert = BeanUtilsConverter. getInstance().convert(toConvert); } catch (Exception e) { throw new ConversionException(e); } return convert; } }
Other converters, such as DatePatternConverter
, are designed only to work with
particular target types. Specifically, DatePatternConverter
implements Converter<String, Date>
. In this case, the setTargetClass()
method can be used to perform a runtime check to ensure that the target class's
type is appropriate for the conversion being attempted, as shown below:
public void setTargetClass(Class clazz) { if (!clazz.equals(Date.class)) { throw new IllegalArgumentException( "Converter will only convert to and from java.util.Date instances"); } }
Next, a converter annotation is required. The converter annotation identifies a factory class
and provides any additional parameters required to configure the Converter
.
The @ConvertDate
annotation is shown below:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ConverterFactoryClass(DateConverterFactory.class) public @interface ConvertDate { String pattern(); }
Apart from identifying the factory class, the only additional information provided by the annotation is the textual pattern used for the conversion.
ConverterFactory
, which in our example above is
DateConverterFactory
. The implementation is very simple and is shown below:
public class DateConverterFactory implements ConverterFactory { public Converter createConverter(Annotation annotation) { ConvertDate annot = (ConvertDate) annotation; DatePatternConverter converter = new DatePatternConverter(annot.pattern()); converter.setTargetClass(Date.class); return converter; } }
Apart from instantiating the converter, the only function
performed by this ConverterFactory
is
providing the converter with a pattern String
.