SpringMVC源碼閱讀:屬性編輯器、數據綁定

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html

本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何經過類型轉換完成數據綁定和屬性編輯器的原理,並自定義屬性編輯器java

2.源碼分析

進入RequestMappingHandlerAdapter,該類支持參數解析和數據返回,進入invokeHandlerMethod方法git

794行構造WebDataBinderFactory,傳入HandlerMethod參數github

點進去getDataBinderFactory方法,看看它作什麼spring

 

886行獲取@InitBinder方法json

891行查找帶有@ControllerAdvice註解支持的Controllerapi

看下RequestParamMethodArgumentResolver的父類AbstractNamedValueMethodArgumentResolver的resolveArgument方法瀏覽器

117行獲取到@InitBinder註解修飾的方法和@ControllerAdvice中的@InitBinder註解修飾的方法mvc

118行建立一個ExtendedServletRequestDataBinderapp

120行arg獲取參數轉換結果

binderFactory變量是WebDataBinderFactory類型,打開WebDataBinderFactory,該類在Spring3.1引入,用來建立WebDataBinder

進入WebDataBinder,該類用於處理Web請求參數和JavaBean之間的數據綁定,ctrl+alt+h打開類繼承圖,WebDataBinder繼承DataBinder

打開DataBinder類,該類容許在目標對象上設置屬性值,支持數據驗證和綁定,實現了PropertyEditorRegistryTypeConverter

先打開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/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

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

文中不免有不足,還望指出

年三十晚上完成了這篇文章,新年快樂

相關文章
相關標籤/搜索