一、概述

Spring 数据绑定 默认情况下只知道如何把 简单类型 (如 int 、 string 、 boolean )自动绑定到与之对应的 Java 类型的属性上。对于复杂的数据绑定场景,开发者可以通过扩展 Spring 数据绑定实现。这篇文章通过源码阅读系统了解 Spring 如何进行数据绑定。可以带着问题读:

  1. 既然有转换(绑定),那得需要一个类表示转换前的 数据状态 。
  2. 既然有转换(绑定),得有一个专门的类来完成这一 动作 。

    二、DataBinder

    2.1 概述

    DataBinder 允许将属性值设置到目标对象上,支持 验证 和 绑定结果分析 。可以指定 允许字段(allowedFields) 、 必填字段(requiredFields) 、 不允许字段(disallowedFields) 、 自定义属性编辑器 等精确控制绑定绑定过程。

    2.2 DataBinder 体系结构

    DataBinder-spring-web.png
  • DataBinder 实现 PropertyEditorRegistry 和 TypeConverter 接口,PropertyEditorRegistry 接口提供 属性编辑器(PropertyEditor) 注册 与 查询 ,TypeConverter 接口提供 类型转换 ,包括通过 JDK 扩展的 PropertyEditor 以及 spring-beans 封装关于类型转换的接口 TypeConverter 。有关类型转换详见
  • WebDataBinder 基于 DataBinder 做进一步扩展,用于 Web 环境。
  • DataBinder 可以通过 BindingResult 接口( getBindingResult() )获取属性绑定结果,该接口实现Error 接口。默认情况下,错误生成是通过 BindingErrorProcessor 策略生成,可以通过 setBindingErrorProcessor 方法设置覆盖默认策略,生成不同类型的错误代码。
  • 可以自定义 MessageSource 自定义校验错误,通常需要将此类错误代码解析为适当的用户可见的错误消息。

    2.3 DataBinder 核心属性

    | 属性 | 说明 | | —- | —- | | target | 关联目标Bean | | objectName | 目标Bean名称 | | bindingResult | 属性绑定结果 | | typeConverter | 类型转换器 | | conversionService | 类型转换服务 | | messageCodesReolver | 校验错误文案Code处理器 | | validators | 关联的Bean Validator实例集合 |
  1. public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  2. /** Default object name used for binding: "target". */
  3. public static final String DEFAULT_OBJECT_NAME = "target";
  4. /** Default limit for array and collection growing: 256. */
  5. public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;
  6. @Nullable
  7. private final Object target;
  8. private final String objectName;
  9. @Nullable
  10. private AbstractPropertyBindingResult bindingResult;
  11. private boolean directFieldAccess = false;
  12. @Nullable
  13. private SimpleTypeConverter typeConverter;
  14. private boolean ignoreUnknownFields = true;
  15. private boolean ignoreInvalidFields = false;
  16. private boolean autoGrowNestedPaths = true;
  17. private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
  18. @Nullable
  19. private String[] allowedFields;
  20. @Nullable
  21. private String[] disallowedFields;
  22. @Nullable
  23. private String[] requiredFields;
  24. // 类型转换器
  25. @Nullable
  26. private ConversionService conversionService;
  27. // 消息编码解析器
  28. @Nullable
  29. private MessageCodesResolver messageCodesResolver;
  30. // 绑定错误执行器(在绑定过程中生成一系列错误消息)
  31. private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
  32. private final List<Validator> validators = new ArrayList<>();
  33. ...
  34. }

2.4 DataBinder 示例

private static void doSimpleSiturationDataBinder() {
    User user = new User();
    // #1.创建DataBinder
    DataBinder binder = new DataBinder(user, "user");

    // #2.创建PropertyValues对象
    Map<String, Object> source = new HashMap<>();
    source.put("name", "Clarencezero");
    source.put("age", 24);
    PropertyValues propertyValue = new MutablePropertyValues(source);

    // #3.绑定(需要对象提供属性的setter方法)
    binder.bind(propertyValue);
}

2.5 DataBinder绑定特殊场景分析

  • 当 PropertyValues 中包含名称 x 的 PropertyValue ,目标对象 B 不存在 x 属性,当 bind 方法执行时
    • Nothing
  • 当 PropertyValues 中包含名称 x.y 的 PropertyValue ,目标对象 B 存在 x 属性(嵌套 y 属性),当 bind 方法执行时,会发生什么?
  • 当 PropertyValues 中包含名称 x 的 PropertyValue ,目标对象 B 存在 x 属性,当 bind 方法执行时,如何避免 B 属性 x 不被绑定?

    DataBinder绑定控制参数

    可以使用如下绑定控制参数进行精确绑定操作
