分层验证与JavaBean验证
Bean Validation简介
Bean Validation为Java Bean验证定义了相应的元数据模型和API
JCP,JSR简介
JCP(Java Community Process)成立于1998年,是使有兴趣的各方参与定义Java的特征和未来版本的正式过程。
JCP使用JSR(Java 规范请求,Java Specification Requests)作为正式规范文档,描述被提议加入到Java体系中的规范和技术
常用约束注解
空值校验类
- @Null 必须为null
- @NotNull 不能为null,但可以为empty,用在基本类型上
- @NotEmpty 不能为null,而且长度必须大于0,用在CharSequence子类型、Collection、Map、数组
- @NotBlank 不能为null,只能作用在String上,而且调用trim()后,长度必须大于0
范围校验类
- @Min 必须是一个数字,其值必须大于等于指定的最小值
- @Max 必须是一个数字,其值必须小于等于指定的最大值
- @Size 大小必须在指定的范围内,作用字符串、Collection、Map、数组等
- @Length 长度必须在指定的范围内,作用CharSequence子类型
- @Digits 必须是一个数字,其值必须在可接受的范围内
- @Future 必须是一个将来的日期
- @Past 不能是一个将来的日期
- @Negative 是不是负数,0不能通过验证
其他校验类
- @Email 必须是电子邮件地址
- @URL 必须为有效的 URL
- @AssertTrue 必须为true
- @Pattern 必须符合指定的正则表达式
依赖
JSR 380是用于bean验证的Java API的规范,是JavaEE和JavaSE的一部分。
根据JSR 380规范,validation-api依赖包含标准验证API:
<!--验证API-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
Hibernate Validator是验证API的参考实现。
要使用它,我们必须添加以下依赖项:
<!--验证API参考实现-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.2.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.1.2.Final</version>
</dependency>
JSR 380提供对变量插值的支持,允许在违规消息中使用表达式。
要解析这些表达式,我们必须在表达式语言API和该API的实现上添加依赖项。GlassFish提供参考实现:
<!--表达式语言依赖关系-->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b06</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b06</version>
</dependency>
中级验证
级联验证
- @Valid 指定递归验证关联的对象;如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证
/**
* 用户好友列表
* @Valid验证集合中的对象
*/
@NotEmpty(message = "好友不能为空")
@Size(min = 1,message = "好友不能少于一个")
private List<@Valid UserInfo> friends;
分组验证
定义接口区分不同场景的分组,然后在在注解验证的groups上指定场景的接口,在需要使用验证的地方指定分组 。
/**
* 分组验证
* 定义两个接口分别对登录和注册分组
*/
public interface LoginGroup{}
public interface RegisterGroup{}
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空",groups = RegisterGroup.class)
private String userId;
/**
* 用户名字
*/
@NotEmpty(message = "用户名称不能为空",groups = LoginGroup.class)
private String userName;
/**
* 用户密码
*/
@NotBlank(message = "用户密码不能为空" ,groups = {LoginGroup.class,RegisterGroup.class})
@Length(min = 6,max = 20,message = "密码长度不能小于6位,不能大于20位")
private String passWord;
组序列
多个分组指定分组的验证顺序,使用@GroupSequence注解
/**
* 组序列场景
*/
@GroupSequence({
LoginGroup.class,
RegisterGroup.class,
Default.class
})
public interface Group{}
实例
/**
* @author fangliu
* @date 2020-02-22
* @description 待验证的Bean对象
*/
@Data
public class UserInfo {
/**
* 分组验证
* 定义两个接口分别对登录和注册分组
*/
public interface LoginGroup{}
public interface RegisterGroup{}
/**
* 组序列场景
* 多个分组指定分组的验证顺序
*/
@GroupSequence({
LoginGroup.class,
RegisterGroup.class,
Default.class
})
public interface Group{}
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空",groups = RegisterGroup.class)
private String userId;
/**
* 用户名字
*/
@NotEmpty(message = "用户名称不能为空",groups = LoginGroup.class)
private String userName;
/**
* 用户密码
*/
@NotBlank(message = "用户密码不能为空" ,groups = {LoginGroup.class,RegisterGroup.class})
@Length(min = 6,max = 20,message = "密码长度不能小于6位,不能大于20位")
private String passWord;
/**
* 用户邮箱
*/
@NotNull(message = "邮箱不能为空",groups = RegisterGroup.class)
@Email(message = "邮箱必须为有效邮箱")
private String email;
/**
* 用户手机号
*/
private String phone;
/**
* 用户年龄
*/
@NotNull(message = "年龄不能为空")
@Min(value = 18,message = "年龄不能小于18岁")
@Max(value = 60,message = "年龄不能大于60岁")
private Integer age;
/**
* 用户生日
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Past(message = "不能是一个将来的时间")
private Date birthday;
/**
* 用户好友列表
* @Valid验证集合中的对象
*/
@NotEmpty(message = "好友不能为空")
@Size(min = 1,message = "好友不能少于一个")
private List<@Valid UserInfo> friends;
}
/**
* @author fangliu
* @date 2020-02-22
* @description validation验证的测试类
*/
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();
//userInfo.setUserId("1111");
userInfo.setUserName("张三");
userInfo.setPassWord("123567");
userInfo.setEmail("6666666@qq.com");
userInfo.setAge(18);
/* LocalDateTime time = LocalDateTime.now();
Instant instant = time.atZone(ZoneId.systemDefault()).toInstant();
Date date1 = Date.from(instant);*/
LocalDate localDate = LocalDate.of(2020, 1, 5);
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
Instant instant1 = zonedDateTime.toInstant();
Date date2 = Date.from(instant1);
userInfo.setBirthday(date2);
userInfo.setFriends(new ArrayList(){
{
add(userInfo);
}
});
}
/**
* 验证结果
*/
@After
public void print(){
// 输出错误信息
set.forEach(info->
System.out.println(info.getMessage())
);
}
/**
* 验证结果
*/
@Test
public void userTest(){
// 使用验证器对对象进行验证
set = validator.validate(userInfo);
}
/**
* 分组验证结果
*/
@Test
public void groupTest(){
// 使用验证器对对象进行验证
set = validator.validate(userInfo,UserInfo.RegisterGroup.class);
}
/**
* 分组排序验证结果
*/
@Test
public void groupSequenceTest(){
// 使用验证器对对象进行验证
set = validator.validate(userInfo,UserInfo.Group.class);
}
}
高级校验
校验参数
校验返回值
校验构造方法
实例
/**
* @author fangliu
* @date 2020-02-22
* @description 用户信息服务类
*/
public class UserInfoService {
/**
* UserInfo作为输入参数
* @param userInfo 输入参数
*/
public void setUserInfo(@Valid UserInfo userInfo){
}
/**
* UserInfo作为输出参数
* @return
*/
public @Valid UserInfo getUserInfo(){
return new UserInfo();
}
/**
* 无参数构造函数
*/
public UserInfoService() {
}
/**
* 接受UserInfo作为参数的构造函数
* @param userInfo
*/
public UserInfoService(@Valid UserInfo userInfo) {
}
}
/**
* @author fangliu
* @date 2020-02-22
* @description validation验证的测试类
*/
public class ValidationTest {
//验证器对象
private Validator validator;
//待验证对象
private UserInfo userInfo;
//验证错误的结果集合
private Set<ConstraintViolation<UserInfoService>> set1;
/**
* 初始化操作
*/
@Before
public void init(){
// 初始化验证器
validator = Validation.buildDefaultValidatorFactory().getValidator();
// 初始化验证对象
userInfo = new UserInfo();
//userInfo.setUserId("1111");
userInfo.setUserName("张三");
userInfo.setPassWord("123567");
userInfo.setEmail("6666666@qq.com");
userInfo.setAge(18);
/* LocalDateTime time = LocalDateTime.now();
Instant instant = time.atZone(ZoneId.systemDefault()).toInstant();
Date date1 = Date.from(instant);*/
LocalDate localDate = LocalDate.of(2020, 1, 5);
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
Instant instant1 = zonedDateTime.toInstant();
Date date2 = Date.from(instant1);
userInfo.setBirthday(date2);
userInfo.setFriends(new ArrayList(){
{
add(userInfo);
}
});
}
/**
* 验证结果
*/
@After
public void print(){
set1.forEach(info->
System.out.println(info.getMessage())
);
}
/**
* 对方法输入参数进行校验
* 在实现方法入参处使用@Valid
*/
@Test
public void paramsValidationTest() throws NoSuchMethodException {
// 获取校验执行器
ExecutableValidator executableValidator = validator.forExecutables();
// 待验证对象
UserInfoService service = new UserInfoService();
// 待验证方法
Method method = service.getClass().getMethod("setUserInfo", UserInfo.class);
// 方法输入参数
Object[] paramObjects = {new UserInfo()};
// 对方法的输入进行参数校验
set1 = executableValidator.validateParameters(service, method, paramObjects);
}
/**
* 对方法对返回值进行约束校验
* 在实现方法返回值处使用@Valid
*/
@Test
public void returnValidationTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 获取校验执行器
ExecutableValidator executableValidator = validator.forExecutables();
// 待验证对象
UserInfoService service = new UserInfoService();
// 待验证方法
Method method = service.getClass().getMethod("getUserInfo");
// 调用方法得到返回值
Object returnValue = method.invoke(service);
// 校验方法返回值是否符合约束
set1 = executableValidator.validateReturnValue(service, method, returnValue);
}
/**
* 对构造函数的入参进行校验
* 在构造方法入参处使用@Valid
*/
@Test
public void constructorTest() throws NoSuchMethodException {
// 获取校验执行器
ExecutableValidator executableValidator = validator.forExecutables();
// 待验证方法
Constructor constructor = UserInfoService.class.getConstructor(UserInfo.class);
// 方法输入参数
Object[] paramObjects = {new UserInfo()};
// 校验构造函数的入参是否符合约束
set1 = executableValidator.validateConstructorParameters(constructor,paramObjects);
}
}
自定义手机号约束注解
1. 自定义校验逻辑方法
/**
* @author fangliu
* @date 2020-02-23
* @description 自定义手机号约束注解关联验证器
*/
public class PhoneValidator implements ConstraintValidator<Phone,String> {
/**
* 自定义校验逻辑方法
* @param s
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
// 手机号校验规则:158开始
String check = "158\d{8}";
Pattern regex = Pattern.compile(check);
String phone = Optional.ofNullable(s).orElse("");
Matcher matcher = regex.matcher(phone);
return matcher.matches();
}
}
2. 自定义手机号约束注解
/**
* 自定义手机号约束注解
*/
@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 {};
}
3. 使用自定义注解
/**
* 用户手机号
*/
@Phone(message = "手机号以158开始")
private String phone;