夯實Spring系列|第十九章:Spring 數據綁定(Data Binding)

夯實Spring系列|第十九章:Spring 數據綁定(Data Binding)

1.項目環境

2.Spring 數據綁定使用場景

Spring BeanDefinition 到 Bean 實例創建

Spring 數據綁定(DataBinder)

Spring Web 參數綁定(WebDataBinder)

3.Spring 數據綁定組件

標準組件

  • org.springframework.validation.DataBinder

Web 組件

  • org.springframework.web.bind.ServletRequestDataBinder
  • org.springframework.web.bind.WebDataBinder
  • org.springframework.web.bind.support.WebExchangeDataBinder - @since 5.0
  • org.springframework.web.bind.support.WebRequestDataBinder

DataBinder 核心屬性

屬性 說明
target 關聯目標 Bean
objectName 目標 Bean 名稱
bindingResult 屬性綁定結果
typeConverter 類型轉換器
conversionService 類型轉換服務
messageCodesResolver 效驗錯誤文案 Code 處理器
validators 關聯的 Bean Validator 實例集合

DataBinder 綁定方法

  • bind(PropertyValues):將 PropertyValue Key-Value 內容映射到關聯的 Bean(target)中的屬性上
    • 假設 PropertyValues 中包含 name = 小馬哥 的鍵值對,同時 User 對象中存在 name 屬性,當 bind 方法執行時,User 對象中的 name 屬性值將被綁定爲 小馬哥

4.Spring 數據綁定元數據

DataBinder 元數據 - PropertyValues

特徵 說明
數據來源 BeanDefinition,主要來源 XML 資源配置 BeanDefinition
數據結構 由一個或者多個 PropertyValue 組成
成員結構 PropertyValue 包含屬性名稱,以及屬性值(包括原始值、類型轉換後的值)
常見實現 MutablePropertyValues
Web 擴展實現 ServletConfigPropertyValues、ServletRequestParameterPropertyValues
相關生命週期 InstantiationAwareBeanPostProcessor#postProcessProperties

4.1 PropertyValues 來源

通常來源於 XML 資源配置 BeanDefinition,因爲 @Bean 或者其他編程方式,屬性值可以直接使用,並不需要使用 PropertyValues 來進行轉換。

通過 org.springframework.beans.factory.config.BeanDefinition#getPropertyValues 來獲取。

5.Spring 數據綁定控制參數

5.1 DataBinder 綁定特殊場景分析

  • 當 PropertyValues 中包含名稱 x 的 PropertyValue,目標對象 B 不存在 x 屬性,當 bind 方法執行時,會發生什麼?
  • 當 PropertyValues 中包含名稱 x 的 PropertyValue,目標對象 B 存在 x 屬性,當 bind 方法執行時,如何避免 B 屬性 x 不被綁定?
  • 當 PropertyValues 中包含名稱 x.y 的 PropertyValue,目標對象 B 不存在 x 屬性(嵌套 y 屬性),當 bind 方法執行時,會發生什麼?

示例

/**
 * {@link DataBinder} 示例
 *
 * @see DataBinder
 */
public class DataBinderDemo {
    public static void main(String[] args) {
        User user = new User();
        DataBinder dataBinder = new DataBinder(user, "user");

        MutablePropertyValues mpvs = new MutablePropertyValues();

        mpvs.add("id", "1")
                .add("name", "xwf")
                // 添加一個不存在的屬性值
                // DataBinder 忽略未知屬性
                .add("otherName", "xwf")
                // 嵌套屬性
                .add("company.name", "阿里");

        dataBinder.bind(mpvs);
        System.out.println(user);
    }
}

新增 Company 類

public class Company {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Company{" +
                "name='" + name + '\'' +
                '}';
    }
}

將屬性增加到 User 類中,並新增 setter/getter 方法,重寫 toString 方法。

執行結果:

User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=Company{name='阿里'}}

由結果可以得到兩個結論

1.添加一個不存在的屬性值,DataBinder 忽略未知屬性

2.DataBinder 支持嵌套屬性

5.2 DataBinder 綁定控制參數

參數名稱 說明
ignoreUnknownFields 是否忽略未知字段,默認值:true
ignoreInvalidFields 是否忽略非法字段,默認值:false
autoGrowNestedPaths 是否自動增加嵌套路徑,默認值:true
allowedFields 綁定字段白名單
disallowedFields 綁定字段黑名單
requiredFields 必須綁定字段

5.2.1 ignoreUnknownFields

