spring 数据绑定

spring 官方文档

从官方文档,可以知道数据绑定属于核心功能。可以很方便的让用户的输入绑定到我们定义的对象上。用户输入包括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

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 类型转换体系。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。