一、概述:
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 | 被注释的元素必须是现在或者过去的日期 |
✈ 其他检查
注解 | 说明 |
| 被注释的元素必须是电子邮箱地址 |
@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.测试:
十.依赖校验:
一个字段的校验依赖另一个字段,比如姓名:中国姓名:姓如果一个字或两个的话,名字基本是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进行测试如果账号和年龄都不能通过校验的话,则网页中只能接收到年龄的校验结果(接收不到用户名的校验结果), 原因应该就是统一异常处理类的两个方法的问题, 改进方案待以后研究。