SpringValidation

一、概述

​ JSR 303中提出了Bean Validation,表示JavaBean的校验,Hibernate Validation是其具体实现,并对其进行了一些扩展,添加了一些实用的自定义校验注解。

​ Spring中集成了这些内容,你可以在Spring中以原生的手段来使用校验功能,当然Spring也对其进行了一点简单的扩展,以便其更适用于Java web的开发。

在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装,方便在Spring中使用。在Spring中有多种校验的方式。

注: 之前在controller中用if进行参数校验,现在可以换成springValidation框架来进行参数校验(只需要在Controller的方法参数上和实体类上加上对应的注解就行)

1.1、Spring 校验使用场景

Spring 常规校验(Validator):通过实现org.springframework.validation.Validator接口,然后在代码中调用这个类

Spring 数据绑定(DataBinder):按照Bean Validation方式来进行校验,即通过注解的方式。

Spring Web 参数绑定(WebDataBinder):按照Bean Validation方式来进行校验,即通过注解的方式。

Spring WebMVC/WebFlux 处理方法参数校验:基于方法实现校验

下边列出了几种使用场景:

(1)SpringMVC输入参数校验

(2)Spring管理的bean方法执行参数校验

(3)Spring初始化过程验证bean的属性

1.2、JSR 303 Bean Validation

​ JSR 303中提供了诸多实用的校验注解,这里简单罗列:

//校验类型(message="错误提示")
//1、@Null                       校验对象是否为null
//2、@NotNull                    校验对象是否不为null
//3、@NotBlank                   校验字符串去头尾空格后的长度是否大于0或是否为null
//4、@NotEmpty                   校验字符串是否为null或是否为empty
//
//5、@AssertTrue                 校验Boolean是否为true
//6、@AssertFalse                校验Boolean是否为false
//
//7、@UniqueElements             校验数组/集合的元素是否唯一
//8、@Size(min,max)              校验数组/集合/字符串长度是否在范围之内
//9、@Length(min,max)            校验数组/集合/字符串长度是否在范围之内
//10、@Range(min,max)            校验Integer/Short/Long是否在范围之内
//11、@Min(number)               校验Integer/Short/Long是否大于等于value
//12、@Max(number)               校验Integer/Short/Long是否小于等于value
//13、@Positive                  校验Integer/Short/Long是否为正整数
//14、@PositiveOrZero            校验Integer/Short/Long是否为正整数或0
//15、@Negative                  校验Integer/Short/Long是否为负整数
//16、@NogativeOrZero            校验Integer/Short/Long是否为负整数或0
//
//17、@DecimalMin(decimal)       校验Float/Double是否大于等于value
//18、@DecimalMax(decimal)       校验Float/Double是否小于等于value
//19、@Digits(integer,fraction)  校验数字是否符合整数位数精度和小数位数精度
//
//20、@Past(date)                校验Date/Calendar是否在当前时间之前
//21、@PastOrPresent(date)       校验Date/Calendar是否在当前时间之前或当前时间
//22、@Future(date)              校验Date/Calendar是否在当前时间之后
//23、@FutureOrPresent(date)     校验Date/Calendar是否在当前时间之后或当前时间
//
//24、@Email                     校验字符串是否符合电子邮箱格式
//25、@URL(protocol,host,port)   校验字符串是否符合URL地址格式
//26、@CreditCardNumber          校验字符串是否符合信用卡号格式
//
//27、@Pattern(regexp)           校验字符串是否符合正则表达式的规则
//
//除此之外,我们还可以自定义一些数据校验规则

举例:

@size (min=3, max=20,message="用户名长度只能在3-20之间")

@size (min=6, max=20,message="密码长度只能在6-20之间")

@pattern(regexp="[a-za-z0-9._%+-]+@[a-za-z0-9.-]+\\.[a-za-z]{2,4}",message="邮件格式错误")

@Length(min =5, max =20, message ="用户名长度必须位于5到20之间")

@Email(message ="比如输入正确的邮箱")

@NotNull(message ="用户名称不能为空")

@Max(value =100, message ="年龄不能大于100岁")

