SpringMVC原始碼閱讀:屬性編輯器、資料繫結
1.前言
SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友可以看 SpringMVC原始碼閱讀入門 ,它交代了SpringMVC的基礎知識和原始碼閱讀的技巧
本文將通過原始碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何通過型別轉換完成資料繫結和屬性編輯器的原理,並自定義屬性編輯器
2.原始碼分析
進入RequestMappingHandlerAdapter,該類支援引數解析和資料返回,進入invokeHandlerMethod方法
794行構造WebDataBinderFactory,傳入HandlerMethod引數
點進去getDataBinderFactory方法,看看它做什麼
886行獲取@ InitBinder 方法
891行查詢帶有@ControllerAdvice註解支援的Controller
看下RequestParamMethodArgumentResolver的父類AbstractNamedValueMethodArgumentResolver的resolveArgument方法
117行獲取到@InitBinder註解修飾的方法和@ControllerAdvice中的@InitBinder註解修飾的方法
118行建立一個ExtendedServletRequestDataBinder
120行arg獲取引數轉換結果
binderFactory變數是 WebDataBinderFactory型別,開啟WebDataBinderFactory,該類在Spring3.1引入,用來建立WebDataBinder
進入WebDataBinder,該類用於處理Web請求引數和JavaBean之間的資料繫結,ctrl+alt+h開啟類繼承圖,WebDataBinder繼承DataBinder
開啟DataBinder類,該類允許在目標物件上設定屬性值,支援資料驗證和繫結,實現了 PropertyEditorRegistry 和 TypeConverter
先開啟PropertyEditorRegistry,該類給註冊的JavaBean封裝方法,註釋提到被BeanWrapper繼承,由BeanWrapperImpl實現
BeanWrappert介面提供操作JavaBean的方法,配置set/get方法
再開啟TypeConverter,該類是定義型別轉換方法的介面,和 PropertyEditorRegistry 組合使用
最後我們找到PropertyEditor,它是屬性編輯的核心介面,看它的子類
稍後我們自定義屬性編輯器要繼承該類,重寫setAsText方法
3.例項
3.1 測試BeanWrapper
建立實體類TestModel
public class TestModel { private int age; private Date birth; private String name; private boolean good; private long times; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isGood() { return good; } public void setGood(boolean good) { this.good = good; } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } }
測試方法
@RequestMapping(value = "/testWrapper", produces={"application/json; charset=UTF-8"}) @ResponseBody public TestModel testWrapper() { TestModel tm = new TestModel(); BeanWrapper bw = new BeanWrapperImpl(tm); bw.setPropertyValue("good", "1"); return tm; }
瀏覽器輸入http://localhost:8080/springmvcdemo/test/testWrapper
在PropertyEditorSupport(實現PropertyEditor)的子類 CustomBooleanEditor中,setAsText方法對上述現象進行了處理
3.2 測試不使用BeanWrapper
@RequestMapping(value = "/testNotUseWrapper", produces={"application/json; charset=UTF-8"}) @ResponseBody public TestModel testNotUseWrapper() { TestModel tm = new TestModel(); BeanWrapperImpl bw = new BeanWrapperImpl(false); bw.setWrappedInstance(tm); bw.setPropertyValue("good", "1"); return tm; }
瀏覽器輸入http://localhost:8080/springmvcdemo/test/testNotUseWrapper
因為沒有對應的屬性編輯器,導致String型別“1”無法轉換成Boolean型別
3.3 測試無註解物件引數繫結
在 SpringMVC原始碼閱讀:Controller中引數解析 我說過,ServletModelAttributeMethodProcessor處理無註解物件
@RequestMapping(value = "testObj", produces={"application/json; charset=UTF-8"}) @ResponseBody public Map testObj(Employee e) { Map resultMap = new HashMap(); resultMap.put("Employee",e); return resultMap; }
瀏覽器輸入http://localhost:8080/springmvcdemo/test/testObj?id=1&name=s&age=12&dept.id=1&dept.name=20
resolveArgument方法在ServletModelAttributeMethodProcessor已廢棄,在其父類ModelAttributeMethodProcessor被實現
99行獲取引數別名
100行獲取屬性列表
110行建立ExtendedServletRequestDataBinder,前文已經說過
113行繫結請求引數,此時屬性列表引數繫結完畢
4.編寫自定義屬性編輯器
自定義屬性編輯器,實現PropertyEditorSupport
public class CustomDeptEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { if(text.indexOf(",") > 0) { Dept dept = new Dept(); String[] arr = text.split(","); dept.setId(Integer.parseInt(arr[0])); dept.setName(arr[1]); setValue(dept); } else { throw new IllegalArgumentException("dept param is error"); } } }
在TestController新增@InitBinder
@InitBinder public void initBinderDept(WebDataBinder binder) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor()); }
新增@ControllerAdvice,保證InitBinder應用到RequestMapping,就是說Controller裡定義的@InitBinder和自定義的@ControllerAdvice裡@InitBinder存在一個即可
@ControllerAdvice public class InitBinderControllerAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor()); } }
dispatcher-servlet需要配置component-scan,掃描到我們定義的ControllerAdvice
<context:component-scan base-package="org.format.demo.controlleradvice" />
瀏覽器輸入http://localhost:8080/springmvcdemo/test/testObj?id=1&name=s&age=12&dept=1,research
5.總結
PropertyEditor是屬性編輯器的介面,setAsText是核心方法,實現類PropertyEditorSupport
PropertyEditorRegistry介面給JavaBean註冊對應的屬性編輯器,實現類PropertyEditorRegistrySupport的createDefaultEditors建立預設的屬性編輯器
TypeConverter介面,通過該介面,可以將value轉換為指定型別物件,實現類TypeConverterSupport將型別轉換委託給TypeConverterDelegate處理
BeanWrapper介面操作JavaBean,配置set/get方法和查詢資料的可讀可寫性,實現類為BeanWrapperImpl
DataBinder用來set值和資料驗證,WebDataBinder處理對Web請求引數到JavaBean的資料繫結
RequestMappingHandlerAdapter呼叫invokeHandlerMethod方法建立WebDataBinderFactory,WebDataBinderFactory建立WebDataBinder
最後HandlerMethodArgumentResolver解析引數
6.參考
https://docs.spring.io/spring/docs/current/javadoc-api/
http://www.cnblogs.com/fangjian0423/p/springMVC-databind-typeconvert.html
https://github.com/spring-projects/spring-framework
文中難免有不足,還望指出
年三十晚上完成了這篇文章,新年快樂