Validator框架可以帮助开发人员少写代码,提高开发效率
一、校验框架
spring的validator校验框架遵循了JSR-303验证规范
在默认情况下springboot会引入hibernate validator机制来支持JSR-303验证规范
springboot的validator校验框架有3个特性:
1、JSR-303特性:JSR-303是一项标准,只提供规范不提供实现,规定了一些校验注解,如@Null,@NotNull,@Pattern
2、hibernate validation特性:hibernate validation是对JSR-303规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等
3、spring validation:spring validateion是对hibernate validation的二次封装,在springmvc中添加了自动校验
二、pom文件
springboot本身就包含,不需要额外的依赖包
三、注解
1、@Valid注解:javax提供的(一般用这个)
2、@Validated注解:spring提供的
3、Validation常用注解
1)@Null:限制只能为null
2)@NotNull:限制必须不为null
3)@AssertFalse:限制必须为false
4)@AssertTrue:限制必须为true
5)@DecimalMax(value):限制必须为一个不大于指定值的数字
6)@DecimalMin(value):限制必须为一个不小于指定值的数字
7)@Digits(integer, fraction):限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
8)@Future:限制必须是一个将来的日期
9)@Max(value):限制必须为一个不大于指定值的数字
10)@Min(value):限制必须为一个不小于指定值的数字
11)@Past:限制必须是一个过去的日期
12)@Pattern(value):限制必须符合指定的正则表达式
13)@Size(max, min):限制字符长度必须在min到max之间
14)@Past:限制必须是一个过去的日期
15)@NotEmpty:验证注解的元素值不为null且不为空(字符串长度不为0,集合大小不为0)
16)@NotBlank:验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
17)@Email:验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
4、修改Teacher.java
package com.example.domain;
import javax.validation.constraints.NotEmpty;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("实体对象")
@Data
public class Teacher {
@ApiModelProperty(value = "姓名")
@NotEmpty(message = "用户名不能为空")
@Length(min = 2, max = 12, message = "长度必须位于2到12之间")
private String name;
@ApiModelProperty(value = "年龄")
@NotEmpty(message = "年龄不能为空")
@Range(min = 20, max = 65, message = "年龄范围必须在20到65之间")
private String age;
@ApiModelProperty(value = "起始时间")
private String beginTime;
@ApiModelProperty(value = "结束时间")
private String endTime;
}
5、修改TeacherController.java测试方法
@ApiOperation("测试方法二")
@PostMapping("/update")
public boolean update(@Valid Teacher request) {
//do something
return true;
}
6、测试下
age填100
{
"data": null,
"sign": null,
"repCode": "999999",
"repMsg": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'teacher' on field 'age': rejected value [100]; codes [Range.teacher.age,Range.age,Range.java.lang.String,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [teacher.age,age]; arguments []; default message [age],65,20]; default message [年龄范围必须在20到65之间]"
}
四、自定义注解
因为validator框架支持的注解有限,不可能方方面面都支持,故需要我们自定义注解
1、自定义验证手机号码的注解
建立包com.example.annotation
在包下建立自定义注解Phone.java
package com.example.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Constraint(validatedBy = PhoneValidator.class) //指定注解的实现类
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
//校验失败的提示信息
String message() default "请输入正确的手机号码";
Class<?>[] groups() default {};
Class <? extends Payload>[] payload() default {};
//定义List,为了让Bean的一个属性上可以添加多套规则
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List{
Phone[] value();
}
}
2、在包com.example下建立注解处理类PhoneValidator.java
package com.example.annotation;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<Phone, String>{
private static final Pattern PHONE_PATTERN = Pattern.compile(
"^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$");
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.length() == 0) {
return true;
}
Matcher m = PHONE_PATTERN.matcher(value);
return m.matches();
}
}
3、修改Teacher.java添加手机号字段
package com.example.domain;
import javax.validation.constraints.NotEmpty;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import com.example.annotation.Phone;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("实体对象")
@Data
public class Teacher {
@ApiModelProperty(value = "姓名")
@NotEmpty(message = "用户名不能为空")
@Length(min = 2, max = 12, message = "长度必须位于2到12之间")
private String name;
@ApiModelProperty(value = "年龄")
@NotEmpty(message = "年龄不能为空")
@Range(min = 20, max = 65, message = "年龄范围必须在20到65之间")
private String age;
@ApiModelProperty(value = "起始时间")
private String beginTime;
@ApiModelProperty(value = "结束时间")
private String endTime;
@ApiModelProperty(value = "手机号")
@Phone
private String phone;
}
4、测试下
手机号输错
{
"data": null,
"sign": null,
"repCode": "999999",
"repMsg": "org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object 'teacher' on field 'phone': rejected value [12000000000]; codes [Phone.teacher.phone,Phone.phone,Phone.java.lang.String,Phone]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [teacher.phone,phone]; arguments []; default message [phone]]; default message [请输入正确的手机号码]"
}
五、添加验证异常的全局异常捕获
@Valid注解校验抛的异常是org.springframework.validation.BindException
1、修改GlobalExceptionHandler.java
添加BindException异常的处理
package com.example.utils;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.example.exception.BizException;
import com.example.message.CommonResponse;
@ControllerAdvice(basePackages = "com.example.web")
@ResponseBody
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
public CommonResponse runtimeException(RuntimeException e) {
return CommonResponse.fail("999999", e.getMessage());
}
@ExceptionHandler(Exception.class)
public CommonResponse exception(Exception e) {
return CommonResponse.fail("999999", e.getMessage());
}
@ExceptionHandler(BizException.class)
public CommonResponse bizException(BizException e) {
return CommonResponse.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(BindException.class)
public CommonResponse bindException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
StringBuffer sb = new StringBuffer();
for(FieldError fieldError : bindingResult.getFieldErrors()){
sb.append(fieldError.getDefaultMessage());
}
return CommonResponse.fail("999999", sb.toString());
}
}
2、再次测试,输错手机号
{
"data": null,
"sign": null,
"repCode": "999999",
"repMsg": "请输入正确的手机号码"
}
六、断言
断言是spring提供的一个工具类:org.springframework.util.Assert
可以用来简化,if判断,然后抛异常的场景。使代码更优雅
1、在TeacherController.java添加测试方法
@ApiOperation("测试断言")
@GetMapping("/testAssert")
public void testAssert() {
Teacher teacher = null;
Assert.notNull(teacher, "对象不能为空");
}
2、断言抛的异常是:java.lang.IllegalArgumentException
修改GlobalExceptionHandler.java
添加IllegalArgumentException异常的处理
@ExceptionHandler(IllegalArgumentException.class)
public CommonResponse illegalArgumentException(IllegalArgumentException e) {
return CommonResponse.fail("999999", e.getMessage());
}
3、测试
{
"data": null,
"sign": null,
"repCode": "999999",
"repMsg": "对象不能为空"
}
七、参考资料
https://www.liaoxuefeng.com/wiki/1252599548343744/1265102026065728
注:最新代码上传至https://github.com/csj50/myboot