@Min(value=18,message="必须年满18岁!")

@AssertTrue(message ="bln4 must is true")

@AssertFalse(message ="blnf must is falase")

@DecimalMax(value="100",message="decim最大值是100")

@DecimalMin(value="100",message="decim最小值是100")

@NotNull(message ="身份证不能为空")

@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份证格式错误")

✈ 空值检查

注解

说明

@NotBlank

用于字符串,字符串不能为null 也不能为空字符串

@NotEmpty

字符串同上,对于集合(Map,List,Set)不能为空,必须有元素

@NotNull

不能为 null

@Null

必须为 null

✈ 数值检查

注解

说明

@DecimalMax(value)

被注释的元素必须为数字,其值必须小于等于指定的值

@DecimalMin(value)

被注释的元素必须为数字,其值必须大于等于指定的值

@Digits(integer, fraction)

被注释的元素必须为数字,其值的整数部分精度为 integer,小数部分精度为 fraction

@Positive

被注释的元素必须为正数

@PositiveOrZero

被注释的元素必须为正数或 0

@Max(value)

被注释的元素必须小于等于指定的值

@Min(value)

被注释的元素必须大于等于指定的值

@Negative

被注释的元素必须为负数

@NegativeOrZero

被注释的元素必须为负数或 0

✈ Boolean 检查

注解

说明

@AssertFalse

被注释的元素必须值为 false

@AssertTrue

被注释的元素必须值为 true

✈ 长度检查

注解

说明

@Size(min,max)

被注释的元素长度必须在 min 和 max 之间,可以是 String、Collection、Map、数组

✈ 日期检查

注解

说明

@Future

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

@FutureOrPresent

被注释的元素必须是现在或者将来的日期

@Past

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

@PastOrPresent

被注释的元素必须是现在或者过去的日期

✈ 其他检查

注解

说明

@Email

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

@Pattern(regexp)

被注释的元素必须符合正则表达式

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验-腾讯云开发者社区-腾讯云

1.3、SpringValidation核心API

二、测试案例:
2.1、需要的依赖
<!--  Spring Validation 校验框架 -->
<!-- validator校验相关依赖 -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <!--<version>7.0.5.Final</version>-->
    <version>6.2.5.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.2</version>
</dependency>

或者直接使用springBoot的启动器依赖这些内容:

<!--校验组件-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<!--web组件-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
2.1.实体类:
package com.zyq.validation.pojo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.*;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @NotNull(message = "用户名不能为null") //SpringValidation框架提供的检测注解
    @NotBlank(message="名字不能为空")//SpringValidation框架提供的检测注解
    @Size(min=2,max=5,message = "名字字符过多")
    private String  userName;
    @Min(value = 0, message = "年龄不能小于0") //SpringValidation框架提供的检测注解
    @Max(value = 150,message = "年龄不能大于150") //SpringValidation框架提供的检测注解
    private int age;
    private String pwd;
    @Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号格式错误")
    private String phone;
    @Email(message = "邮箱格式错误") //SpringValidation框架提供的检测注解
    private String email;

    @Past(message = "生日必须早于当前时间") //SpringValidation框架提供的检测注解
    private Date birth;
    @PositiveOrZero(message = "余额必须大于等于0") //SpringValidation框架提供的检测注解
    private Double money;

    public User(String userName, String pwd) {
        this.userName = userName;
        this.pwd = pwd;
    }
}
2.2.TestController
package com.zyq.validation.controller;
import com.zyq.validation.pojo.entity.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @PostMapping(value = "/reg")
    public User test(@Validated User user){//index.html访问出来后提交数据就会报400的异常,提示名字字符过多,年龄超过限制
        return user;
    }
}
//@RequestBody注解解释:
//@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的,所以只能接收post方式数据);
//在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
//注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
//注:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、
//数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时,原SpringMVC接收
//参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value
//里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。
//即:如果参数时放在请求体中,传入后台的话,那么后台要用@RequestBody才能接收到;如果不是放在
//请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或则形参前什么也不写也能接收。
//注:如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通
//过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。
//
//注:如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名/的话,那么就会自动匹配;没有的话,
/请求也能正确发送。
2.3.index.html:
<html xmlns="http://www.w3.org/1999/html">
<body>
<h1>hello word!!!</h1>
<p>this is a html zyq-page</p>
<meta http-equiv="Content-Type" content="application/json; charset=utf-8">
<!--<meta http-equiv="Content-Type" content="text/html; charset=utf-8">-->
<form action="/reg" method="post">
    <label for="un">用户名:</label> <input id="un" name="userName" type="text" value="zy_qqq1"/><br/>
    <label for="age">年龄:</label> <input id="age" name="age" type="text" value="167"/><br/>
    <label for="pwd">密码:</label> <input id="pwd" name="pwd" type="password" value="123456"/><br/>
    <label for="phone">电话:</label> <input id="phone" name="phone" type="text" value="13367904423"/><br/>
    <input type="submit" value="注册">
