作者:刘仁鹏
GitHub地址:https://github.com/LingPaicoder/agile-base-4j
转载请注明出处
1. 简介
- 之前为大家介绍过用Kotlin语言实现的校验器版本。但大家工作中,很多时候都是使用Java语言进行开发。所以这里再介绍一下校验器的Java语言实现。
- 功能与Kotlin的版本差别不大,但受语言的影响,特性上会有所不同。
- 首先还是介绍一下校验器的功能。
它是一款用于做 参数上下文校验 的工具类。说起校验器,大家比较熟悉的是Java的BeanValidation规范。但BeanValidation规范是用于做 参数字面值校验 的,即只能对 参数本身的值 做某种规则性的判断,如果需要对参数进行某种运算后的结果(例如算数运算、DB操作等)进行规则判断,就无法实现了。
这种对 参数进行某种运算后的结果 进行校验的方式,我把它称作 参数上下文校验。而本文要介绍的校验器就是为了解决这种应用场景而生的。 - 先来看一下校验器实际使用时的效果吧,这是一个对参数是否符合身份证号规则进行判断的方法:
public boolean valide(String idCard) {
try {
CheckUtil.check(idCard, StrRuler.notNull(), StrRuler.lengthEq(18));
String front17Part = idCard.substring(0, 17);
CheckUtil.check(front17Part, StrRuler.num());
String lastStr = getLastCharByFront17Part(front17Part);
CheckUtil.check(lastStr, StrRuler.eq(idCard.substring(17)));
return true;
} catch (CheckException e) {
return false;
}
}
- 代码中的第3、5、7行就是三次校验动作,这里通过CheckUtil的check方法,对校验目标进行规则校验,简化代码,提高可读性。
- 这里要额外说明一点的是,因为Java代码对null一般都需要额外处理,这里校验器也遵循这个约定,所有的内置的非nutNull规则,当校验目标为null时都按校验通过处理,因此当校验目标要求不能为null时,需要显式的通过notNull规则标识,这一特性与Kotlin的实现版本不同,需要特别注意。
- 接下来就一起看一下对校验器更全面的介绍。
2.特性
下面这个图是校验器的特性总览。
接下来介绍一下校验器的使用方法:
1.通过构造函数获得Ruler对象进行校验
@Test
@SuppressWarnings("unchecked")
public void testCommon() {
String trueIdcard = "130802198108204219";
CheckUtil.check(trueIdcard, "身份证号",
new StrNotNullRuler(),
new StrNotEmptyRuler(),
new StrIdCardRuler());
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11007, desc=身份证号必须符合身份证格式");
String falseIdcard = "130802198167204210";
CheckUtil.check(falseIdcard, "身份证号",
new StrNotNullRuler(),
new StrNotEmptyRuler(),
new StrIdCardRuler());
}
2.通过工厂方法获得Ruler对象进行校验
@Test
@SuppressWarnings("unchecked")
public void testFactory() {
String trueIdcard = "130802198108204219";
CheckUtil.check(trueIdcard, "身份证号",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.idCard());
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11007, desc=身份证号必须符合身份证格式");
String falseIdcard = "130802198108204210";
CheckUtil.check(falseIdcard, "身份证号",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.idCard());
}
3.捕获校验失败异常,获取校验失败code码和描述
@Test
@SuppressWarnings("unchecked")
public void testCatch() {
try {
String falseIdcard = "130802198108204210";
CheckUtil.check(falseIdcard, "身份证号",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.idCard());
} catch (CheckException e) {
Assert.assertEquals(-11007L, e.getCode());
Assert.assertEquals("身份证号必须符合身份证格式", e.getDesc());
Assert.assertEquals("code=-11007, desc=身份证号必须符合身份证格式", e.getMessage());
}
}
4.自定义错误编号和错误描述(支持在desc中对norm进行格式化)
@Test
@SuppressWarnings("unchecked")
public void testUserDefinedFailCodeAndDesc() {
String specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫";
final String NAME_DESC = "姓名";
final long NAME_TOO_LONG_CODE = -2L;
final int lteNorm = 10;
// 注意此处%d的用法
final String NAME_TOO_LONG_DESC = "长度超过限制,允许的最大长度:%d";
thrown.expect(CheckException.class);
thrown.expectMessage("code=-2, desc=姓名长度超过限制,允许的最大长度:10");
CheckUtil.check(specialName, NAME_DESC,
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.lengthGte(2),
StrRuler.lengthLte(lteNorm, NAME_TOO_LONG_CODE, NAME_TOO_LONG_DESC));
}
5.链式调用
@Test
@SuppressWarnings("unchecked")
public void testChainingCall() {
String trueIdcard = "130802198108204219";
String specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫";
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10");
// 非链式调用
CheckUtil.check(trueIdcard, "身份证号",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.idCard());
CheckUtil.check(specialName, "姓名",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.lengthGte(2),
StrRuler.lengthLte(10));
// 链式调用
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10");
CheckUtil
.check(trueIdcard, "身份证号",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.idCard())
.check(specialName, "姓名",
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.lengthGte(2),
StrRuler.lengthLte(10));
}
6.已校验结果可依赖性/后置校验对象求值中断功能
@Test
@SuppressWarnings("unchecked")
public void testNull() {
// 注意:并没有抛出NPE
thrown.expect(CheckException.class);
thrown.expectMessage("code=-10000, desc=商家不能为Null");
Custom custom = null;
CheckUtil.check(custom, "商家", new ObjNotNullRuler())
.check(custom.getCustomId(), "商家Id", new StrNotNullRuler());
// 补充:除了通过异常中断,还可以利用FunctionInterface的缓求值特性来实现该功能
// 但只能通过lambda表达式来生成Supplier对象,方法引用仍会抛出NPE
// 详见:https://www.jianshu.com/p/d3e69bd192c7
}
7.规则整合功能(Ruler多合一)
@Test
@SuppressWarnings("unchecked")
public void testRulerMerge() {
String specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫";
Ruler<String> nameRuler = Ruler.ofAll(
StrRuler.notNull(),
StrRuler.notEmpty(),
StrRuler.lengthGte(2),
StrRuler.lengthLte(10));
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10");
CheckUtil.check(specialName, "姓名", nameRuler);
}
8.规则的“或”逻辑
@Test
@SuppressWarnings("unchecked")
public void testOr() {
Ruler<String> nameRuler =
StrRuler.empty().or(StrRuler.lengthGte(2), StrRuler.lengthLte(10));
String specialName = "";
CheckUtil.check(specialName, "姓名", nameRuler);
specialName = "张三";
CheckUtil.check(specialName, "姓名", nameRuler);
thrown.expect(CheckException.class);
thrown.expectMessage("code=-11005, desc=姓名的长度必须小于或等于10");
specialName = "乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫";
CheckUtil.check(specialName, "姓名", nameRuler);
}
9.为实体类不同操作封装个性化Ruler
@Test
@SuppressWarnings("unchecked")
public void testEntity() {
// 可在业务工程中将customAddRuler对象以常量的方式提取到统一的Rulers类中
Ruler<Custom> customAddRuler = custom -> CheckUtil.check(custom, "商家", ObjRuler.notNull())
.check(custom.getCustomId(), "商家Id", StrRuler.notEmpty())
.check(custom.getName(), "商家姓名", StrRuler.notEmpty())
.check(custom.getAge(), "商家年龄", IntRuler.lte(60));
Custom custom = new Custom();
custom.setCustomId("123");
custom.setName("张三");
custom.setAge(80);
thrown.expect(CheckException.class);
thrown.expectMessage("code=-18005, desc=商家年龄必须小于或等于60");
CheckUtil.check(custom, customAddRuler);
}
@Data
static class Custom {
private String customId;
private String name;
private Integer age;
}
3. 其他
- 校验器的GitHub地址是https://github.com/LingPaicoder/agile-base-4j。
- 所有的校验器相关代码都放在 com.lpcoder.agile.base.forj.check 包下。
- 校验器的特性单测放在test的 com.lpcoder.agile.base.forj.check.CheckUtilTest 类中。
end
欢迎留言反馈对校验器的改进意见。
扫码可关注微信公众号: