對於MVC框架,參數綁定一直覺得是很神奇很方便的一個東西,在參數綁定的過程中利用了屬性編輯器、類型轉換器
參數綁定流程
參數綁定:把請求中的數據,轉化成指定類型的對象,交給處理請求的方法

- 請求進入到DisptacherServlet,卸下請求中的數據
- DisptacherServlet將請求中的數據發送給Controller
- 獲取Controller需要接收的參數類型,將參數類型和請求數據發送給DataBinder
- DataBinder將參數類型和請求數據再發給TypeConverter,由TypeConverter裝配成一個bean
- TypeConverter根據bean中的成員類型,在PropertyEditorRegistry中查找已注冊的PropertyEditor
- PropertyEditor將數據setter進bean中的成員
- TypeConverter將裝配好的bean返回給DataBinder
- DataBinder將裝配bean交給處理請求的方法
在參數綁定的過程TypeConverter和PropertyEditor是最核心的數據轉化成對象(非序列化)的過程TypeConverter負責將數據轉化成一個beanPropertyEditor負責將數據轉化成一個成員字段
屬性編輯器
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..
所有的屬性編輯器都是繼承PropertiesEditorSupport,默認的屬性編輯器,Spring在啟動的時候會自動加載除此之外,如果要裝配的屬性沒有合適的編輯器,還可以自定義屬性編輯器注冊了自定義的屬性編輯器之后,在CustomEditorConfigurer中注冊,應用全局都可以使用這個屬性編輯器,因為屬性編輯器的工廠是全局作用域的
PropertiesEditor源碼分析
PropertiesEditor.java
public class PropertiesEditor extends PropertyEditorSupport {
//將String轉成指定類型的對象
@Override
public void setAsText(String text) throws IllegalArgumentException {
Properties props = new Properties();//Properties以key-value存值
if (text != null) {
try {
//將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")));
}
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 {
//指定的date格式,如"yyyy-MM-dd"
private final DateFormat dateFormat;
//是否允許字符串為空
private final boolean allowEmpty;
//嚴格的日期長度
private final int exactDateLength;
public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) {
//構造器方法
}
public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty, int exactDateLength) {
//構造器方法
}
//String轉化成Dtae
@Override
public void setAsText(String text) 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 {
//將text格式化成Date對象
setValue(this.dateFormat.parse(text));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
}
//從Date輸出String
@Override
public String getAsText() {
Date value = (Date) getValue();
//返回格式化的String
return (value != null ? this.dateFormat.format(value) : "");
}
}
從CustomDateEditor的源碼可以看出,最重要的是重寫setAsText方法,先校驗下字符串格式符不符合要求,不符合要求就拋出異常,再根據字符串轉成指定DateFormat的Date對象
類型轉換器
剛剛講的屬性編輯器是用來填充bean中的屬性的,類型轉換器是負責從數據轉換成一個bean所以在轉換的過程中,需要屬性編輯器幫忙填充屬性,那么應該持有一堆屬性編輯器(bean有各種各樣的屬性),那么持有一個PropertyEditorRegistry(一個屬性編輯器工廠)就可以了類型轉化器的實現不像屬性編輯器那么多,主要就是三個
- TypeConverter,類型轉換的接口
- TypeConverterSupport,類型轉換的實現,持有一個TypeConverterDelegate,具體轉換工作交給TypeConverterDelegate完成
- TypeConverterDelegate,類型轉換的委托類,所有類型轉換的工作都由他完成
要實現的方法就只有convertIfNecessary,從源對象轉換為目標對象
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 {
//從注冊的屬性編輯器中獲取能編輯requiredType的屬性編輯器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
//...
//使用屬性編輯器去把oldValue轉化成requiredType
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
//...
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 {
//轉換數據
editor.setValue(convertedValue);
//得到轉換后的數據
Object newConvertedValue = editor.getValue();
if (newConvertedValue != convertedValue) {
convertedValue = newConvertedValue;
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;
}
}

