文章目錄
夯實Spring系列|第十九章:Spring 數據綁定(Data Binding)
1.項目環境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模塊:data-binding
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 屬性值將被綁定爲小馬哥
。
- 假設 PropertyValues 中包含
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
- org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
- org.springframework.validation.DataBinder#getPropertyAccessor
- org.springframework.validation.DataBinder#applyPropertyValues
由上面調用鏈路可以知道當調用 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核心編程思想》