对于MVC框架,参数绑定一直觉得是很神奇很方便的一个东西,在参数绑定的过程中利用了属性编辑器、类型转换器
参数绑定流程
参数绑定:把请求中的数据,转化成指定类型的对象,交给处理请求的方法
- 请求进入到DisptacherServlet,卸下请求中的数据
- DisptacherServlet将请求中的数据发送给Controller
- 获取Controller需要接收的参数类型,将参数类型和请求数据发送给DataBinder
- DataBinder将参数类型和请求数据再发给TypeConverter,由TypeConverter装配成一个bean
- TypeConverter根据bean中的成员类型,在PropertyEditorRegistry中查找已注册的PropertyEditor
- PropertyEditor将数据setter进bean中的成员
- TypeConverter将装配好的bean返回给DataBinder
- DataBinder将装配bean交给处理请求的方法
属性编辑器
PropertiesEditor负责转化简单对象,因为http请求都是以字符串的形式,所以一般都是根据String来转换springmvc提供了很多默认的属性编辑器,在org.springframework.beans.propertyeditors包中,比如
- CustomBooleanEditor.class,String 转换 Boolean
- CustomCollectionEditor.class,String 转换 Collection
- CustomDateEditor.class,String 转换 Date
- CustomMapEditor.class,String 转换 Map
- CustomNumberEditor.class,String 转换 int、floot、double..
PropertiesEditor源码分析
PropertiesEditor.javapublic class PropertiesEditor extends PropertyEditorSupport {
//将String转成指定类型的对象
@Override
public void <span style="color:#ff0000">setAsText(String text)</span> throws IllegalArgumentException {
Properties props = new Properties();//Properties以key-value存值
if (text != null) {
try {
<span style="color:#ff0000"> //将String中表示的key=value或key:value信息,转化成Properties
//key表示bean中字段名称
//如果要转化成Date,则value是Date,String可以是"date=2012-12-12"的形式(date是字段名)
props.load(new ByteArrayInputStream(text.getBytes("ISO-8859-1")));</span>
}
catch (IOException ex) {
throw new IllegalArgumentException(
"Failed to parse [" + text + "] into Properties", ex);
}
}
setValue(props);
}
//将old object转化成新object
@Override
public void setValue(Object value) {
if (!(value instanceof Properties) && value instanceof Map) {
Properties props = new Properties();
props.putAll((Map<?, ?>) value);
super.setValue(props);
}
else {
//父类PropertyEditorSupport持有value对象,就是要转化后的对象
super.setValue(value);
}
}
}
需要注意的是,setAsText通过一定格式的字符串来达到属性编辑的效果,
"成员名称=value",或者是"成员名称:value",这样就会把value set到bean的指定成员中了编辑器中最重要的两个方法就是,setAsTest(String)和setValue(value),在这两个方法中完成从String——object,object——object
CustomDateEditor源码分析
CustomDateEditor是Spring的一个默认属性编辑器,负责将String转化成指定格式的Date对象同样他也是继承了PropertiesEditorSupport,重写了setAsTest方法public class CustomDateEditor extends PropertyEditorSupport {
<span style="color:#ff0000"> //指定的date格式,如"yyyy-MM-dd"</span>
<span style="color:#ff0000"> private final DateFormat dateFormat;</span>
//是否允许字符串为空
private final boolean allowEmpty;
//严格的日期长度
private final int exactDateLength;
public CustomDateEditor(<span style="color:#ff0000">DateFormat dateFormat</span>, boolean allowEmpty) {
//构造器方法
}
public CustomDateEditor(<span style="color:#ff0000">DateFormat dateFormat</span>, boolean allowEmpty, int exactDateLength) {
//构造器方法
}
//String转化成Dtae
@Override
public void <span style="color:#ff0000">setAsText</span><span style="color:#ff0000">(String text)</span> throws IllegalArgumentException {
//判断字符串是否为空
if (this.allowEmpty && !StringUtils.hasText(text)) {
setValue(null);
}
//判断字符串长度是否等于exactDateLength
else if (text != null && this.exactDateLength >= 0 && text.length() != this.exactDateLength) {
throw new IllegalArgumentException(
"Could not parse date: it is not exactly" + this.exactDateLength + "characters long");
}
else {
try {
<span style="color:#ff0000"> //将text格式化成Date对象</span>
<span style="color:#ff0000"> setValue(this.dateFormat.parse(text));</span>
}
catch (ParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
}
<span style="color:#ff0000">//从Date输出String</span>
@Override
public String <span style="color:#ff0000">getAsText</span>() {
Date value = (Date) getValue();
<span style="color:#ff0000">//返回格式化的String</span>
<span style="color:#ff0000">return (value != null ? this.dateFormat.format(value) : "");</span>
}
}
从CustomDateEditor的源码可以看出,最重要的是重写setAsText方法,先校验下字符串格式符不符合要求,不符合要求就抛出异常,再根据字符串转成指定DateFormat的Date对象
类型转换器
刚刚讲的属性编辑器是用来填充bean中的属性的,类型转换器是负责从数据转换成一个bean所以在转换的过程中,需要属性编辑器帮忙填充属性,那么应该持有一堆属性编辑器(bean有各种各样的属性),那么持有一个PropertyEditorRegistry(一个属性编辑器工厂)就可以了类型转化器的实现不像属性编辑器那么多,主要就是三个- TypeConverter,类型转换的接口
- TypeConverterSupport,类型转换的实现,持有一个TypeConverterDelegate,具体转换工作交给TypeConverterDelegate完成
- TypeConverterDelegate,类型转换的委托类,所有类型转换的工作都由他完成
TypeConverterDelegate源码分析
因为转换工作是由TypeConverterDelegate负责的,源码太长,就看看转换那一部分的代码/*
@Param propertyName bean的名称
@Param requiredType 需要的类型
@Param typeDescriptor 类型描述器
*/
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
<span style="color:#ff0000"> //从注册的属性编辑器中获取能编辑requiredType的属性编辑器</span>
<span style="color:#ff0000"> PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);</span>
//...
<span style="color:#ff0000"> //使用属性编辑器去把oldValue转化成requiredType</span>
<span style="color:#ff0000"> convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);</span>
//...
return convertedValue;
}
/*
@Param propertyName bean的名称
@Param requiredType 需要的类型
@Param editor 属性编辑器
*/
private Object doConvertValue(Object oldValue, Object newValue, Class<?> requiredType, PropertyEditor editor) {
Object convertedValue = newValue;
if (editor != null && !(convertedValue instanceof String)) {
try {
//转换数据
<span style="color:#ff0000">editor.setValue(convertedValue);</span>
//得到转换后的数据
<span style="color:#ff0000"> Object newConvertedValue = editor.getValue();</span>
if (newConvertedValue != convertedValue) {
<span style="color:#ff0000">convertedValue = newConvertedValue;</span>
editor = null;
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
}
}
}
Object returnValue = convertedValue;
//...
return returnValue;
}
}