</form>
</body>
</html>

2.4.用浏览器测试:

测试地址:   localhost:8080

三、给案例添加Junit
3.1..ValidationConfig:
package com.zyq.validation.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

//有些案例没有这个配置类
@Configuration
@ComponentScan("com.zyq.validation")
public class ValidationConfig {
    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
 }
3.2.UserService1.java
package com.zyq.validation.service;
import com.zyq.validation.pojo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindException;
import org.springframework.validation.Validator;
@Service
public class UserService1 {
    @Autowired
    private Validator validator;
    public boolean validateUserByValidator(User user){
        BindException bindException=new BindException(user, user.getUserName());        
        validator.validate(user, bindException);//进行用户属性的验证
        return bindException.hasErrors();
    }
 }
3.3.测试类:
package com.zyq.validation.test;
import com.zyq.validation.configuration.ValidationConfig;
import com.zyq.validation.pojo.entity.User;
import com.zyq.validation.service.UserService1;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@SpringBootTest
public class TestValidation {
    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
        UserService1 myService = context.getBean(UserService1.class);
        User user = new User();
        user.setAge(-1);
        boolean validator = myService.validateUserByValidator(user);
        System.out.println(validator);
    }
    //Exception in thread "main" java.lang.IllegalArgumentException: Object name must not be null
    // at com.zyq.validation.service.UserService.validateUserByValidator(UserService.java:16)
    // at com.zyq.validation.test.testValidation.main(testValidation.java:17)

 }
3.4.jakarta.validator测试

A.UserService2:

package com.zyq.validation.service;
import com.zyq.validation.pojo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.validation.ConstraintViolation;
import java.util.Set;
//import jakarta.validation.Validator;

@Service
public class UserService2 {//javax.validation.Validator这种方式对于初学者不建议了解
    // (建议先了解UserService1中的org.springframework.validation.Validator)
    @Autowired
    //private jakarta.validation.Validator validator;
    private javax.validation.Validator validator;
    public boolean validateUserByJavaXValidator(User user){
        Set<ConstraintViolation<User>> sets=validator.validate(user);
        for(ConstraintViolation c:sets){
            System.err.println("------------------");
            System.err.println(c.getMessage());
            System.err.println("------------------");
        }
        return sets.isEmpty();
    }
}

B.TestValidation

四、用apifox测试:
4.1.TestController

给TestController添加方法

4.2. apifox:

给edge浏览器安装apifox插件之后根据下边方式测试:

测试结果:

4.3.统一异常处理

给项目中添加统一异常处理类:

@ControllerAdvice
@Slf4j
public class GlobalException {
    //用本类GlobalException的catchMethodArgumentNotValidException处理所有controller类的MethodArgumentNotValidException
    //编写完后测试,好像没效果
    //catchMethodArgumentNotValidException
    @ExceptionHandler(MethodArgumentNotValidException.class)//捕获所有controller的MethodArgumentNotValidException异常
    @ResponseBody
    public ResponseEntity<Object> exception(MethodArgumentNotValidException e, HttpServletRequest request) {
        Map<String, String> result = new HashMap<>();
        BindingResult bindingResult = e.getBindingResult();//获取异常结果信息
        //request.getMethod():请求方式,request.getRequestURI() 请求路径 //比如:  post    /reg2
        log.error("请求[ {} ] {} 的参数校验发生错误", request.getMethod(), request.getRequestURL());
        for (ObjectError objectError : bindingResult.getAllErrors()) {
            FieldError fieldError = (FieldError) objectError;//验证失败后,Spring会生成一个FieldError对象,其中包含了错误的详细信息
            //fieldError.getField():获取引发FieldError的被拒绝的key(方法返回的是导致FieldError的那个字段的字段名)
            //fieldError.getRejectedValue():获取引发FieldError的被拒绝的value(方法返回的是导致FieldError的那个字段的值,即那个无法通过验证的值。)
            //fieldError.getDefaultMessage(): 获取到错误提示
            log.error("参数 {} = {} 校验错误:{}", fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage());
            result.put(fieldError.getField(), fieldError.getDefaultMessage());
        }
        // 一般项目都会有自己定义的公共返回实体类,这里直接使用现成的 ResponseEntity 进行返回,同时设置 Http 状态码为 400
        return ResponseEntity.badRequest().body(result);
    }
}

测试结果如下:

五、ajax测试:
5.1.下载jquery脚本

下载jquery脚本文件放到resources/static文件夹中。

用index2.html测试TestController中的test2方法:  test2方法比test1方法多了一个@RequestBody

5.2.编写index2.html:
<!DOCTYPE html>
<html>
<head>    
    <meta http-equiv="Content-Type" content="application/json;charset=utf-8">   <meta charset="UTF-8">
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<h1>hello word!!!</h1>
<p>this is a html zyq-page</p>
<!-- [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public
com.zyq.validation.pojo.entity.User com.zyq.validation.controller.TestController.test2(com.zyq.validation.pojo.entity.User):
[Field error in object 'user' on field 'userName': rejected value [zyq_34324];
codes [Size.user.userName,Size.userName,Size.java.lang.String,Size];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [user.userName,userName]; arguments []; default message [userName],5,2]; default message [名字字符过多(2到5个字符)]] ]-->
<!--application/json不起作用--><!--<form action="/reg2"  id="myForm" method="post" enctype="application/json">-->
<form onsubmit="return false" method="post" id="myForm">
    <label for="un">用户名:</label> <input id="un" name="userName" type="text" value="zyq_34324"/><br/>
    <label for="age">年龄:</label> <input id="age" name="age" type="text" value="16"/><br/>
    <label for="pwd">密码:</label> <input id="pwd" name="pwd" type="password" value="123"/><br/>
    <label for="phone">电话:</label> <input id="phone" name="phone" type="text" value="13367904423"/><br/>
    <input type="submit" id="sub"  value="注册2">
</form>
</body>
<script>
    //定义serializeObject方法,序列化表单
    $.fn.serializeObject = function () {
        var o = {};
        var a = this.serializeArray();
        $.each(a, function () {
            if (o[this.name]) {
                if (!o[this.name].push) {
                    o[this.name] = [o[this.name]];
                }
                o[this.name].push(this.value || '');
            } else {      o[this.name] = this.value || '';   }
        });
        return o;
    }
    //表单序列化:
    //第一种: 序列化提交 $(’#form’).serialize()  //这种方式是将表单数据 格式化为  k=v&k=v&k=v&k=v
    //第二种:引入  jquery.serializeExtend-1.0.1.js     $('#form').getJsonData()

    //JSON.stringify()的作用是将 JavaScript 对象(json对象)转换为 JSON 字符串,
    //而JSON.parse()可以将JSON字符串转为一个JavaScript 对象(json对象)。
    $('#myForm').submit(function(e) {   var temp = $("#myForm").serializeObject();

        e.preventDefault(); // 阻止表单默认提交行为
        $.ajax({
            url: 'http://localhost:8080/reg2', // 假设表单有action属性
            type: 'post',
            contentType: 'application/json',
            dataType: 'json',
            data: JSON.stringify(temp) ,
            //$(this).serialize(): 是一些k-v字符串(serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。)
            //JSON.stringify( $(this).serialize() )
            success: function(response) {
                // 成功回调函数
                console.log('Form submitted:', response);
                //alert("成功"+response.userName);
                alert("成功"+JSON.stringify(response));
            },
            error: function(xhr, status, error) {
                // 失败回调函数
                console.error('Submission failed:', status, error);
            }
        });
    });