public class DataBinderDemo {
    public static void main(String[] args) {
        User user = new User();
        DataBinder dataBinder = new DataBinder(user, "user");

        MutablePropertyValues mpvs = new MutablePropertyValues();

        mpvs.add("id", "1")
                .add("name", "xwf")
                // 添加一個不存在的屬性值
                // DataBinder 忽略未知屬性
                .add("otherName", "xwf")
                // 嵌套屬性
                .add("company.name", "阿里");

        // 1.ignoreUnknownFields true(默認) -> false
        dataBinder.setIgnoreUnknownFields(false);

        dataBinder.bind(mpvs);
        // 輸出
        System.out.println(user);
    }
}

執行結果:

Exception in thread "main" org.springframework.beans.NotWritablePropertyException: Invalid property 'otherName' of bean class [com.huajie.thinking.in.spring.ioc.overview.domain.User]: Bean property 'otherName' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

結論:如果設置 ignoreUnknownFields 爲false,綁定的字段不存在,拋出異常 NotWritablePropertyException

5.2.2 autoGrowNestedPaths

public class DataBinderDemo {
    public static void main(String[] args) {
        User user = new User();
        DataBinder dataBinder = new DataBinder(user, "user");

        MutablePropertyValues mpvs = new MutablePropertyValues();

        mpvs.add("id", "1")
                .add("name", "xwf")
                // 添加一個不存在的屬性值
                // DataBinder 忽略未知屬性
                .add("otherName", "xwf")
                // 嵌套屬性
                .add("company.name", "阿里");

        // 1.ignoreUnknownFields true(默認) -> false
//        dataBinder.setIgnoreUnknownFields(false);

        // 2.autoGrowNestedPaths true(默認) -> false
        dataBinder.setAutoGrowNestedPaths(false);
        
        dataBinder.bind(mpvs);
        // 輸出
        System.out.println(user);
    }
}

執行結果:

Exception in thread "main" org.springframework.beans.NullValueInNestedPathException: Invalid property 'company' of bean class [com.huajie.thinking.in.spring.ioc.overview.domain.User]: Value of nested property 'company' is null

設置 autoGrowNestedPaths 爲 false 不支持嵌套屬性。

5.2.3 ignoreInvalidFields

ignoreInvalidFields 屬性設置爲 true,可以忽略 5.2.2 中的錯誤,但是相應出錯的屬性也不會設置

public class DataBinderDemo {
    public static void main(String[] args) {
        User user = new User();
        DataBinder dataBinder = new DataBinder(user, "user");

        MutablePropertyValues mpvs = new MutablePropertyValues();

        mpvs.add("id", "1")
                .add("name", "xwf")
                // 添加一個不存在的屬性值
                // DataBinder 忽略未知屬性
                .add("otherName", "xwf")
                // 嵌套屬性
                .add("company.name", "阿里");

        // 1.ignoreUnknownFields true(默認) -> false
//        dataBinder.setIgnoreUnknownFields(false);

        // 2.autoGrowNestedPaths true(默認) -> false
        dataBinder.setAutoGrowNestedPaths(false);

        // 3.ignoreInvalidFields false(默認) -> true
        dataBinder.setIgnoreInvalidFields(true);

        dataBinder.bind(mpvs);
        // 輸出
        System.out.println(user);
    }
}

執行結果:

User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}

5.2.4 requiredFields

  • 錯誤信息需要通過 dataBinder.getBindingResult(); 獲取,不會報錯
public class DataBinderDemo {
    public static void main(String[] args) {
        User user = new User();
        DataBinder dataBinder = new DataBinder(user, "user");

        MutablePropertyValues mpvs = new MutablePropertyValues();

        mpvs.add("id", "1")
                .add("name", "xwf")
                // 添加一個不存在的屬性值
                // DataBinder 忽略未知屬性
                .add("otherName", "xwf")
                // 嵌套屬性
                .add("company.name", "阿里");

        // 1.ignoreUnknownFields true(默認) -> false
//        dataBinder.setIgnoreUnknownFields(false);

        // 2.autoGrowNestedPaths true(默認) -> false
        dataBinder.setAutoGrowNestedPaths(false);

        // 3.ignoreInvalidFields false(默認) -> true
        dataBinder.setIgnoreInvalidFields(true);

        // 4.requiredFields 設置不能爲空的字段
        dataBinder.setRequiredFields("id","name","age");

        dataBinder.bind(mpvs);
        // 輸出
        System.out.println(user);
        // 綁定結果(結果包含了錯誤文案信息,但是不會報錯)
        BindingResult bindingResult = dataBinder.getBindingResult();
        System.out.println(bindingResult);

    }
}