参数名称 说明
ignoreUnknownFields 是否忽略未知字段,默认值: true
ignoreInvalidFields 是否忽略非法字段,默认值: true
autoGrowNestedPaths 是否自动增加嵌套路径,默认值: true
allowedFields 绑定字段白名单
disallowedFields 绑定字段黑名单
requiredFields 必须绑定字段

2.6 通过源码分析 DataBinder 绑定过程

DataBinder 最重要的方法是 bind(PropertyValues)

// org.springframework.validation.DataBinder#bind
public void bind(PropertyValues pvs) {
    MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ?
                                  (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
    doBind(mpvs);
}
// org.springframework.validation.DataBinder#doBind
protected void doBind(MutablePropertyValues mpvs) {
    // #1 根据允许的字段检查给定的属性值,删除不允许的字段的值。
    checkAllowedFields(mpvs);

    // #2 检查必须字段
    checkRequiredFields(mpvs);

    // #3 进行属性绑定
    applyPropertyValues(mpvs);
}
// org.springframework.validation.DataBinder#applyPropertyValues
protected void applyPropertyValues(MutablePropertyValues mpvs) {
    try {
        // #1 getPropertyAccessor(): 得到属性获取器,实际上是得到BeanWrapper实现类,下面有代码详细分析
        getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
    } catch (PropertyBatchUpdateException ex) {
        // Use bind error processor to create FieldErrors.
        for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
            getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
        }
    }
}

// org.springframework.validation.DataBinder#getPropertyAccessor
protected ConfigurablePropertyAccessor getPropertyAccessor() {
    return getInternalBindingResult().getPropertyAccessor();
}

// org.springframework.validation.DataBinder#getInternalBindingResult
private boolean directFieldAccess = false;
protected AbstractPropertyBindingResult getInternalBindingResult() {
    // 根据directFieldAccess创建 AbstractPropertyBindingResult 对象,该抽象类有两个实现类,分别是
    // org.springframework.validation.DirectFieldBindingResult(使用反向完成属性操作)
    // org.springframework.validation.BeanPropertyBindingResult(使用标准 JavaBeans API 完成属性操作)
    // Spring 默认选用后者完成对属性操作
    if (this.bindingResult == null) {
        this.bindingResult = (this.directFieldAccess ?
                              createDirectFieldBindingResult() : createBeanPropertyBindingResult());
    }
    return this.bindingResult;
}

// org.springframework.validation.BeanPropertyBindingResult#getPropertyAccessor
// 底层使用 Beanwrapper 对对象进行包装,也就是说,
// 其实 org.springframework.validation.DataBinder#getPropertyAccessor也就返回一个BeanWrapperImpl 对象
// 并利用它来完成类型转换、校验等一系列操作。绑定过程中异常处理是由DateBinder本身完成的
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;
}

DataBinder-getpropertyaccessor.png

2.7 DateBinder小结

  1. DateBinder 可单独使用进行对象属性绑定操作。
  2. DateBinder 通过构造一个 BeanWrapper 实例对象,由 beanwrapperimpl 实现类来完成最终的类型转换操作。
  3. DateBinder 通过 BindingErrorProcessor 绑定异常处理器记录绑定过程中出现的异常和错误。如 类型不匹配 、 必须字段缺失 等等。

    三、元数据

    对于 数据绑定 过程而言, Spring 把属性值封装为 PropertyValue ,而 PropertyValues(它只是一个接口,MultablePropertyValues是实现类) 持有一个 List<PropertyValue> 集合。

    3.1 PropertyValue

    持有单个 bean 属性的信息和值的对象。
    在这里使用对象,而不仅仅把所有属性值存储在一个Map的键值对中,是因为它可以提供更大的灵活性,并能够以优化的方式处理索引属性等。此外 PropertyValue 实现多个接口方便属性、值的解析与获取。
    PropertyValue.png

3.2 PropertyValues (接口)

3.2.1 概述

