聊聊 Spring 核心特性中的数据绑定 (DataBinder)

简介: Spring 的核心特性包括 IOC 容器、事件、资源管理、国际化、校验、数据绑定、类型转换、EL 表达式、AOP。其他特性可以轻易的在网络上找到很多资料,而数据绑定这个特性即便在 Spring 官网描述却也不太多。这是因为数据绑定主要应用于 Spring 内部,对于用户而言直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。

前面的话


Spring 的核心特性包括 IOC 容器、事件、资源管理、国际化、校验、数据绑定、类型转换、EL 表达式、AOP。其他特性可以轻易的在网络上找到很多资料,而数据绑定这个特性即便在 Spring 官网描述却也不太多。这是因为数据绑定主要应用于 Spring 内部,对于用户而言直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。


理解数据绑定


数据绑定允许将用户输入的内容动态绑定到应用程序中的领域模型中。对于 Spring 而言,用于输入具体的场景主要包括 xml 中 bean 的定义、web 环境下的请求参数。


数据绑定在 Spring 中的核心类是 org.springframework.validation.DataBinder,可以看到这个类位于 validation 包中,数据绑定时也往往伴随着参数校验。先看看如何手动使用这个类。


@Data
public class LoginDTO {
    private String username;
    private String password;
}
public class App {
    public static void main(String[] args) {
        LoginDTO dto = new LoginDTO();
        Map<String, Object> input = new HashMap<>();
        input.put("username", "hkp");
        input.put("password", "123");
        PropertyValues propertyValues = new MutablePropertyValues(input);
        DataBinder dataBinder = new DataBinder(dto, "loginDTO");
        dataBinder.bind(propertyValues);
    // LoginDTO(username=hkp, password=123)
        System.out.println(dto);
    }
}


DataBinder#bind 是 DataBinder 类的核心方法,它接收 PropertyValues 类型的参数,并将参数中的数据设置到对象的属性中。


PropertyValues


PropertyValues 可以简单的理解为 Map,它包含更多的属性信息。接口核心方法定义如下。


public interface PropertyValues extends Iterable<PropertyValue>
  PropertyValue[] getPropertyValues();
  PropertyValue getPropertyValue(String propertyName);
  boolean contains(String propertyName);
  boolean isEmpty();
}


PropertyValues 继承接口 Iterable,可以理解为 PropertyValue 的容器,PropertyValue 表示对象中的某一个属性值,包含的主要字段如下。


public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
  private final String name;
  private final Object value;
  private boolean optional = false;
  // 是否进行过类型转换
  private boolean converted = false;
  // 类型转换后的值
  private Object convertedValue;
}


PropertyValue 包括原始值和转换后的值,也就意味着 Spring 可能会对用于输入的值进行类型转换。


PropertyValues 接口常用实现如下。


MutablePropertyValues:标准的 PropertyValues 实现,除了属性获取,还支持对属性的设置。

ServletConfigPropertyValues:支持 web 环境获取 servlet 上下文配置的 PropertyValues。

ServletRequestParameterPropertyValues:支持 web 环境获取请求参数的 PropertyValues。


DataBinder


DataBinder 是数据绑定的核心类,它内部的属性可以分为两类,一类是数据绑定的配置,另一类用于数据绑定,理解这些属性后我们就能大概知道它内部的机制。


数据绑定配置有关的字段如下。


public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  // 是否忽略未知字段
  private boolean ignoreUnknownFields = true;
  // 是否忽略无效字段,如这些字段的值在目标对象中不可访问(如嵌套路径中的字段对应值为空)
  private boolean ignoreInvalidFields = false;
  // 是否自动增长包含空值的嵌套路径
  private boolean autoGrowNestedPaths = true;
  // 自动增长的数组或集合的大小的限制
  private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;
  // 绑定字段白名单
  private String[] allowedFields;
  // 绑定字段黑名单
  private String[] disallowedFields;
  // 必须绑定的字段
  private String[] requiredFields;
}


数据绑定功能实现的字段如下。


public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  // 需要进行数据绑定的目标对象
  private final Object target;
  // 需要进行数据绑定的目标对象名称
  private final String objectName;
  // 数据绑定结果
  private AbstractPropertyBindingResult bindingResult;
  // 类型转换
  private SimpleTypeConverter typeConverter;
  // 类型转换服务
  private ConversionService conversionService;
  // 将错误码转换为消息编码的解析器
  private MessageCodesResolver messageCodesResolver;
  // 错误处理器,用于处理字段缺失和进行异常转换
  private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
  // 参数校验器
  private final List<Validator> validators = new ArrayList<>();
}


数据绑定功能实现的字段又分为几大类:数据绑定的目标对象、负责类型转换的转换器、负责参数校验的校验器、保存参数校验结果的 BindingResult。


DataBinder 进行数据绑定时,需要将属性值转换为目标对象属性的类型,还会把校验结果存至 BindingResult。由于 DataBinder 实现了接口 PropertyEditorRegistry、TypeConverter,还会将实现委托到内部的 typeConverter。


