一、场景:
一般我们的参数校验是这样的
概括一下如下图:
通过上图有没有发现,数据校验可谓是贯穿所有的应用程序层,一个 API 接口或方法不简单只有两个输入参数,多则几十个参数,都在进行参数校验,这样既耗时又容易出错。
有没有办法简化这个流程呢?
大家都知道 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException),它也能对参数校验起到一定作用,但是用着用着发现并不太好用,这不是我想要的,还有其他办法吗?
话不多说看图,我想要的是这个样子:
能不能做到呢?请往下看
今天我们就尝试打造一款款轮子,不再纠结参数校验,通过简单的配置就可以完成校验;可以腾出更多时间,去完成业务代码的编写;充分达到验证与业务剥离。
接下来我们的参数校验框架 Hibernate Validator就隆重登场了,那 Hibernate Validator 到底是啥?Hibernate Validator 是 JSR 380 数据校验规范的一种实现。
二、实现
第一步:引入依赖包
compile ‘org.hibernate:hibernate-validator:6.0.17.Final’
compile ‘org.glassfish:javax.el:3.0.1-b09’
校验常用注解如下图:
第二步:定义请求对象,添加校验规则。
public class UserModel1 {
@NotNull(message = "[接口版本号]不能为空!")
@Pattern(regexp = "^(\\d+)(\\.\\d+)?$", message = "[接口版本号]格式错误!")
@Size(min = 0, max = 4, message = "[接口版本号]长度错误,最大长度为[{max}]!")
private String versionNo;
@NotNull(message = "[手机号]不能为空!")
@Size(min = 0, max = 11, message = "[用户手机号码]长度错误,最大长度为[{max}]!")
private String mobile;
@NotNull(message = "[订单号]不能为空!")
@Size(min = 0, max = 32, message = "[订单号]长度错误,最大长度为[{max}]!")
private String orderId;
@NotNull(message = "[验证码]不能为空!")
@Size(min = 0, max = 6, message = "[验证码]长度错误,最大长度为[{max}]!")
private String smsCode;
@Email
private String email;
public String getVersionNo() {
return versionNo;
}
public void setVersionNo(String versionNo) {
this.versionNo = versionNo;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getSmsCode() {
return smsCode;
}
public void setSmsCode(String smsCode) {
this.smsCode = smsCode;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
第三步:直接封装工具类。
public class ValidateUtil {
public static <T> void validate(T t) {
if (null == t) {
throw new ValidateException("001", "参数为空");
}
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
//把对象放到验证器的验证方法中,用Set存储违背约束的对象
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
List<String> errorList = new ArrayList<>();
for (ConstraintViolation<T> constraintViolation : constraintViolations) {
errorList.add(constraintViolation.getMessage());
}
if (!errorList.isEmpty()) {
throw new ValidateException("001", errorList.toString());
}
}
}
第四步:自定义异常,这个根据实际情况而定,非必须
public class ValidateException extends RuntimeException {
private String code;
private String msg;
/**
* @param code 异常码
* @param msg 异常消息
*/
public ValidateException(String code, String msg) {
super(msg);
this.setCode(code);
this.setMsg(msg);
}
public ValidateException() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
第五步:测试。
public class ValidateTest {
public static void main(String[] args) {
UserModel1 request = new UserModel1();
// @CreditCardNumber信用卡验证
// @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
request.setVersionNo("123");
request.setMobile("138013800000");
request.setEmail("130@qq.com");
try {
ValidateUtil.validate(request);
} catch (ValidateException e) {
//TODO 在实际业务中直接获取异常码、异常信息进行包装返回给前端即可
System.out.println(String.format("code: %s, msg:%s", e.getCode(), e.getMsg()));
}
}
}
结果:
三、自定义校验实现
package annotation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = MyCheckImpl.class)
public @interface MyCheck {
String message() default "未知错误";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
package annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MyCheckImpl implements ConstraintValidator<MyCheck, String>{
@Override
public void initialize(MyCheck constraintAnnotation) {
}
@Override
public boolean isValid(String arg0, ConstraintValidatorContext arg1) {
// 具体校验方法
if("男".equals(arg0)||"女".equals(arg0)){
return true;
}
return false;
}
}
在之前的实体类中使用我们的自定义校验
有没有激起大家兴趣呢?赶快尝试一下吧!!