@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借助此处理器完成一系列的消息转换器、数据绑定、数据校验等工作~

相关文章
|
6天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
17 0
|
24天前
|
前端开发 Java 测试技术
深入剖析:Spring Boot Controller中请求处理方法的访问修饰符
【10月更文挑战第21天】 在Spring Boot应用中,Controller类中的请求处理方法通常用于处理HTTP请求。这些方法的访问修饰符(private或public)对方法的行为和可访问性有着重要影响。本文将深入探讨在Controller中使用private和public修饰符的区别,以及它们对Spring MVC框架的影响。
25 8
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
1月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
418 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
1月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
72 2
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
168 2
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
42 0
|
1月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
68 0
|
1月前
|
负载均衡 Java 网络架构
Spring Cloud原理详解
介绍了Spring Cloud的原理和核心组件,包括服务注册与发现、配置管理、负载均衡、断路器、智能路由、分布式消息传递、分布式追踪和服务熔断等,旨在帮助开发人员快速构建和管理微服务架构中的分布式系统。
53 0
|
Java Spring
Spring原理学习系列之五:IOC原理之Bean加载
其实很多同学都想通过阅读框架的源码以汲取框架设计思想以及编程营养,Spring框架其实就是个很好的框架源码学习对象。我们都知道Bean是Spring框架的最小操作单元,Spring框架通过对于Bean的统一管理实现其IOC以及AOP等核心的框架功能,那么Spring框架是如何把Bean加载到环境中来进行管理的呢?本文将围绕这个话题进行详细的阐述,并配合Spring框架的源码解析。
Spring原理学习系列之五:IOC原理之Bean加载