spring 数据绑定

2020-06-06  本文已影响0人  但莫
spring 官方文档

从官方文档,可以知道数据绑定属于核心功能。可以很方便的让用户的输入绑定到我们定义的对象上。用户输入包括mvc的入参,properties配置文件,系统变量。

数据绑定组件

因为数据绑定可以应用在不同的场景,所以针对不同的场景有不同的实现,如:

核心的数据绑定组件是 DataBinder,核心属性如下:

DataBinder 与 BeanWrapper(DataBinder 的 属性绑定操作会委派给 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 底层 java beans 替换实现

spring 是对 java beans api 进行了封装,但是没有全盘使用,下面是比较内容。

JavaBeans 核心实现 - java.beans.BeanInfo

spring 的 BeanWrapper 没有完全实现 javaBeans 的所有功能,主要实现了 PropertyEditor ,方法、事件、表达式使用的 spring 体系的,使用起来比较友好。

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

上一篇 下一篇

猜你喜欢

热点阅读