Spring Web 嵌套对象校验失效

打印 上一主题 下一主题

主题 970|帖子 970|积分 2910

问题复现



  • 当开发一个学籍管理系统时,我们会提供了一个 API 接口去添加学生的相关信息,学生中有个嵌套属性接洽电话,其对象定义参考下面的代码:
    1. import lombok.Data;
    2. import javax.validation.constraints.Size;
    3. @Data
    4. public class Student {
    5.     @Size(max = 10)
    6.     private String name;
    7.     private short age;
    8.     private Phone phone;
    9. }
    10. @Data
    11. class Phone {
    12.     @Size(max = 10)
    13.     private String number;
    14. }
    复制代码
  • 这里我们也给 Phone 对象做了合法性要求(@Size(max = 10)),当我们利用下面的请求(请求 body 携带一个接洽电话信息超过 10 位),测试校验会发现这个约束并不见效。
  • 定义完对象后,我们再定义一个 Controller 去利用它,利用方法如下:
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.web.bind.annotation.RequestBody;
    3. import org.springframework.web.bind.annotation.RequestMapping;
    4. import org.springframework.web.bind.annotation.RequestMethod;
    5. import org.springframework.web.bind.annotation.RestController;
    6. @RestController
    7. @Slf4j
    8. @Validated
    9. public class StudentController {
    10.     @RequestMapping(path = "students", method = RequestMethod.POST)
    11.     public void addStudent(@Validated @RequestBody Student student){
    12.         log.info("add new student: {}", student.toString());
    13.         //省略业务代码
    14.     };
    15. }
    复制代码
  • 我们提供了一个支持学生信息添加的接口。启动服务后,利用 IDEA 自带的 HTTP Client 工具来发送下面的请求以添加一个学生:
    1. POST http://localhost:8080/students
    2. Content-Type: application/json
    3. {
    4.   "name": "xiaoming",
    5.   "age": 10,
    6.   "phone": {"number":"12306123061230612306"}
    7. }
    复制代码
  • 发现校验器并没有见效。
案例剖析



  • 关于 student 本身的 Phone 类型成员是否校验是在校验过程中(即案例 1 中的代码行 binder.validate(validationHints))决定的。
  • 在校验实行时,首先会根据 Student 的类型定义找出全部的校验点,然后对 Student 对象实例实行校验,这个逻辑过程可以参考代码 ValidatorImpl#validate:
    1. @Override
    2. public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
    3.    //省略部分非关键代码
    4.    Class<T> rootBeanClass = (Class<T>) object.getClass();
    5.    //获取校验对象类型的“信息”(包含“约束”)
    6.    BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass );
    7.    if ( !rootBeanMetaData.hasConstraints() ) {
    8.       return Collections.emptySet();
    9.    }
    10.    //省略部分非关键代码
    11.    //执行校验
    12.    return validateInContext( validationContext, valueContext, validationOrder );
    13. }
    复制代码
  • 这里语句"beanMetaDataManager.getBeanMetaData( rootBeanClass )"根据 Student 类型组装出 BeanMetaData,BeanMetaData 即包罗了必要做的校验(即 Constraint)。
  • 在组装 BeanMetaData 过程中,会根据成员字段是否标记了 @Valid 来决定(记载)这个字段以后是否做级联校验,参考代码 AnnotationMetaDataProvider#getCascadingMetaData:
    1. private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElement annotatedElement,
    2.       Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) {
    3.    return CascadingMetaDataBuilder.annotatedObject( type, annotatedElement.isAnnotationPresent( Valid.class ), containerElementTypesCascadingMetaData,
    4.                getGroupConversions( annotatedElement ) );
    5. }
    复制代码
  • 在上述代码中"annotatedElement.isAnnotationPresent( Valid.class )"决定了 CascadingMetaDataBuilder#cascading 是否为 true。如果是,则在后续做具体校验时,做级联校验,而级联校验的过程与宿主对象(即 Student)的校验过程大要相同,即先根据对象类型获取定义再来做校验。
  • 在当前案例代码中,phone 字段并没有被 @Valid 标记,以是关于这个字段信息的 cascading 属性肯定是 false,因此在校验 Student 时并不会级联校验它。
问题修正



  • 从源码级别了解了嵌套 Validation 失败的原因后,我们会发现,要让嵌套校验见效,解决的方法只有一种,就是加上 @Valid,修正代码如下:
    1. @Valid
    2. private Phone phone;
    复制代码
  • 当修正完问题后,我们会发现校验见效了。而如果此时去调试修正后的案例代码,会看到 phone 字段 MetaData 信息中的 cascading 确实为 true 了,参考下图:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

温锦文欧普厨电及净水器总代理

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表