- Type conversion happens automatically
-
A common “ConversionService” underpins the places where type conversion is required
Always used with @RequestParam, JavaBean, @PathVariable, and @RequestHeader, and @CookieValue
HttpMessageConverter may use when reading and writing objects: for @RequestBody, @ResponseBody, HttpEntity, ResponseEntity
1. All major conversion requirements satisfied out-of-the-box:Primitives, Strings, Dates, Collections, Maps, custom value objects
@RequestMapping("primitive")
public @ResponseBody String primitive(@RequestParam Integer value) {
return "Converted primitive " + value;
}
// requires Joda-Time on the classpath
@RequestMapping("date/{value}")
public @ResponseBody String date(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date value) {
return "Converted date " + value;
}
@RequestMapping("collection")
public @ResponseBody String collection(@RequestParam Collection<Integer> values) {
return "Converted collection " + values;
}
2. Can declare annotation-based conversion rules: @NumberFormat, @DateTimeFormat, your own custom @Format annotation
@RequestMapping("formattedCollection")
public @ResponseBody String formattedCollection(@RequestParam @DateTimeFormat(iso=ISO.DATE) Collection<Date> values) {
return "Converted formatted collection " + values;
}
@RequestMapping("bean")
public @ResponseBody String bean(JavaBean bean) {
return "Converted " + bean;
}
JavaBean.java
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
public class JavaBean {
private Integer primitive;
@DateTimeFormat(iso=ISO.DATE)
private Date date;
@MaskFormat("(###) ###-####")
private String masked;
// list will auto-grow as its dereferenced e.g. list[0]=value
private List<Integer> list;
// annotation type conversion rule will be applied to each list element
@DateTimeFormat(iso=ISO.DATE)
private List<Date> formattedList;
// map will auto-grow as its dereferenced e.g. map[key]=value
private Map<Integer, String> map;
// nested will be set when it is referenced e.g. nested.foo=value
private NestedBean nested;
public Integer getPrimitive() {
return primitive;
}
public void setPrimitive(Integer primitive) {
this.primitive = primitive;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getMasked() {
return masked;
}
public void setMasked(String masked) {
this.masked = masked;
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
public List<Date> getFormattedList() {
return formattedList;
}
public void setFormattedList(List<Date> formattedList) {
this.formattedList = formattedList;
}
public Map<Integer, String> getMap() {
return map;
}
public void setMap(Map<Integer, String> map) {
this.map = map;
}
public NestedBean getNested() {
return nested;
}
public void setNested(NestedBean nested) {
this.nested = nested;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("JavaBean");
if (primitive != null) {
sb.append(" primitive=").append(primitive);
}
if (date != null) {
sb.append(" date=").append(date);
}
if (masked != null) {
sb.append(" masked=").append(masked);
}
if (list != null) {
sb.append(" list=").append(list);
}
if (formattedList != null) {
sb.append(" formattedList=").append(formattedList);
}
if (map != null) {
sb.append(" map=").append(map);
}
if (nested != null) {
sb.append(" nested=").append(nested);
}
return sb.toString();
}
}
NestedBean.java
import java.util.List;
import java.util.Map;
public class NestedBean {
private String foo;
private List<NestedBean> list;
private Map<String, NestedBean> map;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public List<NestedBean> getList() {
return list;
}
public void setList(List<NestedBean> list) {
this.list = list;
}
public Map<String, NestedBean> getMap() {
return map;
}
public void setMap(Map<String, NestedBean> map) {
this.map = map;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("NestedBean");
if (foo != null) {
sb.append(" foo=").append(foo);
}
if (list != null) {
sb.append(" list=").append(list);
}
if (map != null) {
sb.append(" map=").append(map);
}
return sb.toString();
}
}
3. Elegant SPI for implementing your own converters
@RequestMapping("value")
public @ResponseBody String valueObject(@RequestParam SocialSecurityNumber value) {
return "Converted value object " + value;
}
@RequestMapping("custom")
public @ResponseBody String customConverter(@RequestParam @MaskFormat("###-##-####") String value) {
return "Converted '" + value + "' with a custom converter";
}
SocialSecurityNumber.java
public final class SocialSecurityNumber {
private final String value;
public SocialSecurityNumber(String value) {
this.value = value;
}
@MaskFormat("###-##-####")
public String getValue() {
return value;
}
public static SocialSecurityNumber valueOf(@MaskFormat("###-##-####") String value) {
return new SocialSecurityNumber(value);
}
}
MaskFormat.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MaskFormat {
String value();
}
MaskFormatAnnotationFormatterFactory.java
import java.text.ParseException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
public class MaskFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<MaskFormat> {
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> fieldTypes = new HashSet<Class<?>>(1, 1);
fieldTypes.add(String.class);
return fieldTypes;
}
public Parser<?> getParser(MaskFormat annotation, Class<?> fieldType) {
return new MaskFormatter(annotation.value());
}
public Printer<?> getPrinter(MaskFormat annotation, Class<?> fieldType) {
return new MaskFormatter(annotation.value());
}
private static class MaskFormatter implements Formatter<String> {
private javax.swing.text.MaskFormatter delegate;
public MaskFormatter(String mask) {
try {
this.delegate = new javax.swing.text.MaskFormatter(mask);
this.delegate.setValueContainsLiteralCharacters(false);
} catch (ParseException e) {
throw new IllegalStateException("Mask could not be parsed " + mask, e);
}
}
public String print(String object, Locale locale) {
try {
return delegate.valueToString(object);
} catch (ParseException e) {
throw new IllegalArgumentException("Unable to print using mask " + delegate.getMask(), e);
}
}
public String parse(String text, Locale locale) throws ParseException {
return (String) delegate.stringToValue(text);
}
}
}