特征 说明
数据来源 BeanDefinition,主要来源 XML 资源配置BeanDefinition
数据结构 由一个或多个 PropertyValue 组成
成员结构 PropertyValue包含属性名称,以及属性值(包含原始值、类型转换后的值)
常见实现 MutablePropertyValues
Web扩展实现 ServletConfigPropertyValues
ServletRequestParameterPropertyValues
相关生命周期 InstantiationAwareBeanPostProcessor#postProcessorProperties

3.2.2 继承体系

MutablePropertyValues.png

PropertyValue 数据主要来源 XML 文件,如果通过Java代码是不需要进行类型转换。因为XML定义的是字符串类型,但我们需要把这里 属性值 转换为对象的属性类型,比如 枚举、Integer 等。

3.2.3 PropertyValues接口方法

propertyvalues-method.png

3.2.4 实现类 — MutablePropertyValues

MutablePropertyValues是PropertyValue 默认实现,允许简单地操作属性,并提供构造函数以支持从 Map 中进行深度复制和构造。接口方法(主要):
mutablepropertyvalues-add-method.png<br />

3.2.5 使用 MutablePropertyValues 绑定示例:

public class User {
    List<String> names;

    public List<String> getNames() {
        return names;
    }

    public void setNames(List<String> names) {
        this.names = names;
    }
}
private static void doDataBindList(){
    User user = new User();
    DataBinder dataBinder = new DataBinder(user, "user");

    MutablePropertyValues propertyValues = new MutablePropertyValues();
    propertyValues.add("names[0]", "FirstName");
    propertyValues.add("names[1]", "LastName");
    dataBinder.bind(propertyValues);
    System.out.println(user);
    System.out.println(user.getNames().size());
}

五、PropertyEditor(转换器,JDK提供)

5.1 概述

PropertyEditor 类原本是在 GUI 界面使用,允许用户编辑给定类型的属性值。支持多种不同种类的显示和更新属性值的方式。它规定了将外部值转换为内部 JavaBean 属性值的 转换 接口方法。接口如下:
propertyeditor-interface.png
PropertyEditor 接口方法是 内部属性值 和 外部设置值 的沟通桥梁。PropertyEditor 默认实现是 PropertyEditorSupport ,用户可以通过此类设计自己的实现类。

5.2 自定义PropertyEditor

  1. 继承 PropertyEditorSupport 接口。
  2. 分别实现 setAsText(String) 、 getAsText() 方法。
  3. 通过创建自定义 PropertyEditorRegistrar 注册自定义属性编辑器

    5.2.1 继承 PropertyEditor 并实现 setAsText(String) 、 getAsText() 方法。

    ```java /**

    • 该自定义编辑器可以将指定时间字符串 yyyy==MM==dd HH时mm分ss秒 转换为LocalDateTime对象。
    • 也可以通过 getAsText 方法返回特定格式的字符串 */ class MyDatePropsEditor extends PropertyEditorSupport { private String pattern;

      public MyDatePropsEditor() { this.pattern = “yyyy==MM==dd HH时mm分ss秒”; }

public MyDatePropsEditor(String pattern) {
    this.pattern = pattern;
}

// 解析字符为自定义类型,通过setValue()方法保存解析后的值
// 父类PropertyEditorSupport持有一个Object引用,用来保存解析后的对象
@Override
public void setAsText(String text) throws IllegalArgumentException {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);

    LocalDateTime localDateTime = LocalDateTime.parse(text, dateTimeFormatter);
    setValue(localDateTime);
}

@Override
public String getAsText() {
    if (getValue() instanceof LocalDateTime) {
        LocalDateTime date = (LocalDateTime) getValue();
        return date.toString();
    }
    return super.getAsText();
}

}

<a name="eyGE2"></a>
### 5.2.2 实现自定义属性注册器

- 实现 `PropertyEditorRegistrar` 完成属性编辑器的注册。
```java
public class CustomTimeStrToLocalTimeResistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        // 1. 只设置类型: 表明这个属性编辑器只针对于这一特定类型
        // registry.registerCustomEditor(LocalDateTime.class,  new CustomTimestrToDatePropertyEditor());

        // 2. 类型+属性名称: 表示这个属性编辑器针对这个类的特定属性
        registry.registerCustomEditor(LocalDateTime.class, "localDateTime", new CustomTimestrToDatePropertyEditor());
    }
}

