SpringBoot 使用validation数据校验-超级详细超级多干货 - 第406篇

SpringBoot 使用validation数据校验-超级详细超级多干货 - 第406篇


相关历史文章(阅读本文前,您可能需要先看下之前的系列 )

国内最全的Spring Boot系列之四

享元模式:共享女友 - 第355篇

SpringBoot有没有学明白,就看你这些面试题是否懂?- 第389篇

Spring Boot CLI你知多少?- 第394篇

创建一个 Spring Boot 项目的4种方法,你会几种?- 第396篇

Spring Boot 项目中的 parent原来还有这么多的讲究 - 第398篇

Spring Boot条件注解 - 第400篇

SpringBoot自定义Condition注解实战 - 第402篇

Spring Boot @ConditionalOnClass上的注解你了解多少-java元注解和注解 - 第404篇

师傅:徒儿,现在对于数据的合理性验证,是怎么处理的呐?


悟纤:Let me see.

师傅:那你好好想想吧。

悟纤:bingo,有了,在前端使用js进行判断之后,数据合理之后在提交到后台


师傅:那要是被攻击了,跳过了前端的判断,那怎么办呢?

悟纤:那就在后端使用if else的方法进行判断呗,如果不合理拦截,不进行处理。

师傅:这个么,你这样是可以满足要求的,但是吧,还有待优化哦~

悟纤:师傅难道有更好的方案?

师傅:那是必须的必,不然怎么做你师傅。

悟纤:那师傅不要藏着掖着了,速速道来。

师傅:那为师准备一下给你好好上一课。看过来,这里有个美女和你一起学习。


今天这节呢,我们主要是来介绍spring-boot-starter-validation,请跟着师傅来看看此玩意是何方神圣。

Spring Boot validation系列:

(1)✅《SpringBoot 使用validation数据校验-超级详细超级多干货》

(2)《SpringBoot 使用validation数据校验之分组校验怎么玩?·分组还有这么多的讲究》

(3)《SpringBoot 使用validation数据校验之自定义校验注解·源码分析+实例》

(4)《SpringBoot 使用validation数据校验之国际化问题怎么搞?》满满的干货

这一节我们先来看看《SpringBoot 使用validation数据校验-超级详细超级多干货》。


一、spring-boot-starter-validation是什么东东?

1.1 spring-boot-starter-validation概述

spring-boot-starter-validation可以用来校验SpringMVC 的入参,也就是可以用来校验参数的合理性。


1.2 spring-boot-starter-validation是什么?

spring-boot-starter-validation是快速使用validation的stateter,实际上是通过Hibernate Validator使用 Java Bean Validation。

我们来验证一下这一段话,点击spring-boot-starter-validation,进入到它的依赖包信息:



到这里我们就可以看到了spring-boot-starter-validation是使用了Hibernate Validator,它并没有自己进行实现。

具体要怎么使用呢?接下来我们通过一个小小的例子来进行说明。


二、spring-boot-starter-validation的小栗子

开发环境说明:

(1)操作系统:Mac OS

(2)Spring Boot 版本:2.5.5

(3)开发工具:IntelliJ IDEA

(4)JDK: 1.8

例子说明:IntelliJ IDEA

(1)创建一个Spring Boot项目;

(2)构建一个接口,此接口提供保存用户信息;

(3)使用validation进行数据的合理性校验;


2.1 准备工作

2.1.1 创建项目

首先使用IntelliJ IDEA创建一个项目spring-boot-validation-demo,引入基本的web依赖即可,版本选择2.5.5。


2.1.2 创建一个实体类

这个实体列就是用于来接收实体类的信息的:

package com.kfit.demo.springbootvalidationdemo.demo;

/**
 * UserInfo实体类
 *
 * @author 悟纤「公众号SpringBoot」
 * @date 2021-10-13
 * @slogan 大道至简 悟在天成
 */
public class UserInfo {
    private int id;
    private String username;
    private String password;
    private String email;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

2.1.3 构建一个API请求访问

编写一个Controller:

package com.kfit.demo.springbootvalidationdemo.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * TODO
 *
 * @author 悟纤「公众号SpringBoot」
 * @date 2021-10-13
 * @slogan 大道至简 悟在天成
 */
@RestController
@RequestMapping("/userInfo")
public class UserInfoController {

