SpringBoot参数校验-Validator
前⾔
在⽇常的开发中,服务端对象的校验是⾮常重要的⼀个环节,⽐如⽤户注册的时候:校验⽤户名,密码,⾝份证,邮箱等信息是否为空,以及格式是否正确,但是这种在⽇常的开发中进⾏校验太繁琐了,代码繁琐⽽且很多。Validator框架应运⽽⽣,它的出现就是为了解决开发⼈员在开发的时候减少代码的,提升开发效率。它专门⽤来做接⼝的参数校验,⽐如:密码长度、是否为空等等。
概述
JSR303 定义了 Bean Validation(校验)的标准 validation-api,但并没有提供实现。Hibernate Validation是对这个规范的实现,并且增加了 @Email、@Length、@Range 等注解。Spring Validation 底层依赖的就是Hibernate Validation。
JSR303:JSR303是⼀项标准,只提供规范不提供实现。定义了校验规范即校验注解如:@Null、@NotNull、@Pattern。位于:straints包下。
hibernate validation:是对 JSR303 规范的实现并且进⾏了增强和扩展。并增加了注解:@Email、@Length、@Range等。
spring Validation:是对Hibernate Validation的⼆次封装。在SpringMvc模块中添加了⾃动校验。并将校验信息封装到特定的类中。
常⽤约束说明
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=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串⾮ null,且长度必须⼤于 0
@Email 被注释的元素必须是电⼦邮箱地址
@Length(min=,max=) 被注释的字符串的⼤⼩必须在指定的范围内
@NotEmpty 被注释的字符串的必须⾮空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
实战开发
@Validated注解⽤法
引⼊依赖
SpringBoot提供了validator启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
⾮SpringBoot项⽬,需要⾃⾏引⼊依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>3.0.3</version>
</dependency>
实体约束⽰例
package ity;
import lombok.Data;
import javax.validation.Valid;
import straints.*;
/**
* created by Luzy on 2021-07-10 19:41
*/
@Data
public class Student {
@NotBlank(message = "⽤户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能⼩于18岁")
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "⼿机号格式错误")
private String phone;
@Email(message = "邮箱格式错误")
private String email;
@Valid
@NotNull
private School school;
@Data
private static class School{
@NotBlank(message = "学校名不能为空")
private String name;
@NotBlank(message = "学校地址不能为空")
private String address;
}
}
校验演⽰
准备Contoller进⾏测试
package ller;
import fig.Update;
import ity.Student;
slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import straints.NotBlank;
/**
* created by Luzy on 2021-07-10 19:41
*/
@RestController
@Slf4j
public class TestController {
}
当Controller层⽅法中参数是对象时(如Student,且对象中字段已经加了约束),若不加@Validated注解,则约束不起效果
@RestController
@Slf4j
public class TestController {
@GetMapping("/t1")
public String test3(Student student) {
log.info("学⽣信息:{}", student);
return "ok";
}
}
2021-07-18 15:20:38.740  INFO 3212 --- [nio-8080-exec-8] ller.TestController        : 学⽣信息:Student(id=, name=, age=null, phone=, email=, school=Student.School(name=, address=))
对象参数前加上@Validated注解后约束⽣效
@RestController
@Slf4j
public class TestController {
@GetMapping("/t2")
public String test1(@Validated Student student) {
log.info("学⽣信息:{}", student);
return "ok";
}
@GetMapping("/t3")
public String test2(@Validated @RequestBody Student student) {
log.info("学⽣信息:{}", student);
return "ok";
}
validation框架}
2021-07-18 15:30:53.440  WARN 3212 --- [io-8080-exec-10] .s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 6 errors
Field error in object 'student' on field 'name': rejected value []; codes [NotBlank.student.name,NotBla
nk.name,NotBlank.java.lang.String,NotBlank]; arguments [t.support.DefaultMessageSourceResolvable: codes [student.name,name];
Field error in object 'student' on field 'school.name': rejected value []; codes [NotBlank.student.school.name,NotBlank.school.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [t.support.DefaultMessageSourceRe Field error in object 'student' on field 'school.address': rejected value []; codes [NotBlank.student.school.address,NotBlank.school.address,NotBlank.address,NotBlank.java.lang.String,NotBlank]; arguments [t.support.DefaultMessageS Field error in object 'student' on field 'age': rejected value [11]; codes [Min.student.age,Min.age,Min.java.lang.Integer,Min]; arguments [t.support.DefaultMessageSourceResolvable: codes [student.age,age]; arguments []; default messa Field error in object 'student' on field 'email': rejected value [1234qq]; codes [ail,Email.java.lang.String,Email]; arguments [t.support.DefaultMessageSourceResolvable: codes [ail,email]; ar Field error in object 'student' on field 'phone': rejected value []; codes [Pattern.student.phone,Pattern.phone,Pattern.java.lang.String,Pattern]; arguments [org.springframe
@RestController
@Slf4j
public class TestController {
@GetMapping("/t3")
public String test2(@Validated @RequestBody Student student) {
log.info("学⽣信息:{}", student);
return "ok";
}
}
2021-07-18 15:32:56.979  WARN 3212 --- [nio-8080-exec-3] .s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String lle
如果想对Controller层⽅法中参数直接进⾏约束,此时必须在类上添加@Validated,否则约束不起效果
@RestController
@Slf4j
public class TestController {
@GetMapping("/t4")
public String test4(@NotBlank(message = "⽤户名不能为空") String name,
@Min(value = 18, message = "年龄不能⼩于18岁") Integer age) {
log.info("学⽣{}的年龄为{}", name,age);
return "ok";
}
}
2021-07-18 15:49:53.751  INFO 21548 --- [nio-8080-exec-7] ller.TestController        : 学⽣null的年龄为16
在类上加上@Validated后约束⽣效
@RestController
@Slf4j
@Validated
public class TestController {
@GetMapping("/t4")
public String test4(@NotBlank(message = "⽤户名不能为空") String name,
@Min(value = 18, message = "年龄不能⼩于18岁") Integer age) {
log.info("学⽣{}的年龄为{}", name,age);
return "ok";
}
}
2021-07-18 15:55:20.738 ERROR 16840 --- [nio-8080-exec-4] C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintV javax.validation.ConstraintViolationException: test4.name: ⽤户名不能为空, test4.age: 年龄不能⼩于18岁
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.8.jar:5.3.8]....
@Validated与@Valid
@Valid注解与@Validated注解功能⼤部分类似;
不同点:
@Valid属于javax包下,⽽@Validated属于Spring下
@Valid⽀持嵌套校验、⽽@Validated不⽀持
@Validated⽀持分组,⽽@Valid不⽀持
⾃定义约束注解
创建⾃定义注解@Phone
package com.lzy.annotation;
import com.lzy.handler.PhoneValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* created by Luzy on 2021-07-18 16:31
*
* @Description: ⾃定义注解:校验⼿机号码格式
*/
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
String message() default "⼿机格式不正确!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
定义具体的验证器
package com.lzy.handler;
import com.lzy.annotation.Phone;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import Matcher;
import Pattern;
/**
* created by Luzy on 2021-07-18 16:32
*
* @Description: ⾃定义注解校验器
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public boolean isValid(String phoneNum, ConstraintValidatorContext constraintValidatorContext) {
/
/ 1: 如果⽤户没输⼊直接返回不校验,因为空的判断交给@NotNull去做就⾏了
if (phoneNum == null && phoneNum.length() == 0) {
return true;
}
Pattern p = Patternpile("^(13[0-9]|14[5|7|9]|15[0|1|2|3|5|6|7|8|9]|17[0|1|6|7|8]|18[0-9])\\d{8}$");
// 2:如果校验通过就返回true,否则返回false;
Matcher matcher = p.matcher(phoneNum);
return matcher.matches();
}
@Override
public void initialize(Phone constraintAnnotation) {
}
}
使⽤及测试
@RestController
@Slf4j
@Validated
public class TestController {
@GetMapping("/phone")
public String phone(@Phone(message = "⼿机号别瞎jb填") String phoneNum) {
log.info("学⽣⼿机号码:{}", phoneNum);
return "ok";
}
}
2021-07-18 17:48:47.890 ERROR 18844 --- [nio-8080-exec-3] C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintV javax.validation.ConstraintViolationException: phone.phoneNum: ⼿机号别瞎jb填
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.3.8.jar:5.3.8]....
多级嵌套模型校验
前⾯已经出现过,Student对象中的school属性也是⼀个对象,如果要想School对象⾥的name,adress上的约束⽣效,则必须要在school上添加 @Valid注解,
且上⽂已说过,@Valid⽀持嵌套校验、⽽@Validated不⽀持,当然此处应该加上⼀个 @NotNull,避免school对象为null
package ity;
import lombok.Data;
import javax.validation.Valid;
import straints.*;
/**
* created by Luzy on 2021-07-10 19:41
*/
@Data
public class Student {
@NotBlank(message = "⽤户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能⼩于18岁")
private Integer age;
@Pattern(regexp = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$", message = "⼿机号格式错误")
private String phone;
@Email(message = "邮箱格式错误")
private String email;
@Valid
@NotNull
private School school;
@Data
private static class School{
@NotBlank(message = "学校名不能为空")
private String name;
@NotBlank(message = "学校地址不能为空")
private String address;
}
}
分组校验
场景说明:⽐如我们在业务开发中对⽤户的新增和修改操作,新增⽤户时肯定没有⽤户id,修改⽤户时肯定要传⼊⽤户id,此时便可以使⽤分组校验
⾃定义两个分组Create和Update
package fig;
import ups.Default;
/**
* created by Luzy on 2021-07-10 19:41
* @Description: ⾃定义分组 Create
*/
public interface Create extends Default {
}
package fig;
import ups.Default;
/**
* created by Luzy on 2021-07-10 19:41
* @Description: ⾃定义分组 Default
*/
public interface Update extends Default {
}
在之前的Student对象模型中id属性上指定group为Update
@Data
public class Student {
@NotBlank(message = "id不能为空",groups ={Update.class} )
private String id;
@NotBlank(message = "⽤户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能⼩于18岁")
private Integer age;
@Phone
private String phone;
@Email(message = "邮箱格式错误")
private String email;
@Valid
@NotNull
private School school;
@Data
private static class School{
@NotBlank(message = "学校名不能为空")
private String name;
@NotBlank(message = "学校地址不能为空")
private String address;
}
}
在Controller启动校验时,指定校验分组
@PostMapping("/createStudent")
public String createStudent(@Validated @RequestBody Student student) {
log.info("学⽣信息:{}", student);
return "createStudent success!";
}
@PostMapping("/updateStudent")
public String updateStudent(@Validated(value = Update.class) @RequestBody Student student) {
log.info("学⽣信息:{}", student);
return "updateStudent success!";
}
2021-07-18 18:15:00.239  INFO 23140 --- [nio-8080-exec-3] ller.TestController        : 学⽣信息:Student(id=, name=xxxxxx@qq, age=18, phone=181********, email=xxxxxx@qq, school=Student.School(name=中科⼤, address=安徽省合肥
2021-07-18 18:15:48.322  WARN 23140 --- [nio-8080-exec-5] .s.DefaultHandlerExceptionRes
olver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String 关于⾃定义分组继承Default说明
继承Default并不是必须的。
如果继承了Default,那么@Validated(value = Create.class)的校验范畴为【Create】和【Default】;
如果没继承Default,那么@Validated(value = Create.class)的校验范畴为【Create】;
Student对象中的name、age、phone等属性默认分组为Default,如果⾃定义分组没有继承Default,那在Contoller的⽅法中指定校验分组时,必须加上Default分组,否则name、age、phone等
属性约束会失效
处理校验抛出的异常
当注解校验不通过时,直接将异常信息返回给前端并不友好,我们可以将异常包装处理后返回给前端。
⽅式⼀:使⽤BindingResult类
@PostMapping("/t6")
public String test6(@Validated @RequestBody Student student,BindingResult bindingResult) {
if (bindingResult.hasErrors()){
List<ObjectError> allErrors = AllErrors();
StringBuilder builder = new StringBuilder();
allErrors.forEach(e->{
<(e.getDefaultMessage());
builder.append("【"+e.getDefaultMessage()+"】");
});
String();
}
log.info("学⽣信息:{}", student);
return "ok";
}
注意:
@Validated(或@Valid) 和 BindingResult 是成对出现的,如果有多个@Validated,那么每个@Validated后⾯都需要添加BindingResult⽤于接收对象中的校验信息
⽅式⼆(推荐):全局异常处理
详情可见系列⽂章《SpringBoot全局异常处理》
Renference

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。