5.2.3 通过 XML 或 @Bean 注解配置注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.clarencezero.c2_core.custom_property_editor.UserByProperty">
        <property name="localDateTime" value="2020==08==31 20时12分12秒" />
    </bean>

  <!--将自定义属性注册器交给Spring管理-->
    <bean id="customTimeStrToLocalTimeResistrar" class="com.clarencezero.c2_core.custom_property_editor.CustomTimeStrToLocalTimeResistrar" />

    <!--配置CustomEditorConfigurer-->
    <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="propertyEditorRegistrars">
            <list>
                <ref bean="customTimeStrToLocalTimeResistrar"/>
            </list>
        </property>
    </bean>

</beans>
@Configurable
public class CustomPropertyEditoryConfiguration {
    @Bean
    public PropertyEditorRegistrar customTimeStrToLocalTimeResistrar() {
        return new CustomTimeStrToLocalTimeResistrar();
    }

    @Bean
    public CustomEditorConfigurer customEditorConfigurer() {
        CustomEditorConfigurer customEditorConfigurer = new CustomEditorConfigurer();
        PropertyEditorRegistrar[] propertyEditorRegistrars = new PropertyEditorRegistrar[1];
        propertyEditorRegistrars[0] = customTimeStrToLocalTimeResistrar();
        customEditorConfigurer.setPropertyEditorRegistrars(propertyEditorRegistrars);
        return customEditorConfigurer;
    }
}

五、JDK的JavaBean

5.1 什么是JavaBean

  • 所有属性修饰符为 private (使用 getters/setters )
  • 有一个 public 无参构造器
  • 实现 Serializable 接口

    5.2 JavaBeans核心实现— java.beans.BeanInfo

  • 属性 Property — java.beans.PropertyEditor

  • 方法 Method
  • 事件 Event
  • 表达式 Expression

    5.3 标准的 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事件集合描述符 |

5.4 简单实例

private static void doJavaBeansIntrospector() throws Exception{
    BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
        System.out.println("------遍历属性描述符-----");
        System.out.println("属性名称:" + propertyDescriptor.getName());
        System.out.println("get方法:" + propertyDescriptor.getReadMethod());
        System.out.println("属性类型:" + propertyDescriptor.getPropertyType());
        System.out.println("set方法:" + propertyDescriptor.getWriteMethod());
        System.out.println("表示名称:" + propertyDescriptor.getDisplayName());
    }
    System.out.println("--------------------------------------");
    BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
    System.out.println(beanDescriptor.getName());
    System.out.println("--------------------------------------");
    MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
    for (MethodDescriptor methodDescriptor : methodDescriptors) {
        System.out.println("------遍历方法描述符------");
        System.out.println("方法名称:" + methodDescriptor.getName());
        System.out.println("方法对象:" + methodDescriptor.getMethod());
        // System.out.println("方法参数描述:" + methodDescriptor.getParameterDescriptors()[0]);
    }
}

5.5 小结

JDK 提供一套内省机制(Introspector)访问JavaBean ,就像 X光 照射,窥探里面的 属性 、 方法 等。 JDK 也提供了 JavaBean 事件机制。与反射区别:

  • 反射 可以对任意类进行操作,而 内省 只能操作 JavaBean 类型。
  • 内省 需要类提供 setter方法,才能完成属性赋值,而 反射 直接操作属性Field。
  • 反射 出来的数据是客观事件,有就有,没有就没有。而 内省 更像主观判断,如果存在 get/set 方法,就认为会存在这个属性。比如 getClass 方法。

    六、△BeanWrapper

    6.1 BeanWrapper 概述

    spring-beans 包下非常重要的类是 BeanWrapper 以及其相应的实现类 BeanWrapperImpl 。提供以下能力:

  • 设置/获取 属性值 (单个/批量)

  • 获取属性描述符( PropertyDescriptor )
  • 查询属性以确定 可读/可写 功能
  • 允许将子属性的属性设置为无限深度
  • 支持标准 JDK JavaBeans 相关的 PropertyChangeListeners 和 VetoableChangeListeners ,而不需要在目标类中写相关的代码
  • 提供对设置索引属性的支持

Spring 底层 JavaBean 基础设施中央接口。通常不会直接使用,而是通过 BeanFactory 或 org.springframework.validation.DataBinder 隐式调用。

6.2 BeanWrapper继承体系

BeanWrapper-uml.png
简单了解所 继承/实现 的接口:

6.2.1 PropertyEditorRegistry

拥有自定义属性编辑器 注册 、 查找 能力。
property-editor-registry-interface.png