    @RequestMapping("/saveUserInfo")
    public UserInfo saveUserInfo(UserInfo userInfo){
        //save userInfo:将userInfo进行保存
        //userInfoService.save(userInfo);
        return userInfo;
    }

}

2.1.4 启动应用进行测试

启动应用,访问如下地址进行测试:

127.0.0.1:8080/userInfo

结果:



这可了得,保存的数据一点都没有校验,啥都没填写,就保存到数据库了,这也不行呢。

对于数据的校验,那么就是我们接下来要进行讲解的,请接着往下看。


2.2 使用validation进行数据验证

2.2.1 引入依赖

要使用validation需要在pom.xml文件如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.2.2 属性上添加校验注解

在UserInfo类上添加校验注解:

@NotBlank(message = "用户名不能为空")
private String username;

@NotBlank(message = "密码不能为空")
@Length(min = 6,max = 20,message = "密码长度在6-20之间")
private String password;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不合理")
private String email;


2.2.3 在controller方法参数上添加校验注解

在Controller的方法的参数前面添加注解@Valid或者@Validated:

@RequestMapping("/saveUserInfo")
public UserInfo saveUserInfo(@Valid UserInfo userInfo){
    //save userInfo:将userInfo进行保存
    //userInfoService.save(userInfo);
    return userInfo;
}


2.2.4 重新启动测试

接下来就可以重新启动进行测试了:

不合理的请求:

127.0.0.1:8080/userInfo


在控制台可以看到:

合理的请求:

127.0.0.1:8080/userInfo

三、validation常用的校验注解

3.1 JSR提供的校验注解

JSR提供的校验注解:

@Null 被注释的元素必须为null

@NotNull 被注释的元素必须不为null

@AssertTrue 被注释的元素必须为true

@AssertFalse 被注释的元素必须为false

@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max=,min=) 被注释的元素的大小必须在指定的范围内

