校验、数据绑定与类型转换 (Validation, Data Binding, and Type Conversion)
是否将校验视为业务逻辑各有利弊,而 Spring 提供的校验和数据绑定设计并不排斥这两者。具体而言,校验不应与 Web 层捆绑,应当易于本地化,并且应当允许插入任何可用的校验器(Validator)。考虑到这些需求,Spring 提供了一个 Validator 契约,它既基础又在应用程序的每一层都非常实用。
数据绑定对于让用户输入动态绑定到应用程序的领域模型(或你用来处理用户输入的任何对象)非常有用。Spring 提供了名副其实的 DataBinder 来完成这项工作。Validator 和 DataBinder 构成了 validation 包,该包主要用于(但不限于)Web 层。
BeanWrapper 是 Spring 框架中的一个基本概念,在很多地方都有用到。然而,你可能不需要直接使用 BeanWrapper。由于这是参考文档,我们认为有必要进行一些解释。我们在本章中解释 BeanWrapper,因为如果你要使用它,最有可能是在尝试将数据绑定到对象时。
Spring 的 DataBinder 和底层的 BeanWrapper 都使用 PropertyEditorSupport 实现来解析和格式化属性值。PropertyEditor 和 PropertyEditorSupport 类型是 JavaBeans 规范的一部分,本章也将进行说明。Spring 的 core.convert 包提供了一般性的类型转换机制,以及更高级别用于格式化 UI 字段值的 format 包。你可以使用这些包作为 PropertyEditorSupport 实现的更简单替代方案。本章也将对它们进行讨论。
Spring 通过基础设施设置和适配器支持 Java Bean Validation(JSR-303/JSR-380),将其适配到 Spring 自己的 Validator 契约。应用程序可以按照 Java Bean Validation 中所述,全局启用一次 Bean Validation,并将其专门用于所有校验需求。在 Web 层,应用程序可以针对每个 DataBinder 进一步注册控制器局部的 Spring Validator 实例,如 配置 DataBinder 中所述,这对于插入自定义校验逻辑非常有用。
补充教学
1. Spring Validator 与 Bean Validation 的关系
在 Spring 环境下,校验通常分为两种模式:
- Spring
Validator接口:这是 Spring 原生的校验方式,通过编写 Java 代码手动实现逻辑。它完全独立于具体的技术栈,不仅限于 Web。 - Bean Validation (JSR-303/JSR-380):这是 Java 官方标准(如 Hibernate Validator 是其最著名的实现)。它允许你通过注解(如
@NotNull,@Min,@Email)直接在 POJO 的字段上定义规则。 - 整合:在现代开发中,推荐使用 Bean Validation 定义规则,Spring 会自动将其适配为
Validator契约,并在运行时执行校验。
2. DataBinder 的双向任务
DataBinder 在 Spring MVC 中扮演着极其重要的角色,它主要完成两件事:
- 数据类型转换:将 HTTP 请求中的字符串参数(例如
"123","2023-10-01") 转换为 Java 对象类型(如Integer,LocalDate)。 - 数据校验:在转换完成后,立即调用注册的校验器检查对象是否合法。如果校验失败,错误信息会收集到
BindingResult中。
3. BeanWrapper:Spring 操作对象的“万能手”
虽然开发人员很少直接使用它,但 BeanWrapper 是 Spring 处理 Bean 的核心工具。它能够:
- 自省(Introspection):获取对象的属性信息。
- 设置/获取属性:支持层级属性路径(如
person.address.city)。 - 类型处理:在设置属性值时,自动调用注册的
PropertyEditor或ConversionService进行类型转换。
4. 现代转折点:从 PropertyEditor 到 ConversionService
PropertyEditor:源自 JavaBeans 规范,基于状态且非线程安全,主要用于将字符串转换为对象。ConversionService:Spring 3 引入的更现代的类型转换系统。它是线程安全的,支持任意类型之间的转换(不限于字符串),在高性能和高并发环境下表现更佳。
5. Spring Boot 下的校验最佳实践
在 Spring Boot 中,你只需要引入 spring-boot-starter-validation,即可:
- 在 Controller 方法参数上使用
@Valid或@Validated触发校验。 - 在方法所在的类上使用
@Validated获取方法级别的参数校验支持。 - 通过自定义注解实现复杂的跨字段校验逻辑。