</script>
</html>
5.3.编写TestController:
package com.zyq.validation.controller;
import com.zyq.validation.pojo.entity.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
    @PostMapping(value = "/reg")
    public User test(@Validated User user){//index.html访问出来后提交数据就会报400的异常,提示名字字符过多,年龄超过限制
        return user;
    }

    //test2方法需要用postman或apifox 或者ajax的方式提交数据来访问(不能用html的form表单的action提交参数)
    //因为html的form表单的action提交方式的Content-type的值是'application/x-www-form-url (而且无法修改<我目前掌握的知识无法修改它>)
    //可以将form表单的数据改用ajax方式提交数据
    @PostMapping(value = "/reg2", produces = "application/json;charset=UTF-8")
    public User test2(@RequestBody @Validated User user){//index.html访问出来后提交数据就会报400的异常,提示名字字符过多,年龄超过限制
        return user;
    }
}
//@RequestBody注解解释:
//@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的,所以只能接收post方式数据);
//在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
//注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
//注:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、//数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时,原SpringMVC接收参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value
//里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。
//即:如果参数时放在请求体中,传入后台的话,那么后台要用@RequestBody才能接收到;如果不是放在//请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或则形参前什么也不写也能接收。
//注:如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通
//过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。
//
//注:如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名
//的话,那么就会自动匹配;没有的话,请求也能正确发送。
5.4.浏览器测试:

执行结果:

六、传递校验:

在一个Customer顾客类中定义一个成员变量 User  user来引用用户类对象,并用@Valid注解进行属性验证。

6.1.Customer顾客类
package com.zyq.validation.pojo.entity;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
public class Customer  implements Serializable {
    @NotBlank(message = "会员卡不能为空")
    private String idCardNo;
    @Valid
    private User user;
}

再比如 某个类中有一个成员变量为:         private  List<User>  users;     此时也可以给这个成员变量上边加@Valid 来校验users中的元素。

6.2.TestController

TestController控制器类中添加如下方法

@PostMapping(value = "/reg3", produces = "application/json;charset=UTF-8")
public Customer test3(@RequestBody @Validated Customer customer){
    return customer;
}//https://app.apifox.com/project/4524520
6.3.apifox进行测试

七、分组检测:
7.1.将User复制为User2

7.2.TestController加方法

7.3.测试:

分组1  group1的测试

分组2  group2的测试

八、自定义校验:
8.1.自定义注解:
package com.zyq.validation.annotation;
import com.zyq.validation.validator.CannotBlankValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;//此注解可用在方法上, 成员变量上, 注解上, 构造方法上, 方法参数上
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
//在运行时生效
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {CannotBlankValidator.class})
public @interface CannotBlank {
    //默认错误信息
    String  message() default "不能包含空格";
    //分组
    Class<?>[] groups() default {};
    //负载
    Class <? extends Payload>[]   payload() default{};

    //指定多个时使用
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        CannotBlank[] value();
    }
}
8.2.自定义校验器:
package com.zyq.validation.validator;
import com.zyq.validation.annotation.CannotBlank;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CannotBlankValidator implements ConstraintValidator<CannotBlank, String> {
    public boolean isValid(String value, ConstraintValidatorContext context) {
        //null时不进行校验
         if(value != null  &&  value.contains(" ")){//如果value包含空格就不能通过验证。
             //获取默认提示信息
             String defaultConstraintMessgeTemplate=context.getDefaultConstraintMessageTemplate();
             System.out.println("default message :"+defaultConstraintMessgeTemplate);
             //禁用默认提示信息
             context.disableDefaultConstraintViolation();
             //设置提示语
             context.buildConstraintViolationWithTemplate("can not contains blank-不能包含空格").addConstraintViolation();
             return false;
         }
         return true;
    }
}
8.3.统一异常处理:

统一异常处理类中将exception方法复制一份儿,然后将异常类型改为BindException

