一、概述:
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) |
被注释的元素必须符合正则表达式 |
https://cloud.tencent.com/developer/article/2207507
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
@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进行测试如果账号和年龄都不能通过校验的话,则网页中只能接收到年龄的校验结果(接收不到用户名的校验结果), 原因应该就是统一异常处理类的两个方法的问题, 改进方案待以后研究。
标签:校验,SpringValidation,org,import,message,validation,public From: https://www.cnblogs.com/zhaoyongqi/p/18214813