在项目开发中许多地方需要加以验证,对于使用if-else简单粗暴一个一个验证,spring的validation封装了Javax ValidationI校验参数,大大缩减了代码量。
以前的分层验证,从controller到落入数据库,一层一层验证,代码重复、冗余。
Javax ValidationI使用Java Bean验证通过注解将约束添加到域模型中,将验证逻辑从代码中分离出来。
Javax ValidationI的依赖:
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.16.Final</version> </dependency>
springboot对Javax ValidationI封装,依赖变成
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
一、初级注解
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=) 被注释的元素必须符合指定的正则表达式
Validator提供的验证注解:
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
二、创建验证器
/** * 验证测试类 */ public class ValidationTest { // 验证器对象 private Validator validator; // 待验证对象 private UserInfo userInfo; // 验证结果集合 private Set<ConstraintViolation<UserInfo>> set; /** * 初始化操作 */ @Before public void init() { // 初始化验证器 validator = Validation.buildDefaultValidatorFactory() .getValidator(); // 初始化待验证对象 - 用户信息 userInfo = new UserInfo(); } /** * 结果打印 */ @After public void print() { set.forEach(item -> { // 输出验证错误信息 System.out.println(item.getMessage()); }); } @Test public void nullValidation() { // 使用验证器对对象进行验证 set = validator.validate(userInfo); } }
三、中级约束
中级约束中分为分组约束、组序列
public class UserInfo { // 登录场景 public interface LoginGroup {} // 注册场景 public interface RegisterGroup {} /** * 用户ID */ @NotNull(message = "用户ID不能为空", groups = LoginGroup.class) private String userId; /** * 用户名 * NotEmpty 不会自动去掉前后空格 */ @NotEmpty(message = "用户名称不能为空",groups = RegisterGroup.class) private String userName; /** * 用户密码 * NotBlank 自动去掉字符串前后空格后验证是否为空 */ @NotBlank(message = "用户密码不能为空") @Length(min = 6, max = 20, message = "密码长度不能少于6位,多于20位") private String passWord; }
/** * 分组验证测试方法 */ @Test public void groupValidation() { set = validator.validate(userInfo, UserInfo.RegisterGroup.class, UserInfo.LoginGroup.class); }
组排序:
public class UserInfo { // 登录场景 public interface LoginGroup {} // 注册场景 public interface RegisterGroup {} // 组排序场景 @GroupSequence({ LoginGroup.class, RegisterGroup.class, Default.class }) public interface Group {} /** * 用户ID */ @NotNull(message = "用户ID不能为空", groups = LoginGroup.class) private String userId; /** * 用户名 * NotEmpty 不会自动去掉前后空格 */ @NotEmpty(message = "用户名称不能为空", groups = RegisterGroup .class) private String userName; /** * 用户密码 * NotBlank 自动去掉字符串前后空格后验证是否为空 */ @NotBlank(message = "用户密码不能为空") @Length(min = 6, max = 20, message = "密码长度不能少于6位,多于20位") private String passWord; }
/** * 组序列测试 */ @Test public void groupSequenceValidation() { set = validator.validate(userInfo, UserInfo.Group.class); }
三、高级约束
高级约束是对参数、返回值的约束。
使用注解:
javax:@Valid
spring:@Validated
在检验参数、返回值是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同。
@Validated:提供了一个分组功能,可以在入参、返回值验证时,根据不同的分组采用不同的验证机制。
@Valid:不支持分组功能。
注解地方:
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上。
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上。
/** * 用户信息 */ public class UserInfoService { /** * UserInfo 作为输入参数 * @param userInfo */ public void setUserInfo(@Valid UserInfo userInfo) {} /** * UserInfo 作为输出参数 * @return */ public @Valid UserInfo getUserInfo() { return new UserInfo(); } }
/** * 验证测试类 */ public class ValidationTest { // 验证器对象 private Validator validator; // 待验证对象 private UserInfo userInfo; // 验证结果集合 private Set<ConstraintViolation<UserInfoService>> otherSet; /** * 初始化操作 */ @Before public void init() { // 初始化验证器 validator = Validation.buildDefaultValidatorFactory() .getValidator(); // 初始化待验证对象 - 用户信息 userInfo = new UserInfo(); } /** * 结果打印 */ @After public void print() { set.forEach(item -> { // 输出验证错误信息 System.out.println(item.getMessage()); }); } /** * 对方法输入参数进行约束注解校验 */ @Test public void paramValidation() throws NoSuchMethodException { // 获取校验执行器 ExecutableValidator executableValidator = validator.forExecutables(); // 待验证对象 UserInfoService service = new UserInfoService(); // 待验证方法 Method method = service.getClass() .getMethod("setUserInfo", UserInfo.class); // 方法输入参数 Object[] paramObjects = new Object[]{new UserInfo()}; // 对方法的输入参数进行校验 otherSet = executableValidator.validateParameters( service, method, paramObjects); } /** * 对方法返回值进行约束校验 */ @Test public void returnValueValidation() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // 获取校验执行器 ExecutableValidator executableValidator = validator.forExecutables(); // 构造要验证的方法对象 UserInfoService service = new UserInfoService(); Method method = service.getClass() .getMethod("getUserInfo"); // 调用方法得到返回值 Object returnValue = method.invoke(service); // 校验方法返回值是否符合约束 otherSet = executableValidator.validateReturnValue( service, method, returnValue); } }
在controller中验证入参一般使用@Validated
@RequestMapping(method = RequestMethod.POST) public UserInfo create(@RequestBody @Validated( { RegisterGroup.class }) UserInfo userInfo) { return userService.create(userInfo); } @RequestMapping(method = RequestMethod.GET) public UserInfo getUserById(@NotNull(message = "id不能为空") int userId) { return userService.getUserById(userId); }
四、自定义约束注解
/** * 自定义手机号约束注解 */ @Documented // 注解的作用目标 @Target({ElementType.FIELD}) // 注解的保留策略 @Retention(RetentionPolicy.RUNTIME) // 不同之处:于约束注解关联的验证器 @Constraint(validatedBy = PhoneValidator.class) public @interface Phone { // 约束注解验证时的输出信息 String message() default "手机号校验错误"; // 约束注解在验证时所属的组别 Class<?>[] groups() default {}; // 约束注解的有效负载(严重程度) Class<? extends Payload>[] payload() default {}; }
/** * 自定义手机号约束注解关联验证器 */ public class PhoneValidator implements ConstraintValidator<Phone, String> { /** * 自定义校验逻辑方法 * @param value * @param context * @return */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 手机号验证规则:158后头随便 String check = "158\d{8}"; Pattern regex = Pattern.compile(check); // 空值处理 String phone = Optional.ofNullable(value).orElse(""); Matcher matcher = regex.matcher(phone); return matcher.matches(); } }
自定义注解使用:
public class UserInfo{ /** * 手机号 */ @Phone(message = "手机号不是158后头随便") private String phone; }