@Digits(integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past 被注释的元素必须是一个过去的日期

@Future 被注释的元素必须是一个将来的日期

@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式


3.2 Hibernate Validator提供的校验注解

HibernateValidator提供的校验注解:

@NotBlank(message=) 验证字符串非null,且trim后长度必须大于0

@Email 被注释的元素必须是电子邮箱地址

@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内

@NotEmpty 被注释的字符串的必须非空

@Range(min=,max=,message=) 被注释的元素必须在合适的范围内


3.3 小栗子

3.3.1 姓名长度必须在1-20之间

@NotNull(message = "姓名不能为空")
@Size(min = 1, max = 20, message = "姓名长度必须在1-20之间")
private String name;


3.3.2 年龄需要在10-150之间

@Min(value = 10, message = "年龄必须大于10")
@Max(value = 150, message = "年龄必须小于150")
private Integer age;

3.3.3 邮箱格式校验

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不合理")
private String email;

3.3.4 自定义邮箱正则

@Email(regexp = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$",message = "邮箱格式不合理")
private String email;

3.3.5 手机号格式校验

@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
@NotBlank(message = "手机号码不能为空")
private String phone;


四、进阶使用

4.1 有无@NotBlank的区别

看如下两段代码:

代码一:

@Length(min = 6,max = 20,message = "密码长度在6-20之间")
private String password;

代码二:

@NotBlank(message = "密码不能为空")
@Length(min = 6,max = 20,message = "密码长度在6-20之间")
private String password;

这两段代码的区别就在于方法上是否有@NotBlank的注解。

结果也是有区别的,当访问如下请求的话,

/userInfo/saveUserInfo?username=wuqian&email=aa@qq.com

代码一是可以正常访问的,代码二是就会提示:密码不能为空。

也就是代码一:入参password是可以不填写的,此时就会进行长度的校验了,可以正常的访问。

代码二:入参是必须要填写的,填写的规则必须满足长度要求。


4.2 异常处理

对于异常信息的处理,可以使用全局异常进行统一处理,具体查看文章:

《5. 全局异常捕捉【从零开始学Spring Boot】》

iteye.com/blog/41288795


4.3 异常独自处理

如果你的异常信息想自己处理,而不想直接抛出,那么就添加一个参数BindingResult,具体使用如下:

@RequestMapping("/saveUserInfo")
public UserInfo saveUserInfo(@Valid UserInfo userInfo, BindingResult bindingResult){
    if(bindingResult.hasErrors()){
        System.out.println("有异常信息");
        for (ObjectError error : bindingResult.getAllErrors()) {
            System.out.println(error.getDefaultMessage());
        }
        return null;
    }
    //save userInfo:将userInfo进行保存
    //userInfoService.save(userInfo);
    return userInfo;
}

此时我们的代码会进入到我们的具体的业务代码里,具体怎么处理就你自己决定了。


4.4 单个参数校验

需要在类上添加@Validated注解,否则不会校验

import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * TODO
 *
 * @author 悟纤「公众号SpringBoot」
 * @date 2021-10-13
 * @slogan 大道至简 悟在天成
 */
@RestController
@RequestMapping("/userInfo")
@Validated
public class UserInfoController {

    @RequestMapping("/test")
    public String test(@NotNull(message = "需要参数name") String name){
        //save userInfo:将userInfo进行保存
        //userInfoService.save(userInfo);
        return name;
    }

}

当参数平铺到方法入参中时,在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解(如@Min等)。

参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解,并在入参上声明约束注解(如@Min等)。


4.5 导入spring-boot-starter-web没有validation校验框架的解决办法

在低版本的Spring Boot,只需要导入spring-boot-starter-web:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

就可以使用validation。在高版本的话,是需要手动导入spring-boot-starter-validation:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

总的来说:如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖:


4.6 校验模式

上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。Hibernate Validator有以下两种验证模式:

(1)普通模式(默认是这个模式)

普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

(2)快速失败返回模式

快速失败返回模式(只要有一个验证失败,则返回)


配置为“快速失败返回模式”:

@Bean
public LocalValidatorFactoryBean getValidatorFactory() {
    LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
    localValidatorFactoryBean.getValidationPropertyMap().put("hibernate.validator.fail_fast", "true");
    return localValidatorFactoryBean;
}


或者:

@Bean
public Validator validator(){
    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .failFast(true)
            //.addProperty( "hibernate.validator.fail_fast", "true" )
            .buildValidatorFactory();
    Validator validator = validatorFactory.getValidator();
    return validator;
}

4.7 嵌套调用/对象级联校验

前面的示例中,DTO类里面的字段都是基本数据类型和String类型。但是实际场景中,有可能某个字段也是一个对象,这种情况先,可以使用嵌套校验。比如,上面保存User信息的时候同时还带有Job信息。需要注意的是,此时DTO类的对应字段必须标记@Valid注解。

一句话说明就是:对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证。


4.8 @Valid和@Validated的区别

@Valid和@ Validated在基本用法上是没什么区别的,

@Valid 和 @Validated 都用来触发一次校验, @Valid 是 JSR 303规范的注解,

@Validated 是Spring 加强版的注解,在一些进阶的用法上就有区别了,具体是什么区别呢?我们下节揭晓!


 我就是我,是颜色不一样的烟火。
 我就是我,是与众不同的小苹果。
à云课堂学院:悟空学院

学院中有Spring Boot相关的课程!!

SpringBoot视频:从零开始学Spring Boot Plus - 网易云课堂

SpringBoot交流平台:t.cn/R3QDhU0

SpringSecurity5.0视频:权限管理spring security - 网易云课堂

ShardingJDBC分库分表:分库分表Sharding-JDBC实战 - 网易云课堂

分布式事务解决方案:分布式事务解决方案「手写代码」 - 网易云课堂

JVM内存模型调优实战:深入理解JVM内存模型/调优实战 - 网易云课堂

Spring入门到精通:Spring零基础从入门到精通 - 网易云课堂

大话设计模式之爱你:大话设计模式之爱你一万年 - 网易云课堂
编辑于 2021-12-13 21:59