至于校验器则用于额外的校验方法使用,在上篇《Spring 参数校验最佳实践及原理解析》有进行分析 web 请求参数的校验会调用 DataBinder 的校验方法,感兴趣可以自行阅读。


也就是说 DataBinder 兼具对象属性设置、类型转换、校验的能力。#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());
      }
    }
  }


属性设置直接调用 PropertyAccessor 设置属性的方法,遇到异常后则使用错误处理器处理。那么 PropertyAccessor 是从哪里来的呢?继续跟踪代码。


  protected ConfigurablePropertyAccessor getPropertyAccessor() {
    return getInternalBindingResult().getPropertyAccessor();
  }


这里可以看出,PropertyAccessor 是从内部的 BindingResult 获取到的,继续跟踪代码则会发现这里 BindingResult 的实现是 BeanPropertyBindingResult,至于 PropertyAccessor 的实现使用的则是 BeanWrapper。也是说真正进行数据绑定的实现由 BeanWrapper 完成,这里不再具体分析。


Spring XML 配置中属性设置分析


我们已经对 Spring 数据绑定的能力有了一定的了解,那 Spring 是怎么运用这项能力的呢?这里进行一些简单分析,先看 Spring 如何将 XML 中的属性配置设置到 bean 的属性上的。


数据绑定中,属性使用 PropertyValues 表示,Spring 会将 xml 中的属性配置转换为 PropertyValues 并存储至表示 bean 元数据的 BeanDefinition。核心代码如下。


public class BeanDefinitionParserDelegate {
  public void parsePropertyElement(Element ele, BeanDefinition bd) {
    String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
    ... 省略部分代码
    try {
      Object val = parsePropertyValue(ele, bd, propertyName);
      PropertyValue pv = new PropertyValue(propertyName, val);
      bd.getPropertyValues().addPropertyValue(pv);
    } finally {
      this.parseState.pop();
    }
  }
}


有了 BeanDefinition,Spring 就可以根据其内部的元数据实例化 bean,并设置 bean 的属性。核心代码如下。


public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
    implements AutowireCapableBeanFactory {
  protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
    ... 省略部分代码
    try {
      bw.setPropertyValues(new MutablePropertyValues(deepCopy));
    } catch (BeansException ex) {
      throw new BeanCreationException(
          mbd.getResourceDescription(), beanName, "Error setting property values", ex);
    }
  }
}


可以看到,bean 属性的设置和 DataBinder 数据绑定的底层实现一样,也是委托给 BeanWrapper 设置属性值,至于这里的 BeanWrapper,则是 Spring 实例化 bean 之后创建的。


WEB 请求参数数据绑定分析


Web 环境下,Spring 需要把请求中的参数或其他参数绑定到 Controller 方法参数值中,这是由 HandlerMethodArgumentResolver 接口来处理的,它可以根据 Controller 方法参数定义解析出参数值。


对于请求到 Model 参数的绑定,实现类为 ModelAttributeMethodProcessor 有关参数绑定的核心代码如下。


public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
  @Override
  @Nullable
  public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
      ... 省略部分代码
      WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
      if (binder.getTarget() != null) {
        if (!mavContainer.isBindingDisabled(name)) {
          // 数据绑定
          bindRequestParameters(binder, webRequest);
        }
        // 参数校验
        validateIfApplicable(binder, parameter);
        if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
          throw new BindException(binder.getBindingResult());
        }
      }
      if (!parameter.getParameterType().isInstance(attribute)) {
        // 类型转换
        attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
      }
      bindingResult = binder.getBindingResult();
      ... 省略部分代码    
  }
  protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    ((WebRequestDataBinder) binder).bind(request);
  }
}


可以看到,这里直接调用了WebRequestDataBinder#bind方法将 request 转换为方法参数中的属性,此外数据绑定后还进行了参数校验与类型转换。


总结

Spring 数据绑定的核心类是 DataBinder,其底层使用 BeanWrapper 实现对象属性值的设置,对于数据绑定,往往又伴随着数据校验、类型转换。Spring 中的类型转换同样具有多种实现,下篇将进行介绍。


目录
相关文章
|
10月前
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
167 0
|
1月前
|
负载均衡 Java API
Spring Cloud是什么及基本特性都有哪些?
Spring Cloud 是用于构建健壮云应用的框架,包含多个子项目。其核心组件如Eureka(服务注册与发现)、Hystrix(熔断器)、Ribbon(负载均衡)等,帮助开发者快速实现微服务架构。Spring Cloud 提供了服务注册与发现、分布式配置、路由、断路器等功能,简化了微服务开发与管理。本文将重点介绍服务注册与发现及分布式配置两大特性。
|
5月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
93 4
|
5月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
95 0
|
2月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
63 6
|
7月前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
65 1
|
10月前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
98 1
|
4月前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
112 2
|
4月前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
64 0
|
10月前
|
安全 Java 程序员
Spring框架的核心特性是什么?
【4月更文挑战第30天】Spring 的特性
548 0