夯实Spring系列|第十九章:Spring 数据绑定(Data Binding)

文章目录

  • 夯实Spring系列|第十九章:Spring 数据绑定(Data Binding)
    • 1.项目环境
    • 2.Spring 数据绑定使用场景
    • 3.Spring 数据绑定组件
    • 4.Spring 数据绑定元数据
      • 4.1 PropertyValues 来源
    • 5.Spring 数据绑定控制参数
      • 5.1 DataBinder 绑定特殊场景分析
      • 5.2 DataBinder 绑定控制参数
        • 5.2.1 ignoreUnknownFields
        • 5.2.2 autoGrowNestedPaths
        • 5.2.3 ignoreInvalidFields
        • 5.2.4 requiredFields
        • 5.2.5 allowedFields
        • 5.2.6 disallowedFields
    • 6.Spring 底层 Java Beans 替换实现
    • 7.BeanWrapper 的使用场景
    • 8.JavaBeans
    • 9.DataBinder 与 BeanWrapper 关系
    • 10.面试
      • 10.1 Spring 数据绑定 API 是什么?
      • 10.2 BeanWrapper 与 JavaBeans 之间关系是?
    • 11.参考

夯实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 属性值将被绑定为 小马哥

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核心编程思想》

你可能感兴趣的:(Spring系列,spring,数据绑定,Data,Binding)