定制Java中的Validation
Java Validation简介
javax.validation包提供了大量用于验证数据的API工具,可以帮助开发者方便的检验程序的输入输出。最近刚刚接触到这方的知识,还只知道使用其提供的注解工具。
想要使用这个工具,需要导入这个包。以gradle为例:
compile group: 'javax.validation', name: 'validation-api', version: '2.0.0.Final'但这个包仅仅定义了规范的接口、注解等,并没有提供具体实现。常用的实现是由Hibernate提供的:
compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.3.Final'
compile group: 'org.hibernate', name: 'hibernate-validator-annotation-processor', version: '6.0.3.Final'注解使用示例
我们以@NotEmpty注解为例,这是由Hibernate提供的注解,而不是标准规范注解。
假设我们有一个Controller,需要接收一个POST请求,RequestBody为JSON,包含username和password两个字段。
首先定义我们的Body:
@Data
public class LoginCommand {
@NotEmpty(message = "username cannot be empty")
private String username;
@NotEmpty(message = "password cannot be empty")
private String password;
}@Data注解由lombok提供,可以帮助我们创建出一个Java Bean。@NotEmpty注解会在LoginCommand对象创建后验证被注解修饰的field的值,当username为null或""时,会得到false,解析这个注解的Validator就会抛出异常,异常信息的message就是这里设置的值,最终Spring会把这样的异常以400响应返回给客户端。
接下来是我们的Controller:
@RestController
public class UserController {
@PostMapping("login")
public String (@Valid @RequestBody LoginCommand command) {
return "23333";
}
}@Valid注解由javax.validation提供,一般使用在除基本类型和String之外的其他类型的对象上,以允许实现Validator的类能够对该对象进行递归的调用。
这样,当我们请求/login接口的时候,如果username或password为null或"",则会得到400的HTTP响应。
如果希望系统学习,请移步:http://hibernate.org/validator/documentation/
面临需求
现在我们面临一个需求,对于POST的数据,其中一个字段可以为""或者是正常的字符串,而不能是null或" "。面对这样的情况,我们没法在Hibernate提供的库里找到需要的注解。这个时候,为了满足需求,我们需要定制我们的Validator以满足需求。
通过注解实现定制
解决方法来自:https://stackoverflow.com/a/43716689/6487869
最简单的方式,当然是不要写任何实现代码啦。仔细研究javax.validation和Hibernate提供的注解,我们找到了@Null、@NotEmpty和@NotBlank三个注解。发现组合这几个注解可以帮助我们构造出我们需要的注解。
EmptyOrNotBlank = NotBlank | Empty
Empty = !(Null | NotEmpty)结合前面stack overflow的回答,我们能够很容易的利用已有的注解构造出@Empty和@NotBlank这两个注解,并且不需要通过实现接口来完成逻辑代码。
@Empty注解
@ConstraintComposition(CompositionType.ALL_FALSE)
@Null
@NotEmpty
@ReportAsSingleViolation
@Target({FIELD, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
public @interface Empty {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}@EmptyOrNotBlank注解
@ConstraintComposition(CompositionType.OR)
@Empty
@NotBlank
@ReportAsSingleViolation
@Target({ANNOTATION_TYPE, FIELD, METHOD, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = {})
public @interface EmptyOrNotBlank {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}自定义Validator实现定制
另一种实现方式当然就是通过实现接口来做到啦,现在我们不用再去组合已有的注解,而是需要为@Constraint注解的validateBy指定用于验证的类。
于是我们的@EmptyOrNotBlank可以写成:
@ReportAsSingleViolation
@Target({ANNOTATION_TYPE, FIELD, METHOD, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = EmptyOrNotBlankValidator.class)
public @interface EmptyOrNotBlank {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}然后,我们需要编写EmptyOrNotBlankValidator这个类。
public class EmptyOrNotBlankValidator implements ConstraintValidator<EmptyOrNotBlank, String> {
@Override
public void initalize(EmptyOrNotBlank constraintAnnotation) {}
@Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if (object == null) {
return false;
}
if (object.isEmpty()) {
return true;
}
return !object.trim().isEmpty();
}
}完成之后,就能达到和使用组合注解的方法一样的效果了。对于一些复杂的、组合注解难以实现的验证,更加推荐实现ConstraintValidator的方式。