8.4.Student:
package com.zyq.validation.pojo.entity;
import com.zyq.validation.annotation.CannotBlank;
import lombok.Data;
@Data
public class Student {
    @CannotBlank(message = "名字不能包含空格")
    private String stuName;
}
8.5.TestController:

8.6.apifox测试:

控制台显示:

九、让字段逐个校验:
9.1.写配置类:
package com.zyq.validation.configuration;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidatorConfiguration {//本配置类可以让需要校验的字段一个一个校验,而不是全部字段一次性都校验
    @Bean
    public Validator validator(AutowireCapableBeanFactory springBeanFactory){
        ValidatorFactory factory= Validation.byProvider(HibernateValidator.class)
                .configure()//快速失败
                .failFast(true)
                //解决springboot依赖注入的问题
                .constraintValidatorFactory(new SpringConstraintValidatorFactory(springBeanFactory))
                .buildValidatorFactory();
        return factory.getValidator();
    }
}
9.2.配置文件

application.properties

9.3.测试:

localhost:8080/index2.html

十.依赖校验:

一个字段的校验依赖另一个字段,比如姓名:中国姓名:姓如果一个字或两个的话,名字基本是1-4个字之间。  

外国姓名::姓如果超过两字的话,名字基本是3-17个字之间

案例中的校验规则是(User3GroupSequenceProvider中指定):

姓如果一个字或两个的话,采用第一组校验规则(名字基本是1-4个字之间)

姓如果超过两字的话,采用第二组校验规则(名字基本是3-17个字之间)

10.1.删ValidatorConfiguration

10.2.校验分组:
package com.zyq.validation.configuration;
import java.util.ArrayList;
import java.util.List;
import com.zyq.validation.pojo.entity.User3;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
//本类用于:根据lastName判断firstName(firstName为0时lastName是一种校验方式,不为0时是另一种校验方式)
//如果account为空,密码也就不能有数据(用第一组校验规则)
//如果account不为空就判断密码字符个数(用第二组校验规则)
@Slf4j
public class User3GroupSequenceProvider implements DefaultGroupSequenceProvider<User3> {
    public List<Class<?>> getValidationGroups(User3 user3) {
        List<Class<?>>  defaultGroupSequence=new ArrayList<>();
        defaultGroupSequence.add(User3.class);
        if(user3!=null){
            String firstName=user3.getFirstName();
            log.info("账号{}.",firstName);
            if(firstName!=null&&1<=firstName.length() && firstName.length()<=2 )//如果姓的长度在1到3之间,其他字段可以采用分组1进行校验
                defaultGroupSequence.add(User3.group1.class);
            else
                defaultGroupSequence.add(User3.group2.class);
            /*String acc=user3.getAccount();
            log.info("账号{}",acc);
            if(acc==null||acc.equals(""))//如果用户名不存在,其他字段可以采用分组1进行校验
                defaultGroupSequence.add(User3.group1.class);
            else
                defaultGroupSequence.add(User3.group2.class);*/
        }
        return defaultGroupSequence;
    }
}
10.3.编写User3
package com.zyq.validation.pojo.entity;
import com.zyq.validation.configuration.User3GroupSequenceProvider;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.group.GroupSequenceProvider;
import javax.validation.constraints.*;
import java.util.Date;
@GroupSequenceProvider(User3GroupSequenceProvider.class)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User3 {  //根据lastName判断firstName(firstName为0时lastName是一种校验方式,不为0时是另一种校验方式)
    @NotNull(message = "firstName不能为null")
    @Size(min=1,max=15,message = "用户名字符数不符合规范(1到15个字符)") //SpringValidation框架提供的检测注解
    private String  firstName;
    //如果account不为空就判断密码字符个数(用第2组校验规则)
    @Size(min=1,max=3,message = "中国名字字符数不符合规范(1到3个字符)" , groups = {User3.group1.class})     
@Size(min=3,max=17,message = "英文名字字符数不符合规范(3到17个字符)" , groups = {User3.group2.class}) 
    private String  lastName;//名

    public interface  group1{}//如果account为空,密码也就不能有数据(用第一组校验规则)
    public interface  group2{}//如果account不为空就判断密码字符个数(用第2组校验规则)
}
10.4TestController:
@RequestMapping(value = "/reg7", produces = "application/json;charset=UTF-8")
public User3 test7(@RequestBody @Validated User3 user){
    return user;
}//https://app.apifox.com/project/4524520
10.5.apifox测试:

