Spring的参数校验会用到两个库:validation-api,hibernate-validator validation-api是一套标准,hibernate-validator实现了此标准 JSR-303 是Java EE 6 中的一项子规范,叫做BeanValidation,官方参考实现是hibernate-validator。 (JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator) 开始使用validator,引入validator的依赖,非web场景(这里开发场景使用web开发场景,所以我们不单独引入该依赖). 主要的校验器有两种 hibernate-validator spring-boot-starter-validation 这两种都是对JSR303的实现,而spring-boot-starter-validation 又是对 hibernate-validator的二次封装。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
常规参数校验 Spring 参数校验作为 spring-context 模块的一部分存在,Validator 是 Spring 参数校验的核心接口,我们先看下这个接口。接口中只存在两个方法,supports 方法用于校验前确认是否支持给定的类型,validate方法用于参数校验,比较奇怪的是校验结果使用参数中的 Errors 存储及获取。
public interface Validator {
// 当前验证器是否支持给定的类型
boolean supports(Class<?> clazz);
// 校验给定的 target,提供的 errors 对象用于存储和获取错误信息
void validate(Object target, Errors errors);
}
@Data
public class LoginDTO {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);
Validator validator = context.getBean(LocalValidatorFactoryBean.class);
LoginDTO loginDTO = new LoginDTO();
Errors errors = new BeanPropertyBindingResult(loginDTO, "login");
validator.validate(loginDTO, errors);
errors.getAllErrors().forEach(System.out::println);
context.close();
}
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
}
常用参数校验
对于简单类型参数(非Bean),直接在参数前,使用注解添加约束规则。注解如下所示:
@AssertTrue / @AssertFalse
验证适用字段:boolean
注解说明:验证值是否为true / false
@DecimalMax / @DecimalMin
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值是否小于或者等于指定的小数值,要注意小数存在精度问题
@Digits
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值的数字构成是否合法
属性说明:integer:指定整数部分的数字的位数。fraction: 指定小数部分的数字的位数。
@Future / @Past
验证适用字段:Date,Calendar
注解说明:验证值是否在当前时间之后 / 之前
属性说明:公共
@Max / @Min
验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long
注解说明:验证值是否小于或者等于指定的整数值
属性说明:公共
注意事项:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单提交的值为“”时无法转换为int
@NotNull / @Null
验证适用字段:引用数据类型
注解说明:验证值是否为非空 / 空
属性说明:公共
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为Null或者是EMPTY.
@NotBlank 与 @NotEmpty 的区别:空格(" ")对于 NotEmpty 是合法的,而 NotBlank 会抛出校验异常
@Pattern
验证适用字段:String
注解说明:验证值是否配备正则表达式
属性说明:regexp:正则表达式flags: 指定Pattern.Flag 的数组,表示正则表达式的相关选项。
@Size
验证适用字段:String,Collection,Map,数组
注解说明:验证值是否满足长度要求
属性说明:max:指定最大长度,min:指定最小长度。
@Length(min=, max=):专门应用于String类型
@Valid
验证适用字段:递归的对关联对象进行校验
注解说明:如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验(是否进行递归验证)
属性说明:无
@Range(min=, max=) 被指定的元素必须在合适的范围内
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@URL(protocol=,host=, port=,regexp=, flags=)
优雅的使用方式
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
/**
* @Author: wangxia
* @Date: 2021/10/20 16:30
*/
public class TestPerson {
@NotEmpty(message = "用户名不能为空")
private String username;
@Min(value = 0,message = "年龄不能小于0岁")
@Max(value =150,message = "年龄不能大于150岁")
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
## 校验
@RequestMapping("/")
@RestController
public class TestValidatController {
@PostMapping("/testValid")
public String testValid(@Validated @RequestBody TestPerson testPerson){
return "测试成功";
}
}
## 捕获异常
@ControllerAdvice
@ResponseBody
public class MethodArgumentNotValidHandel {
@ExceptionHandler(value=MethodArgumentNotValidException.class)
public JSONObject MethodArgumentNotValidHandler(HttpServletRequest request,
MethodArgumentNotValidException exception) throws Exception
{
JSONObject result=new JSONObject();
result.put("code","fail");
JSONObject errorMsg=new JSONObject();
for (FieldError error : exception.getBindingResult().getFieldErrors()) {
errorMsg.put(error.getField(),error.getDefaultMessage());
}
result.put("msg",errorMsg);
return result;
}
}
自定义验证注解
## 注解
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UserNameConstraintValidator.class)
@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface UserName {
String message() default "username is invalid";
Class<?>[] groups() default {};
int min();
Class<? extends Payload>[] payload() default { };
}
## 校验器
public class UserNameConstraintValidator implements ConstraintValidator<UserName, String> {
private Integer min = 0;
@Override
public void initialize(UserName constraintAnnotation) {
min = constraintAnnotation.min();
}
@Override
public boolean isValid(String o, ConstraintValidatorContext constraintValidatorContext) {
return StringUtils.hasText(o) && o.length()>min ;
}
}
spring总的全局异常参数处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理标注了 @Validated 的类的方法调用参数校验失败导致的异常
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("|"));
return Result.fail(message);
}
// 处理表单类型请求普通参数缺失导致的异常
@ExceptionHandler(ServletRequestBindingException.class)
public Result<?> handleServletRequestBindingException(ServletRequestBindingException e) {
String message = e.getMessage();
return Result.fail(message);
}
// 处理表单类型请求的复杂参数校验失败导致的异常
@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException e) {
String message = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining("|"));
return Result.fail(message);
}
// 处理 application/json 类型请求的参数校验失败导致的异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining("|"));
return Result.fail(message);
}
}