原来工作几年了,只用了数据校验的皮毛~

不知不觉 Spring Boot专栏文章已经写到第十四章了,无论写的好与不好,作者都在尽力写的详细,写的与其它的文章不同,每一章都不是浅尝辄止。如果前面的文章没有看过的朋友,点击这里前往

今天介绍一下 Spring Boot 如何优雅的整合 JSR-303进行参数校验,说到参数校验可能都用过,但是你真的会用吗?网上的教程很多,大多是简单的介绍。

什么是 JSR-303?

JSR-303JAVA EE 6 中的一项子规范,叫做 Bean Validation

Bean ValidationJavaBean 验证定义了相应的 元数据模型API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode , 就可以确保数据模型( JavaBean)的正确性。 constraint 可以附加到字段, getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraintBean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

添加依赖

Spring Boot整合JSR-303只需要添加一个 starter即可,如下:

<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span>    <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag">groupId</span>></span>
<span>   <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-validation<span class="hljs-tag">artifactId</span>></span>
<span><span class="hljs-tag">dependency</span>></span>

内嵌的注解有哪些?

Bean Validation 内嵌的注解很多,基本实际开发中已经够用了,注解如下:

以上是 Bean Validation的内嵌的注解,但是 Hibernate Validator在原有的基础上也内嵌了几个注解,如下。

如何使用?

参数校验分为 简单校验嵌套校验分组校验