6.2.2 PropertyAccessor

表达式 说明
name 最简单属性名称
a.b 嵌套属性
a[1] 索引属性第二个元素。属性可能为 数组 、 有序集合
r[‘a’] 指向一个 Map 属性,键为 a
  • 可以访问命名属性的类的通用接口(例如对象的Bean属性或对象中的字段),包含嵌套属性访问。
  • 根据属性名称判断 可读/可写 、获取 属性描述符 、给属性填充对应的值。

    PropertyAccessor.png

    PropertyAccesor 的实现类有两个,分别是:

  • BeanWrapperImpl : 使用 JavaBean 内省 API完成setter 方法获取,然后通过反射注入属性。 Spring 默认使用此实现完成属性写入。

  • DirectFieldAccessor : 直接通过反射来获取字段。不需要Bean提供 gettter/setter 方法。

    6.2.3 ConfigurablePropertyAccessor

  • 可配置属性获取接口,三合一接口

  • 扩展关于 ConversionService(Spring3.0+ 新一套类型转换接口) set/get 方法

上述可知, BeanWrapper 拥有属性编辑器 注册 ,属性填充指定值,管理 ConversionService 类型转换等功能。

7.3 BeanWrapper 定义相关接口

public interface BeanWrapper extends ConfigurablePropertyAccessor {

    // 指定数组和集合自动增长的限制。默认无限制
    void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);

    // 返回数组和集合自动增长的限制。
    int getAutoGrowCollectionLimit();

    // 返回此对象包装的Bean实例,此时对象并未进行赋值的初始化对象
    Object getWrappedInstance();

    // 返回被包装的类类型
    Class<?> getWrappedClass();

    // 获取被包装类的属性描述符(由标准JavaBean内省确定)
    PropertyDescriptor[] getPropertyDescriptors();

    // 获取指定包装类属性的属性描述符
    PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;

}

7.3 BeanWapper 实现类 BeanWrapperImpl

7.3.1 概述

默认的 BeanWrapper 实现应足以应付所有典型的用例,缓存自省结果以提高效率。

7.3.2 BeanWapperImpl 继承体系

BeanWrapperImpl.png

① TypeConverter

类型转换器接口定义,将指定的值转换为指定的类型。相关类型转换请看这里

7.6 简述 Spring 如何通过 BeanWrapper 完成数据绑定与类型转换

  1. 在 ApplicationContext#finishBeanFactoryInitialization 阶段通过依赖查找类型为 ConversionService 、名称为 conversionService 的bean并存入 BeanFactory 中。这是 Spring 3.0+ 新一套类型转换接口。
  2. 经过 BeanFactoryPostProcessor 完成类路径扫描之后,生成对应的 BeanDefinition 。
  3. 循环遍历 BeanDefinitionName 数据,挨个实现化Bean对象。
  4. 通过 BeanWrapperImpl 包装已使用反射完成实例化的对象,此时只是一个空壳对象,还未完成属性注入与Spring生命周期流程。
  5. 调用 AbstractAutowireCapableBeanFactory#populateBean 方法完成属性注入
    1. 如果得到的pvs(一般通过XML配置属性)不为空,则调用 applyPropertyValues 完成数据绑定与类型转换
      1. 调用 convertIfNecessary 方法,这个方法把 数据绑定 与 类型转换 将由代理类 TypeConverterDelegate 代理实现。

        7.5 小结

  • Spring 底层 JavaBeans 基础设施的中心化接口
  • 通常不会直接使用,间接用于 BeanFactory 和 DataBinder
  • 提供标准 JavaBeans 分析和操作,能够单独或批量存储Java Bean的属性(properties)
  • 支持嵌套属性路径
  • 实现类 org.springframework.beans.BeanWrapperImpl

八、总结

  1. 对象属性值使用 PropertyValues 进行包装而不是简单的 Map 对象。这样可以拥有更宽泛的操作空间。
  2. 通过反射实例化的对象被 BeanWrapperImpl 对象包装,这个对象拥有很强大的能力。主要能力包括 类型转换 以及 数据绑定
  3. 类型转换 与 数据绑定 是通过代理类TypeConverterDelegate 完成,而它最终也是通过 Spring 3.0+ 相关的 ConversionService 以及 JDK 的 PropertyEditor 完成相应的动作。
  4. BeanWrapperImpl 是一个纽带。