五年前,科技大厦 1 层 B 座。java
小明的眼睛直勾勾地盯着屏幕,双手噼里啪啦的敲着键盘。git
思考是不存在的,思考只会让小明的速度降下来。程序员
优秀的程序员彻底不须要思考,就像不须要写文档和注释同样。github
“真是简单的需求啊”,小明以为有些无聊,“毫无挑战。”web
和无数个 web 开发者同样,小明今天作的是用户的注册功能。编程
首先定义一下对应的用户注册对象:api
public class UserRegister { /** * 名称 */ private String name; /** * 原始密码 */ private String password; /** * 确认密码 */ private String password2; /** * 性别 */ private String sex; // getter & setter & toString() }
注册时格式要求文档也作了简单的限制:框架
(1)name 名称必须介于 1-32 位之间maven
(2)password 密码必须介于 6-32 位之间ide
(3)password2 确认密码必须和 password 保持一致
(4)sex 性别必须为 BOY/GIRL 二者中的一个。
“这也不难”,无情的编码机器开始疯狂的敲打着键盘,不一下子基本的校验方法就写好了:
private void paramCheck(UserRegister userRegister) { //1. 名称 String name = userRegister.getName(); if(name == null) { throw new IllegalArgumentException("名称不可为空"); } if(name.length() < 1 || name.length() > 32) { throw new IllegalArgumentException("名称长度必须介于 1-32 之间"); } //2. 密码 String password = userRegister.getPassword(); if(password == null) { throw new IllegalArgumentException("密码不可为空"); } if(password.length() < 6 || password.length() > 32) { throw new IllegalArgumentException("密码长度必须介于 6-32 之间"); } //2.2 确认密码 String password2 = userRegister.getPassword2(); if(!password.equals(password2)) { throw new IllegalArgumentException("确认密码必须和密码保持一致"); } //3. 性别 String sex = userRegister.getSex(); if(!SexEnum.BOY.getCode().equals(sex) && !SexEnum.GIRL.getCode().equals(sex)) { throw new IllegalArgumentException("性别必须指定为 GIRL/BOY"); } }
打完收工,小明把代码提交完毕,就早早地下班跑路了。
“小明啊,我今天简单地看了一下你的代码。”,项目经理看似随意地提了一句。
小明停下了手中的工做,看向项目经理,意思是让他继续说下去。
“总体仍是比较严谨的,就是写了太多的校验代码。”
“太多的校验代码?不校验数据用户乱填怎么办?”,小明有些不太明白。
“校验代码的话,有时间能够了解一下 hibernate-validator 校验框架。”
“能够,我有时间看下。”
嘴上说着,小明内心一万个不肯意。
什么休眠框架,影响我搬砖的速度。
后来小明仍是勉为其难的搜索了一下 hibernate-validator,看了看感受还不错。
这个框架提供了不少内置的注解,便于平常校验的开发,大大提高了校验方法的可复用性。
因而,小明把本身的校验方法改良了一下:
public class UserRegister { /** * 名称 */ @NotNull(message = "名称不可为空") @Length(min = 1, max = 32, message = "名称长度必须介于 1-32 之间") private String name; /** * 原始密码 */ @NotNull(message = "密码不可为空不可为空") @Length(min = 1, max = 32, message = "密码长度必须介于 6-32 之间") private String password; /** * 确认密码 */ @NotNull(message = "确认密码不可为空不可为空") @Length(min = 1, max = 32, message = "确认密码必须介于 6-32 之间") private String password2; /** * 性别 */ private String sex; }
校验方法调整以下:
private void paramCheck2(UserRegister userRegister) { //1. 名称 ValidateUtil.validate(userRegister); //2.2 确认密码 String password2 = userRegister.getPassword2(); if(!userRegister.getPassword().equals(password2)) { throw new IllegalArgumentException("确认密码必须和密码保持一致"); } //3. 性别 String sex = userRegister.getSex(); if(!SexEnum.BOY.getCode().equals(sex) && !SexEnum.GIRL.getCode().equals(sex)) { throw new IllegalArgumentException("性别必须指定为 GIRL/BOY"); } }
确实清爽了不少,ValidateUtil 是基于一个简单的工具类:
public class ValidateUtil { /** * 使用hibernate的注解来进行验证 */ private static Validator validator = Validation .byProvider(HibernateValidator.class) .configure().failFast(true) .buildValidatorFactory() .getValidator(); public static <T> void validate(T t) { Set<ConstraintViolation<T>> constraintViolations = validator.validate(t); // 抛出检验异常 if (constraintViolations.size() > 0) { final String msg = constraintViolations.iterator().next().getMessage(); throw new IllegalArgumentException(msg); } } }
可是小明依然以为不满意,sex 的校验能够进一步优化吗?
答案是确定的,小明发现 hibernate-validator 支持自定义注解。
这是一个很强大的功能,优秀的框架就应该为使用者提供更多的可能性。
因而小明实现了一个自定义注解:
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MyEnumRangesValidator.class) public @interface MyEnumRanges { Class<? extends Enum> value(); String message() default ""; }
MyEnumRangesValidator 的实现以下:
public class MyEnumRangesValidator implements ConstraintValidator<MyEnumRanges, String> { private MyEnumRanges myEnumRanges; @Override public void initialize(MyEnumRanges constraintAnnotation) { this.myEnumRanges = constraintAnnotation; } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return getEnumValues(myEnumRanges.value()).contains(value); } /** * 获取枚举值对应的信息 * * @param enumClass 枚举类 * @return 枚举说明 * @since 0.0.9 */ private List<String> getEnumValues(Class<? extends Enum> enumClass) { Enum[] enums = enumClass.getEnumConstants(); return ArrayUtil.toList(enums, new IHandler<Enum, String>() { @Override public String handle(Enum anEnum) { return anEnum.toString(); } }); } }
限制当前的字段值必须在指定的枚举范围内,之后全部涉及到枚举范围的,使用这个注解便可搞定。
而后把 @MyEnumRanges
加在 sex 字段上:
@NotNull(message = "性别不可为空") @MyEnumRanges(message = "性别必须在 BOY/GIRL 范围内", value = SexEnum.class) private String sex;
这样校验方法能够简化以下:
private void paramCheck3(UserRegister userRegister) { //1. 名称 ValidateUtil.validate(userRegister); //2.2 确认密码 String password2 = userRegister.getPassword2(); if(!userRegister.getPassword().equals(password2)) { throw new IllegalArgumentException("确认密码必须和密码保持一致"); } }
小明满意的笑了笑。
可是他的笑容只是持续了一下子,由于他发现了一个不使人满意的地方。
确认密码这一段代码能够去掉吗?
好像直接使用 hibernate-validator 框架是作不到的。
这一切令小明很痛苦,他发现框架自己确实有不少不足之处。
现在 java 最流行的 hibernate-validator 框架,可是有些场景是没法知足的。
好比:
验证新密码和确认密码是否相同。(同一对象下的不一样属性之间关系)
当一个属性值知足某个条件时,才进行其余值的参数校验。
多个属性值,至少有一个不能为 null
其实,在对于多个字段的关联关系处理时,hibernate-validator 就会比较弱。
本项目结合原有的优势,进行这一点的功能强化。
validation-api 提供了丰富的特性定义,也同时带来了一个问题。
实现起来,特别复杂。
然而咱们实际使用中,经常不须要这么复杂的实现。
valid-api 提供了一套简化不少的 api,便于用户自行实现。
hibernate-validator 在使用中,自定义约束实现是基于注解的,针对单个属性校验不够灵活。
本项目中,将属性校验约束和注解约束区分开,便于复用和拓展。
hibernate-validator 核心支持的是注解式编程,基于 bean 的校验。
一个问题是针对属性校验不灵活,有时候针对 bean 的校验,仍是要本身写判断。
本项目支持 fluent-api 进行过程式编程,同时支持注解式编程。
尽量兼顾灵活性与便利性。
因而小明花了很长时间,写了一个校验工具,但愿能够弥补上述工具的不足。
支持 fluent-validation
支持 jsr-303 注解,支持全部 hibenrate-validator 经常使用注解
支持 i18n
支持用户自定义策略
支持用户自定义注解
支持针对属性的校验
支持过程式编程与注解式编程
支持指定校验生效的条件
<dependency> <groupId>com.github.houbb</groupId> <artifactId>valid-jsr</artifactId> <version>0.2.2</version> </dependency>
工具类使用:
User user = new User(); user.sex("what").password("old").password2("new"); ValidHelper.failOverThrow(user);
报错以下:
会抛出 ValidRuntimeException 异常,异常的信息以下:
name: 值 <null> 不是预期值,password: 值 <old> 不是预期值,sex: 值 <what> 不是预期值
其中 User 的定义以下:
public class User { /** * 名称 */ @HasNotNull({"nickName"}) private String name; /** * 昵称 */ private String nickName; /** * 原始密码 */ @AllEquals("password2") private String password; /** * 新密码 */ private String password2; /** * 性别 */ @Ranges({"boy", "girl"}) private String sex; /** * 失败类型枚举 */ @EnumRanges(FailTypeEnum.class) private String failType; //Getter and Setter }
内置注解简介以下:
注解 | 说明 |
---|---|
@AllEquals | 当前字段及指定字段值必须所有相等 |
@HasNotNull | 当前字段及指定字段值至少有一个不为 null |
@EnumRanges | 当前字段值必须在枚举属性范围内 |
@Ranges | 当前字段值必须在指定属性范围内 |
小明在设计验证工具的时候,针对 hibernater 的不足都作了一点小小的改进。
可让字段之间产生联系,以提供更增强大的功能。
每个注解都有对应的过程式方法,让你能够在注解式和过程式中切换自如。
内置了 @Condition
的注解生效条件,让注解生效更加灵活。
小明抬头看了看墙上的钟,夜已经太深了,百闻不如一见,感兴趣的小伙伴能够本身去感觉一下:
这个开源工具是平常工做中不想写太多校验方法的产物,还处于初期阶段,还有不少须要改进的地方。
不过,但愿你能喜欢。
我是老马,期待与你的下次重逢。