@Validated和@Valid的区别?教你使用它完成Controller参数校验(含级联属性校验)以及原理分析【享学Spring】(上)

简介: @Validated和@Valid的区别?教你使用它完成Controller参数校验(含级联属性校验)以及原理分析【享学Spring】(上)

前言


上篇文章 介绍了Spring环境下实现优雅的方法级别的数据校验,并且埋下一个伏笔:它在Spring MVC(Controller层)里怎么应用呢?本文为此继续展开讲解Spring MVC中的数据校验~


可能小伙伴能立马想到:这不一样吗?我们使用Controller就是方法级别的,所以它就是直接应用了方法级别的校验而已嘛~对于此疑问我先不解答,而是顺势再抛出两个问题你自己应该就能想明白了:


  1. 上文有说过,基于方法级别的校验Spring默认是并未开启的,但是为什么你在Spring MVC却可以直接使用@Valid完成校验呢?
  2. 可能有的小伙伴说他用的是SpringBoot可能默认给开启了,其实不然。哪怕你用的传统Spring MVC你会发现也是直接可用的,不信你就试试
  3. 类比一下:Spring MVC的HandlerInterceptor是AOP思想的实现,但你有没有发现即使你没有启动@EnableAspectJAutoProxy的支持,它依旧好使~


若你能想明白我提出的这两个问题,下文就非常不难理解了。当然即使你知道了这两个问题的答案,还是建议你读下去。毕竟:永远相信本文能给你带来意想不到的收获~


使用示例


关于数据校验这一块在Spring MVC中的使用案例,我相信但凡有点经验的Java程序员应该没有不会使用的,并且还不乏熟练的选手。在此之前我简单“采访”过,绝大多数程序员甚至一度认为Spring中的数据校验就是指的在Controller中使用@Validated校验入参JavaBean这一块~


因此下面这个例子,你应该一点都不陌生:


@Getter
@Setter
@ToString
public class Person {
    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;
    @Valid // 让InnerChild的属性也参与校验
    @NotNull
    private InnerChild child;
    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }
}
@RestController
@RequestMapping
public class HelloController {
    @PostMapping("/hello")
    public Object helloPost(@Valid @RequestBody Person person, BindingResult result) {
        System.out.println(result.getErrorCount());
        System.out.println(result.getAllErrors());
        return person;
    }
}


发送post请求:/hello Content-Type=application/json,传入的json串如下:


{
  "name" : "fsx",
  "age" : "-1",
  "child" : {
    "age" : 1
  }
}


控制台有如下打印:

2
[Field error in object 'person' on field 'child.name': rejected value [null]; codes


从打印上看:校验生效(拿着错误消息就可以返回前端展示,或者定位到错误页面都行)。


此例两个小细节务必注意:


@RequestBody注解不能省略,否则传入的json无法完成数据绑定(即使不绑定,校验也是生效的哦)~

若方法入参不写BindingResult result这个参数,请求得到的直接是400错误,因为若有校验失败的服务端会抛出异常org.springframework.web.bind.MethodArgumentNotValidException。若写了,那就调用者自己处理喽~

据我不完全和不成熟的统计,就这个案例就覆盖了小伙伴们实际使用中的90%以上的真实使用场景,使用起来确实非常的简单、优雅、高效~


但是作为一个有丰富经验的程序员的你,虽然你使用了@Valid优雅的完成了数据校验,但回头是你是否还会发现你的代码里还是存在了大量的if else的基础的校验?什么原因?其实根本原因只有一个:很多case使用@Valid并不能覆盖,因为它只能校验JavaBean

我相信你是有这样那样的使用痛点的,本文先从原理层面分析,进而给出你所遇到的痛点问题的参考解决参考方案~


原理分析


Controller提供的使用@Valid便捷校验JavaBean的原理,和Spring方法级别的校验支持的原理是有很大差异的(可类比Spring MVC拦截器和Spring AOP的差异区别~),那么现在就看看这块吧


请不要忽视优雅代码的力量,它会成倍提升你的编码效率、成倍降低后期维护成本,甚至成倍提升你的扩展性和成倍降低你写bug的可能性~

回忆DataBinder/WebDataBinder

若对Spring数据绑定模块不是很熟悉的(有阅读过我之前文章的可忽略),建议先补:


  1. 【小家Spring】聊聊Spring中的数据绑定 — DataBinder本尊(源码分析)
  2. 【小家Spring】聊聊Spring中的数据绑定 — WebDataBinder、ServletRequestDataBinder、WebBindingInitializer…


DataBinder类名叫数据绑定,但它在org.springframework.validation这个包,可见Spring它把数据绑定和数据校验牢牢的放在了一起,并且内部弱化了数据校验的概念以及逻辑(Spring想让调用者无需关心数据校验的细节,全由它来自动完成,减少使用的成本)。


我们知道DataBinder它主要对外提供了bind(PropertyValues pvs)和validate()方法,当然还有处理绑定/校验失败的相关(配置)组件:


public class DataBinder implements PropertyEditorRegistry, TypeConverter {
  ...
  @Nullable
  private AbstractPropertyBindingResult bindingResult; // 它是个BindingResult
  @Nullable
  private MessageCodesResolver messageCodesResolver;
  private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
  // 最重要是它:它是org.springframework.validation.Validator
  // 一个DataBinder 可以持有对个验证器。也就是说对于一个Bean,是可以交给多个验证器去验证的(当然一般都只有一个即可而已~~~)
  private final List<Validator> validators = new ArrayList<>();
  public void bind(PropertyValues pvs) {
    MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));
    doBind(mpvs);
  }
  ...
  public void validate() {
    Object target = getTarget();
    Assert.state(target != null, "No target to validate");
    BindingResult bindingResult = getBindingResult();
    // 拿到所有的验证器  一个个的对此target进行验证~~~
    // Call each validator with the same binding result
    for (Validator validator : getValidators()) {
      validator.validate(target, bindingResult);
    }
  }
}


