對於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; } }