从官方文档,可以知道数据绑定属于核心功能。可以很方便的让用户的输入绑定到我们定义的对象上。用户输入包括mvc的入参,properties配置文件,系统变量。
数据绑定组件
因为数据绑定可以应用在不同的场景,所以针对不同的场景有不同的实现,如:
- WebDataBinder
- ServletRequestDataBinder
- WebRequestDataBinder
- WebExchangeDataBinder
核心的数据绑定组件是 DataBinder,核心属性如下:
- target 目标bean
- objectName:目标Bean名称
- bindingResult:属性绑定结果
- typeConverter 类型转换器
- conversionService : 类型转换服务
- messageCodesResolver:校验错误文案处理器
- validators:关联的 bean vaalidator 实例集合
DataBinder 与 BeanWrapper(DataBinder 的 属性绑定操作会委派给 BeanWrapper)
- bind方法生成 BeanPropertyBindingResult
- BeanPropertyBindingResult 关联 BeanWrapper
绑定方法:bind(PropertyVaalues)
DataBinder 元数据 - PropertyValues
特征 | 说明 |
---|---|
数据来源 | BeanDefinition,主要来源XML资源配置BeanDefinition |
数据结构 | 由一个或多个PropertyValue组成 |
成员结构 | propertyValue包含属性名称,以及属性值(包括原始值、类型转换后的值) |
常见实现 | MutablePropertyValues |
web 扩展实现 | ServletConfigPropertyValues、ServletRequestParameterPropertyValues |
相关生命周期 | InstantiationAwareBeanPostProcessor#postProcessProperties |
spring 数据绑定控制参数
参数名称 | 说明 |
---|---|
ignoreUnknownFields | 是否忽略未知字段,默认值:true |
ignoreInvalidFields | 是否忽略非法字段,默认值:false |
autoGrowNestedPaths | 是否自动增加嵌套路径,默认:true。false 嵌套的对象必须显示的传入,否则报错。true 自动创建嵌套对象 |
allowedFieldss | 绑定字段白名单 |
disallowedFields | 绑定字段黑名单 |
requiedFields | 必须绑定字段。如果没有配置这里的字段捕获报错,会以BindingResult的方式返回,里面有详细的错误信息 |
DataBinder 绑定特殊场景分析
场景1
使用默认参数,只设置必要的属性,绑定属性。
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "why");
map.put("address.city", "beijing");
PropertyValues propertyValues = new MutablePropertyValues(map);
dataBinder.bind(propertyValues);
System.out.println(user);
// 结果,绑定属性,成功,并且嵌套对象的属性也绑定成功了,说明autoGrowNestedPaths参数是有效的。
// User{id=1, name='why', address=Address{province='null', city='beijing', street='null'}, city=null, workCities=null}
如果我们修改dataBinder.setAutoGrowNestedPaths(false);,就会抛异常:
Exception in thread "main" org.springframework.beans.NullValueInNestedPathException: Invalid property 'address' of bean class [pojo.User]: Value of nested property 'address' is null
要修复这个问题也很简单,只要手动创建一个Address对象设置到User对象里即可。
场景2
当我们设置id是string是,是不能转换成Integer的,这时会从BindingResult获取到失败的记录。
map.put("id", "A");
BindingResult bindingResult = dataBinder.getBindingResult();
System.out.println("bindingResult = " + bindingResult);
// bindingResult = org.springframework.validation.BeanPropertyBindingResult: 1 errors
// Field error in object 'user' on field 'id': rejected value [A]; codes [typeMismatch.user.id,typeMismatch.id,typeMismatch.java.lang.Integer,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.id,id]; arguments []; default message [id]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'id'; nested exception is java.lang.NumberFormatException: For input string: "A"]
场景3
当PropertyValues中包含名称 x 的propertyValue,目标对象不存在x属性,当bind方法执行会发生什么?
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "why");
map.put("XX", "XX");
PropertyValues propertyValues = new MutablePropertyValues(map);
dataBinder.bind(propertyValues);
System.out.println(user);
// 忽略了不存在的属性,并且也没有异常。
// 原因是 IgnoreUnknownFields 属性默认是true,会忽略掉unknown fileld
// User{id=1, name='why', address=null, city=null, workCities=null}
我们设置 dataBinder.setIgnoreUnknownFields(false); 会抛出异常
Exception in thread "main" org.springframework.beans.NotWritablePropertyException: Invalid property 'XX' of bean class [pojo.User]: Bean property 'XX' is not writable or has an invalid setter method. Did you mean 'id'?
场景4
当PropertyValues中包含名称 x 的propertyValue,目标对象存在x属性,当bind方法时,如何忽略x属性不被绑定。
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
Map<String, Object> map = new HashMap<>();
map.put("id", "2");
map.put("name", "why");
PropertyValues propertyValues = new MutablePropertyValues(map);
// 通过黑名单设置忽略的属性
dataBinder.setDisallowedFields("name");
dataBinder.bind(propertyValues);
System.out.println(user);
// User{id=2, name='null', address=null, city=null, workCities=null}
场景5
当PropertyValues中包含名称 x,y,x... 的propertyValue,目标对象存在x属性,当bind方法时,如何只绑定x属性。
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
Map<String, Object> map = new HashMap<>();
map.put("id", "2");
map.put("name", "why");
PropertyValues propertyValues = new MutablePropertyValues(map);
// 通过白名单设置需要绑定的属性
dataBinder.setAllowedFields("name");
dataBinder.bind(propertyValues);
System.out.println(user);
// User{id=null, name='why', address=null, city=null, workCities=null}
场景6
当PropertyValues中包含名称 x.y 的propertyValue,目标对象不存在x属性嵌套y属性,当bind方法执行会发生什么?
User user = new User();
DataBinder dataBinder = new DataBinder(user, "user");
Map<String, Object> map = new HashMap<>();
map.put("id", "2");
map.put("name", "why");
map.put("address.XX", "XX");
PropertyValues propertyValues = new MutablePropertyValues(map);
dataBinder.bind(propertyValues);
System.out.println(user);
// 我们看到结果,默认是忽略不存在的嵌套属性的
// User{id=2, name='why', address=Address{province='null', city='null', street='null'}, city=null, workCities=null}
深入bind方法
代码的主题流程如下:
// 入口方法
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
doBind(mpvs);
}
protected void doBind(MutablePropertyValues mpvs) {
// 两个校验方法,可忽略
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
// 关键的方法,创建了一个 BeanWrapper,通过 BeanWrapper 的方法 setPropertyValues 设置属性
// org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
@Override
public final ConfigurablePropertyAccessor getPropertyAccessor() {
if (this.beanWrapper == null) {
this.beanWrapper = createBeanWrapper();
this.beanWrapper.setExtractOldValueForEditor(true);
this.beanWrapper.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
this.beanWrapper.setAutoGrowCollectionLimit(this.autoGrowCollectionLimit);
}
return this.beanWrapper;
}
具体的逻辑委派给了 BeanWrapper ,先看下 BeanWrapper 的继承关系如下。
BeanWrapper 使用场景:
- spring 底层 JavaBeans 基础设施的核心接口。spring 在处理 javaBean 的时候,实际上用到了 javaBeans 里的api,这些 api操作都封装在 BeanWrapper 中。也可以把 BeanWrapper 理解为 javaBeans api 的装饰。
- 通常不会直接使用,间接用于 BeanFactory, DataBinder。
- DataBinder#bind 方法也是间接的通过 BeanWrapper 设置属性。
- BeanFactory 主要是在创建 bean 的时候会用到 beanWraapper。
在 AbstractAutowireCapableBeanFactory#populateBean 方法的最后,调用 #applyPropertyValues 方法设置 bean的属性,就是通过 BeanWrapper实现的。
- 提供标准 JavaBeans分析和操作,能够单独或者批量存储 JavaBean的属性
- 致辞嵌套属性路径 nested path
- 实现类 org.springframework.beans.BeansWrapperImpl,唯一实现。
spring 底层 java beans 替换实现
spring 是对 java beans api 进行了封装,但是没有全盘使用,下面是比较内容。
JavaBeans 核心实现 - java.beans.BeanInfo
- 属性(Property)java.beans.PropertyEditor
- propertyDescriptor
- 方法 method
- 事件 Event
- 表达式 Expresion
spring 的 BeanWrapper 没有完全实现 javaBeans 的所有功能,主要实现了 PropertyEditor ,方法、事件、表达式使用的 spring 体系的,使用起来比较友好。
- 属性 property java.beans.PropertyEditor
- PropertyDescriptor, javaBeans 的属性描述
- 嵌套属性路径 nested path
JavaBeans 简介
javaBeans 是一套完整的标准api,spring 也是基于 javaBean 扩展的。尤其像 BeanWrapper。
下面是一些核心api:
API | 说明 |
---|---|
java.beans.Introspector | 内省api,可以获取 BeanInfo |
java.beans.BeanInfo | bean 原信息,包含 bean 的精确信息 |
java.beans.BeanDescriptor | 描述 bean 的全局信息 |
java.beans.MethodDescriptor | 可访问的方法的描述信息 |
java.beans.EventSetDescriptor | 事件集合描述符 |
BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
beanInfo.getPropertyDescriptors();
beanInfo.getMethodDescriptors();
beanInfo.getEventSetDescriptors();
思考题与总结
spirng 的数据绑定(bean创建,DataBinder)是委派给 BeanWrapper 来实现,beanWrapper 是对 javaBeans 标准 api 的封装,忽略了不常用的功能,某些功能使用 spring 的实现,如 PropertyEditor 替换为 TypeConverter,更适合在 spring 技术体系内使用。
spirng 数据绑定 API 是什么?
org.springframework.validation.DataBinder, 只针对 web 场景有特定的实现。
beanWrapper 与 javabeans 的关系?
javabeans是标准api,beanWrapper是基于javaBeans的封装,使用更加友好
BeanWrapper的唯一一个实现是 BeanWrapperImpl,一般不需要扩展。
DataBinder 是怎么完成属性类型的转换的
spring淘汰了 propertyEditor的方式,用 ConversionService 实现类型转换。这就涉及到了 spring 类型转换体系。