DataBinder提供了这两个非常独立的原子方法:绑定 + 校验。他俩结合完成了我们的数据绑定+数据校验,完全的和业务无关~

网上有很多文章说DataBinder完成数据绑定后继续校验,这种说法是不准确的呀,因为它并不处理这部分的组合逻辑,它只提供原始能力~


Spring MVC处理入参的时机


Spring MVC处理入参的逻辑是非常复杂的,前面花大篇幅讲了Spring MVC对返回值的处理器:HandlerMethodReturnValueHandler,详见:

【小家Spring】Spring MVC容器的web九大组件之—HandlerAdapter源码详解—一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler


同样的,本文只关注它对@RequestBody这种类型的入参进行讲解~

处理入参的处理器:HandlerMethodArgumentResolver,处理@RequestBody最终使用的实现类是:RequestResponseBodyMethodProcessor,Spring借助此处理器完成一系列的消息转换器、数据绑定、数据校验等工作~

相关文章
|
4天前
|
XML 监控 Java
Spring框架的核心原理与应用实践
Spring框架的核心原理与应用实践
|
1天前
|
Java 开发者 Spring
自动装配在Spring框架中的原理与实现方式
自动装配在Spring框架中的原理与实现方式
|
3天前
|
Java 开发者 Spring
深入理解Spring Boot中的自动配置原理
深入理解Spring Boot中的自动配置原理
|
3天前
|
开发框架 Java 开发者
Spring Boot中的自动装配原理
Spring Boot中的自动装配原理
|
2天前
|
Java 开发者 Spring
自动装配在Spring框架中的原理与实现方式
自动装配在Spring框架中的原理与实现方式
|
3天前
|
XML 监控 Java
Spring框架的核心原理与应用实践
Spring框架的核心原理与应用实践
|
3天前
|
Cloud Native Java 开发者
深入解析Spring Framework的核心设计原理
深入解析Spring Framework的核心设计原理
|
Java Spring 前端开发
【spring Boot】Spring中@Controller和@RestController之间的区别
spring Boot入手的第一天,看到例子中的@RestController ............. 相同点:都是用来表示Spring某个类的是否可以接收HTTP请求 不同点:@Controller标识一个Spring类是Spring MVC controller处理器     @RestController:  a convenience annotation that does nothing more than adding the@Controller and @ResponseBody annotations。
1406 0
|
4天前
|
前端开发 Java 微服务
Spring Boot与微前端架构的集成开发
Spring Boot与微前端架构的集成开发
|
10天前
|
Java
springboot自定义拦截器,校验token
springboot自定义拦截器,校验token
23 6