A.英文姓,中文名:

控制台:

B.中文姓,英文名:

控制台信息:

11、自定义校验器2

11.1.注释ValidatorConfiguration:

注释掉ValidatorConfiguration中所有

11.2.编写User1:
package com.zyq.validation.pojo.entity;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User1 {
    @NotNull(message = "用户名不能为null") //SpringValidation框架提供的检测注解
    @NotBlank(message="名字不能为空") //SpringValidation框架提供的检测注解
    @CannotBlank(message = "名字不能包含空格")
    @Size(min=2,max=5,message = "名字字符过多(2到5个字符)") //SpringValidation框架提供的检测注解
    private String  userName;
    private int age;
}
11.3.AgeOutOfRangeException
package com.zyq.validation.exception;
import lombok.Data;
@Data
public class AgeOutOfRangeException extends  RuntimeException{
    private String key;
    private String val;
    public AgeOutOfRangeException(String message) {    super(message);       }
    public AgeOutOfRangeException(String message,String key,String val) {
        super(message);
        this.key=key;
        this.val=val;
    }

    public AgeOutOfRangeException(String message, Throwable cause) {   super(message, cause);     }
    public AgeOutOfRangeException(Throwable cause) {   super(cause);      }
    public AgeOutOfRangeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
11.4.GlobalException

GlobalException统一异常处理类中添加如下方法:

@ExceptionHandler(AgeOutOfRangeException.class)//捕获所有controller的BindException异常
@ResponseBody
public ResponseEntity<Object> exception(AgeOutOfRangeException e, HttpServletRequest request) {
    Map<String, String> result = new HashMap<>();
    //request.getMethod():请求方式,request.getRequestURI() 请求路径 //比如:  post    /reg2
    log.error("请求[ {} ] {} 的参数校验发生错误", request.getMethod(), request.getRequestURL());
    log.error("参数 {} = {} 校验错误:{}", e.getKey(), e.getVal(), e.getMessage());
    result.put(e.getKey(), e.getMessage());
    return ResponseEntity.badRequest().body(result);
}
11.5.AgeBetweenValidator:
package com.zyq.validation.validator;
import com.zyq.validation.pojo.entity.User1;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

public class AgeBetweenValidator implements Validator{
    public boolean supports(Class<?> aClass) {//参数是否是User1的字节码对象
        return User1.class.equals(aClass);//判断参数aClass是否为User1.class
    }

    public void validate(Object objTarget, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors,"age","age can not empty年龄不能为空");
        User1 u1=(User1)objTarget;
        if(0>=u1.getAge()){
            errors.rejectValue("age","age<=0, 年龄不能小于等于0");
        }else if( u1.getAge() >=239){
            errors.rejectValue("age","age>=239, 年龄不能大于等于239");
        }
    }
 }
11.6.TestController

TestController中添加如下 方法

@InitBinder
protected void initBinder(WebDataBinder binder) {
    binder.addValidators(new AgeBetweenValidator());
}

@RequestMapping(value = "/reg1", produces = "application/json;charset=UTF-8")
public User1 test1(@RequestBody @Validated User1 user, BindingResult result){
    // 参数校验
    if (result.hasErrors()) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        fieldErrors.forEach(e -> {
            System.out.println(e.getField() + e.getCode());
        });
        throw new AgeOutOfRangeException("年龄数值超范围异常[1,239]","age",user.getAge()+"");
    }
    return user;
}//https://app.apifox.com/project/4524520

统一异常处理类GlobalException中有两个统一异常处理方法, apifox进行测试如果账号和年龄都不能通过校验的话,则网页中只能接收到年龄的校验结果(接收不到用户名的校验结果), 原因应该就是统一异常处理类的两个方法的问题, 改进方案待以后研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值