JSR 303
在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。
1. javax.validation.constraints中的注解
Constraint | 详细信息 |
---|---|
@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(value) | 被注释的元素必须符合指定的正则表达式 |
@Email | 被注释的元素必须是电子邮箱地址 |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@FutureOrPresent | 被注解的元素必须是将来或者现在的日期 |
@PastOrPresent | 被注解的元素必须是过去或者现在的日期 |
@NegativeOrZero | 被注解的元素必须是负数或者0 |
@PositiveOrZero | 被注解的元素必须是正数或者0 |
2. 自定义注解
2.1 自定义组合注解
我们可以也可以自己定义一个Constraint,例如下面我们可以定义一个 @Price
:
@Max(10000)
@Min(8000)
@Constraint(validatedBy = {})
@Documented
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Price {
String message() default "错误的价格";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
上面@Price
中我们使用了@Max
和@Min
注解来限定价格的区间,被注解的元素范围将被限定在指定区间。
2.2 自定义约束注解
我们也可以自定义Constraint的约束条件,比如我们定义一个 @Status
:
@Constraint(validatedBy = {StatusValidator.class})
@Documented
@Target({ElementType.ANNOTATION_TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
String message() default "不正确的状态,应该是'created','paid','closed'中的一种";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
解析注解:
public class StatusValidator implements ConstraintValidator<Status,String> {
private final String[] ALL_STATUS= {"created","paid","closed"};
@Override
public void initialize(Status constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return Arrays.asList(ALL_STATUS).contains(value);
}
}
注意的是要在@Status
中的@Constraint
注解中指定使用StatusValidator
来验证。
3. 通用的验证工具类
在调用的时候,我们可以调用工具类中的某个方法对对象进行验证:
/**
* 通用验证处理方法 工具类,符合JSR303验证规范,内部采用Hibernate Validator实现
*/
public class BeanValidatorUtils {
/** 存放错误信息的properties数组 */
public static final List<String> VALIDATION_MESSAGES = Arrays.asList("validationMessage");
/** 错误消息分隔符 */
public static final String SEPARATOR = "<br>";
/**
* 使用hibernate的注解来进行验证 .failFast(true)遇到错误立即报错
*/
private static Validator validator = Validation.byProvider(HibernateValidator.class).configure()
.messageInterpolator(
new ResourceBundleMessageInterpolator(new AggregateResourceBundleLocator(VALIDATION_MESSAGES)))
.buildValidatorFactory().getValidator();
/**
* 调用JSR303的validate方法, 验证失败时返回错误信息列表
*
* @param bean
* 要验证的JavaBean
* @param groups
* 验证分组
* @return 错误信息列表
*/
public static <T> List<String> validateListResult(T bean, Class<?>... groups) {
List<String> errorMessages = new ArrayList<>();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(bean, groups);
if (constraintViolations.size() > 0) {
for (ConstraintViolation<T> violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
}
return errorMessages;
}
/**
* 调用JSR303的validate方法, 验证失败时返回错误信息Map
*
* @param bean
* 要验证的JavaBean
* @param groups
* 验证分组
* @return 错误信息列表
*/
public static <T> Map<String, String> validateMapResult(T bean, Class<?>... groups) {
Map<String, String> errorMessages = new HashMap<>();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(bean, groups);
if (constraintViolations.size() > 0) {
for (ConstraintViolation<T> violation : constraintViolations) {
errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
}
}
return errorMessages;
}
/**
* 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.
*
* @param bean
* 要验证的JavaBean
* @param groups
* 验证分组
*
*/
public static void validateWithException(Object bean, Class<?>... groups) throws ConstraintViolationException {
Set<? extends ConstraintViolation<?>> constraintViolations = validator.validate(bean, groups);
if (constraintViolations.size() > 0) {
// 错误信息以异常的 形式抛出可通过e.getConstraintViolations()方法获取具体验证信息
throw new ConstraintViolationException(constraintViolations);
}
}
/**
* 内部转换方法,将错误信息List转换成String
*
* @param errorList
* 将错误信息List
* @return 错误信息
*/
public static String validateListToString(List<String> errorList) {
StringBuffer strBuffer = new StringBuffer();
for (String error : errorList) {
strBuffer.append(error);
strBuffer.append(SEPARATOR);
}
return strBuffer.toString();
}
}
校验可以分组,我们可以指定在某些时候采用什么样的校验规则组合 ValidatorGroup
:
/**
* 通用验证分组,可根据需求增加接口即可
*/
public interface ValidatorGroup {
/**
* 保存验证分组
*/
public static interface Save {}
/**
* 插入验证分组
*/
public static interface Insert {}
/**
* 查询验证分组
*/
public static interface Query {}
/**
* 删除验证分组
*/
public static interface Delete {}
/**
* 更新验证分组
*/
public static interface Update {}
}
定义一个被校验的对象类 Person
:
@Data
@AllArgsConstructor
public class Person {
@NotNull(message = "不能为空",groups = {ValidatorGroup.Save.class,ValidatorGroup.Update.class})
@Length(min = 11)
private String tel;
@Digits(integer = 12,fraction = 20)
private Integer count;
}
这样我们在使用的时候,就可以直接使用工具类来进行校验了:
@Test
public void test1(){
Person person=new Person(null,43,"asdf","");
// 返回校验结果List
List<String> res1 = BeanValidatorUtils.validateListResult(person);
res1.forEach(System.out::println);
//返回校验结果Map
Map<String, String> res2 = BeanValidatorUtils.validateMapResult(person);
res2.forEach((k,v)->{
System.out.println(k+":"+v);
});
//分组进行校验
BeanValidatorUtils.validateWithException(person,ValidatorGroup.Save.class);
}