简单的校验即是没有嵌套属性,直接在需要的元素上标注约束注解即可。如下:


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleDTO</span> </span>{
<span>
<span>    (message = <span class="hljs-string">"&#x6587;&#x7AE0;id&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span>    (value = <span class="hljs-number">1</span>,message = <span class="hljs-string">"&#x6587;&#x7AE0;ID&#x4E0D;&#x80FD;&#x4E3A;&#x8D1F;&#x6570;"</span>)
<span>    <span class="hljs-keyword">private</span> Integer id;
<span>
<span>    (message = <span class="hljs-string">"&#x6587;&#x7AE0;&#x5185;&#x5BB9;&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span>    <span class="hljs-keyword">private</span> String content;
<span>
<span>    (message = <span class="hljs-string">"&#x4F5C;&#x8005;Id&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span>    <span class="hljs-keyword">private</span> String authorId;
<span>
<span>    (message = <span class="hljs-string">"&#x63D0;&#x4EA4;&#x65F6;&#x95F4;&#x4E0D;&#x80FD;&#x4E3A;&#x8FC7;&#x53BB;&#x65F6;&#x95F4;"</span>)
<span>    <span class="hljs-keyword">private</span> Date submitTime;
<span>}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

同一个属性可以指定多个约束,比如 @NotNull@MAX,其中的 message属性指定了约束条件不满足时的提示信息。

以上约束标记完成之后,要想完成校验,需要在 controller层的接口标注 @Valid注解以及声明一个 BindingResult类型的参数来接收校验的结果。

下面简单的演示下添加文章的接口,如下:

仅仅在属性上添加了约束注解还不行,还需在接口参数上标注 @Valid注解并且声明一个 BindingResult类型的参数来接收校验结果。

举个栗子:上传文章不需要传文章 ID,但是修改文章需要上传文章 ID,并且用的都是同一个 DTO接收参数,此时的约束条件该如何写呢?

此时就需要对这个文章 ID进行分组校验,上传文章接口是一个分组,不需要执行 @NotNull校验,修改文章的接口是一个分组,需要执行 @NotNull的校验。

所有的校验注解都有一个 groups属性用来指定分组, Class<?>[]类型,没有实际意义,因此只需要定义一个或者多个接口用来区分即可。


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleDTO</span> </span>{
</span>

JSR303本身的 @Valid并不支持分组校验,但是Spring在其基础提供了一个注解 @Validated支持分组校验。 @Validated这个注解 value属性指定需要校验的分组。

嵌套校验简单的解释就是一个实体中包含另外一个实体,并且这两个或者多个实体都需要校验。

举个栗子:文章可以有一个或者多个分类,作者在提交文章的时候必须指定文章分类,而分类是单独一个实体,有 &#x5206;&#x7C7B;ID&#x540D;&#x79F0;等等。大致的结构如下:

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleDTO</span></span>{
<span>  ...&#x6587;&#x7AE0;&#x7684;&#x4E00;&#x4E9B;&#x5C5E;&#x6027;.....

<span>
<span>
<span>  <span class="hljs-keyword">private</span> CategoryDTO categoryDTO;
<span>}
</span></span></span></span></span>

此时文章和分类的属性都需要校验,这种就叫做嵌套校验。

嵌套校验很简单,只需要在嵌套的实体属性标注 @Valid注解,则其中的属性也将会得到校验,否则不会校验。

如下 文章分类实体类校验

文章的实体类中有个嵌套的文章分类 CategoryDTO属性,需要使用 @Valid标注才能嵌套校验,如下:


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleDTO</span> </span>{
<span>
<span>    (message = <span class="hljs-string">"&#x6587;&#x7AE0;&#x5185;&#x5BB9;&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span>    <span class="hljs-keyword">private</span> String content;
<span>
<span>    (message = <span class="hljs-string">"&#x4F5C;&#x8005;Id&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span>    <span class="hljs-keyword">private</span> String authorId;
<span>
<span>    (message = <span class="hljs-string">"&#x63D0;&#x4EA4;&#x65F6;&#x95F4;&#x4E0D;&#x80FD;&#x4E3A;&#x8FC7;&#x53BB;&#x65F6;&#x95F4;"</span>)
<span>    <span class="hljs-keyword">private</span> Date submitTime;
</span></span></span></span></span></span></span></span></span></span>

嵌套校验针对 分组查询仍然生效,如果嵌套的实体类(比如 CategoryDTO)中的校验的属性和接口中 @Validated注解指定的分组不同,则不会校验。

JSR-303针对 &#x96C6;&#x5408;的嵌套校验也是可行的,比如 List的嵌套校验,同样需要在属性上标注一个 @Valid注解才会生效,如下:


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ArticleDTO</span> </span>{
</span>

总结:嵌套校验只需要在需要校验的元素(单个或者集合)上添加 @Valid注解,接口层需要使用 @Valid或者 @Validated注解标注入参。

如何接收校验结果?

接收校验的结果的方式很多,不过实际开发中最好选择一个优雅的方式,下面介绍常见的两种方式。

这种方式需要在 Controller层的每个接口方法参数中指定,Validator会将校验的信息自动封装到其中。这也是上面例子中一直用的方式。如下:

(<span class="hljs-string">"/add"</span>)
<span>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">add</span><span class="hljs-params">(@Valid @RequestBody ArticleDTO articleDTO, BindingResult bindingResult)</span></span>{}
</span>

这种方式的弊端很明显,每个接口方法参数都要声明,同时每个方法都要处理校验信息,显然不现实,舍弃。

此种方式还有一个优化的方案:使用 AOP,在 Controller接口方法执行之前处理 BindingResult的消息提示,不过这种方案仍然 不推荐使用

参数在校验失败的时候会抛出的 MethodArgumentNotValidException或者 BindException两种异常,可以在全局的异常处理器中捕捉到这两种异常,将提示信息或者自定义信息返回给客户端。

作者这里就不再详细的贴出其他的异常捕获了,仅仅贴一下参数校验的异常捕获( 仅仅举个例子,具体的返回信息需要自己封装),如下:


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionRsHandler</span> </span>{
<span>
<span>
<span>    <span class="hljs-keyword">private</span> ObjectMapper objectMapper;
</span></span></span></span>

spring-boot-starter-validation做了什么?

这个启动器的自动配置类是 ValidationAutoConfiguration,最重要的代码就是注入了一个 Validator(校验器)的实现类,代码如下:


<span> (BeanDefinition.ROLE_INFRASTRUCTURE)
<span> (Validator<span class="hljs-class">.<span class="hljs-keyword">class</span>)
<span> <span class="hljs-title">public</span> <span class="hljs-title">static</span> <span class="hljs-title">LocalValidatorFactoryBean</span> <span class="hljs-title">defaultValidator</span>() </span>{
<span>  LocalValidatorFactoryBean factoryBean = <span class="hljs-keyword">new</span> LocalValidatorFactoryBean();
<span>  MessageInterpolatorFactory interpolatorFactory = <span class="hljs-keyword">new</span> MessageInterpolatorFactory();
<span>  factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
<span>  <span class="hljs-keyword">return</span> factoryBean;
<span> }
</span></span></span></span></span></span></span></span>

这个有什么用呢? Validator这个接口定义了校验的方法,如下:


<span><t> Set<constraintviolation<t>> validate(T object, Class<?>... groups);
<span>
<span>
<span><t> Set<constraintviolation<t>> validateProperty(T object,
<span>              String propertyName,
<span>              Class<?>... groups);
<span>
<span><t> Set<constraintviolation<t>> validateValue(Class<t> beanType,
<span>              String propertyName,
<span>              Object value,
<span>              Class<?>... groups);
<span>......

</span></span></span></span></t></constraintviolation<t></t></span></span></span></span></constraintviolation<t></t></span></span></span></constraintviolation<t></t></span>

这个 Validator可以用来自定义实现自己的校验逻辑,有些大公司完全不用JSR-303提供的 @Valid注解,而是有一套自己的实现,其实本质就是利用 Validator这个接口的实现。

如何自定义校验?

虽说在日常的开发中内置的约束注解已经够用了,但是仍然有些时候不能满足需求,需要自定义一些校验约束。

举个栗子:有这样一个例子,传入的数字要在列举的值范围中,否则校验失败。

首先需要自定义一个校验注解,如下:


<span>(validatedBy = { EnumValuesConstraintValidator<span class="hljs-class">.<span class="hljs-keyword">class</span>})
<span>@<span class="hljs-title">Target</span>(</span>{ METHOD, FIELD, ANNOTATION_TYPE })
<span>(RUNTIME)
<span>(message = <span class="hljs-string">"&#x4E0D;&#x80FD;&#x4E3A;&#x7A7A;"</span>)
<span><span class="hljs-keyword">public</span>  EnumValues {
</span></span></span></span></span>

根据 Bean Validation API 规范的要求有如下三个属性是必须的:

除了以上三个必须要的属性,添加了一个 values属性用来接收限制的范围。

该校验注解头上标注的如下一行代码:

(validatedBy = { EnumValuesConstraintValidator<span class="hljs-class">.<span class="hljs-keyword">class</span>})
</span>

这个 @Constraint注解指定了通过哪个校验器去校验。

自定义校验注解可以复用内嵌的注解,比如 @EnumValues注解头上标注了一个 @NotNull注解,这样 @EnumValues就兼具了 @NotNull的功能。

@Constraint注解指定了校验器为 EnumValuesConstraintValidator,因此需要自定义一个。

自定义校验器需要实现 ConstraintValidator这个接口,第一个泛型是 &#x6821;&#x9A8C;&#x6CE8;&#x89E3;,第二个是 &#x53C2;&#x6570;&#x7C7B;&#x578B;。代码如下:

如果约束注解需要对其他数据类型进行校验,则可以的自定义对应数据类型的校验器,然后在约束注解头上的 @Constraint注解中指定其他的校验器。

校验注解和校验器自定义成功之后即可使用,如下:


<span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthorDTO</span> </span>{
<span>    (values = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>},message = <span class="hljs-string">"&#x6027;&#x522B;&#x53EA;&#x80FD;&#x4F20;&#x5165;1&#x6216;&#x8005;2"</span>)
<span>    <span class="hljs-keyword">private</span> Integer gender;
<span>}
</span></span></span></span>

数据校验作为客户端和服务端的一道屏障,有着重要的作用,通过这篇文章希望能够对 JSR-303数据校验有着全面的认识。

Original: https://www.cnblogs.com/Chenjiabing/p/13890384.html
Author: 爱撒谎的男孩
Title: 原来工作几年了,只用了数据校验的皮毛~

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/610919/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 设计模式之行为型模式-访问者模式

    访问者模式(Visitor Pattern) 一、 介绍 模式定义:封装一些作用于某种数据结构中的各元素的操作,它可以在 不改变数据结构的前提下定义作用于这些元素的 新的操作。 意…

    Java 2023年6月7日
    086
  • 开源软件SoftEther拆分隧道突破

    最近在寻找比较好用的开源VPN,感觉SoftEther很符合我的需求。一方面是SoftEther属于开源软件并且一直在更新,另一方面是功能强大,好用。 VPN支持路由功能和NAT功…

    Java 2023年6月5日
    0106
  • ActiveMQ 即时通讯服务 浅析

    解决方法: 需要用到eclipse的jdt来编译class,不能再使用javac的默认编译方式。 在eclipse或MyEclipse的eclipse/plugin目录中找到org…

    Java 2023年5月29日
    062
  • Java如何仅将字符串中的数字部分转成数字类型

    需要将字符串中的数字部分转成数字类型,看到网上很多都是利用Integer.parseInt(string)用过循环的方式来判断单个字符能否转换成功再进行拼接转换 其实可以使用Str…

    Java 2023年6月7日
    081
  • java–面向对象基础

    类的定义 面向过程 :是一种以过程为中心的编程思想,实现功能的每一步,都是自己实现的面向对象 :是一种以对象为中心的编程思想,通过指挥对象实现具体的功能 类的理解 类是对现实生活中…

    Java 2023年6月15日
    091
  • 原云生实战

    注:1.此文档来自尚硅谷-雷丰阳老师–原云生实战笔记. 初始连接:https://www.yuque.com/leifengyang/oncloud/vfvmcd 1、…

    Java 2023年6月16日
    085
  • Python工具箱系列(五)

    上一期介绍了Anaconda的安装,本期介绍Miniconda的安装,它们共同的部分是Conda,确实如此。Conda是一个开源的包管理系统,本身的志向非常宏大,要为Python、…

    Java 2023年6月16日
    067
  • 2.微服务’黑话’集锦及Eureka注册中心相关概念

    微服务’黑话’集锦 服务提供者 : 业务实现者,封装业务接口同时提供业务实现逻辑 服务消费者 : 业务调用者,调用服务提供者对外暴露的接口 负载均衡 : 同…

    Java 2023年6月8日
    076
  • rocketmq实现延迟队列(精确到秒级)

    开源版本中,只有RocketMQ支持延迟消息,且只支持18个特定级别的延迟 付费版本中,阿里云和腾讯云上的MQ产品都支持精度为秒级别的延迟消息 定时消息:Producer将消息发送…

    Java 2023年6月5日
    087
  • 怎么关闭电脑系统提示声音

    1、在电脑桌面的空白处,点击鼠标右键,在跳转的选项中点击”个性化”。 2、页面进入”个性化”设置中。 3、点击页面下方的&#8221…

    Java 2023年6月5日
    0128
  • Springboot使用Maven Profile和Spring Profile进行多环境配置

    https://www.jianshu.com/p/b7c75b0c364c Original: https://www.cnblogs.com/tszr/p/16506781.h…

    Java 2023年5月30日
    070
  • 五、redis哨兵两套环境同一局域网容灾切换问题

    上周遇到个灵异事件,实验室有两套环境来搭建redis集群和哨兵,分别是: 第一套环境IP:67(master) 65(salve) 66(salve)第二套环境IP:115(mas…

    Java 2023年6月5日
    083
  • Java中的synchronized关键字

    讨论synchronized 之前先看简单看一些java 中的多线程同步。 当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态…

    Java 2023年5月29日
    081
  • 负载均衡在web系统中的应用

    在日常的架构设计与开发中,常用的负载均衡算法主要分为静态和动态两类。静态负载算法以固定的频率分配任务不考虑服务器的状态信息,如轮询法、随机法等;动态负载均衡算法以服务器的实时负载状…

    Java 2023年6月9日
    075
  • elasticsearch快速安装启动

    准备 docker docker内安装centos容器,模拟服务器环境 centos容器安装 下载centos容器 docker pull centos 启动docker容器 do…

    Java 2023年6月8日
    076
  • 驳”一切不谈考核的管理都是扯淡”

    一、引子 以我个人的从业经验认为,研发人员的量化考核,始终是一个世界难题。正巧不久前在园子里看到了 “一切不谈考核的管理都是扯淡!”一文(下面简称为&#82…

    Java 2023年6月15日
    089
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球