一、概述

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

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

    二、DataBinder

    2.1 概述

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

    2.2 DataBinder 体系结构

    DataBinder-spring-web.png
  • DataBinder 实现 PropertyEditorRegistryTypeConverter 接口,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 中包含名称 xPropertyValue ,目标对象 B 不存在 x 属性,当 bind 方法执行时
    • Nothing
  • PropertyValues 中包含名称 x.yPropertyValue ,目标对象 B 存在 x 属性(嵌套 y 属性),当 bind 方法执行时,会发生什么?
  • PropertyValues 中包含名称 xPropertyValue ,目标对象 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

MutablePropertyValuesPropertyValue 默认实现,允许简单地操作属性,并提供构造函数以支持从 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

  • 属性 Propertyjava.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 相关的 PropertyChangeListenersVetoableChangeListeners ,而不需要在目标类中写相关的代码
  • 提供对设置索引属性的支持

Spring 底层 JavaBean 基础设施中央接口。通常不会直接使用,而是通过 BeanFactoryorg.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 基础设施的中心化接口
  • 通常不会直接使用,间接用于 BeanFactoryDataBinder
  • 提供标准 JavaBeans 分析和操作,能够单独或批量存储Java Bean的属性(properties)
  • 支持嵌套属性路径
  • 实现类 org.springframework.beans.BeanWrapperImpl

八、总结

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