執行結果:

User{id=1, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'user' on field 'age': rejected value []; codes [required.user.age,required.age,required.java.lang.Integer,required]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [Field 'age' is required]

5.2.5 allowedFields

相關設置

dataBinder.setAllowedFields("id");//表示只允許 id 字段進行綁定

執行結果:

User{id=1, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}

5.2.6 disallowedFields

相關設置

dataBinder.setDisallowedFields("id");//表示不允許 id 字段進行綁定

執行結果:

User{id=null, name='xwf', age=null, configFileReource=null, city=null, cities=null, lifeCities=null, company=null}

6.Spring 底層 Java Beans 替換實現

JavaBeans 核心實現 - java.beans.BeanInfo

  • 屬性(Property)
    • java.beans.PropertyEditor
  • 方法(Method)
  • 事件(Event)
  • 表達式(Expression)

Spring 替代實現 - org.springframework.beans.BeanWrapper

  • 屬性(Property)
    • java.beans.PropertyEditor
  • 嵌套屬性路徑(nested path)

7.BeanWrapper 的使用場景

BeanWrapper

  • Spring 底層 JavaBeans 基礎設施的中心化接口
  • 通常不會直接使用,間接用於 BeanFactory 和 DataBinder
  • 提供標準 JavaBeans 分析和操作,能夠單獨或者批量存儲 Java Bean 的屬性(Properties)
  • 支持嵌套屬性路徑(nested path)
  • 實現類 org.springframework.beans.BeanWrapperImpl

BeanWrapper 接口源碼中相關的方法

  • setAutoGrowCollectionLimit

    • org.springframework.beans.AbstractNestablePropertyAccessor 中

      private int autoGrowCollectionLimit = Integer.MAX_VALUE;
      

      默認實現中這個值是無限制的,表示支持無線的嵌套路徑

  • getWrappedInstance 獲取 Bean 的實例,在 IoC 場景下的 BeanWrapper 是和 Bean 關聯的

  • getWrappedClass 獲取 Bean 的 Class

  • getPropertyDescriptors 獲取所有屬性的描述信息

  • getPropertyDescriptor 獲取單個屬性的描述信息

8.JavaBeans

標準 JavaBeans 是如何操作屬性的?

API 說明
java.beans.Introspector Java Beans 內省 API
java.beans.BeanInfo Java Bean 元信息 API
java.beans.BeanDescriptor Java Bean 信息描述符
java.beans.PropertyDescriptor Java Bean 屬性描述符
java.beans.MethodDescriptor Java Bean 方法描述符
java.beans.EventSetDescriptor Java Bean 事件集合描述符

簡單示例

/**
 * JavaBeans 示例
 */
public class JavaBeansDemo {
    public static void main(String[] args) throws IntrospectionException {
        // stopClass 排除類
        BeanInfo beanInfo = Introspector.getBeanInfo(User.class,Object.class);

        Stream.of(beanInfo.getPropertyDescriptors()).forEach(propertyDescriptor -> {
            System.out.println(propertyDescriptor);
        });

        Stream.of(beanInfo.getMethodDescriptors()).forEach(System.out::println);
    }
}

9.DataBinder 與 BeanWrapper 關係

  • bind 方法生成 BeanPropertyBindingResult
  • BeanPropertyBindingResult 關聯 BeanWrappper

源碼調用鏈路

org.springframework.validation.DataBinder#bind

  • org.springframework.validation.DataBinder#doBind
    • org.springframework.validation.DataBinder#applyPropertyValues
      • org.springframework.validation.DataBinder#getPropertyAccessor
        • org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
          • org.springframework.validation.BeanPropertyBindingResult#createBeanWrapper

由上面調用鏈路可以知道當調用 DataBinder#bind 方法時,默認會創建 BeanWrapper 對象,此對象和 BeanPropertyBindingResult 進行關聯。

10.面試

10.1 Spring 數據綁定 API 是什麼?

  • org.springframework.validation.DataBinder

  • org.springframework.validation.BeanPropertyBindingResult

10.2 BeanWrapper 與 JavaBeans 之間關係是?

  • Spring 底層 JavaBeans 基礎設施的中心化接口
  • BeanWrapper 有且僅有一個實現類 BeanWrapperImpl,BeanWrapperImpl 底層的一些實現是基於 JavaBeans 進行的二次封裝

11.參考

  • 極客時間-小馬哥《小馬哥講Spring核心編程思想》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章