目前spring常用的验证框架有 JSR - 303 、Bean验证框架等等。但是这些验证框架都有一个毛病,就是使用困难,内聚性差,并且代码可控性低。
比如JSR - 303框架注解,@Null、@NotNull 等等,太过于繁琐,并且一个实体类中每个字段都要注解验证。但是我们常用的验证可能只有空值检查,或者适合我们项目的常用验证。并且自定义验证太过于麻烦,并且报错信息控制繁琐到让人难以忍受。所以本人按照个人理解和使用习惯,写了一个自定义验证框架,盼望各位看官多多指教。
首先写一个自定义注解
//自定义注解,用于放置需要验证的controller方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BetterCheck {
CheckGroupEumn group() default CheckGroupEumn.NORMAL;
}
public enum CheckGroupEumn {
NORMAL(1, "普通组"),
UPDATE(2, "更新组")
;
}
这个自定义注解里面,包含了一个组枚举类,用于区分校验组,比如新建组,和修改组。这样可以应付多种情况下的检测,从而达到少写实体类的目的。
//检验注解,普通组和更新组
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NormalCheck {
CheckTypeEumn type() default CheckTypeEumn.NOT_CHECK;
boolean allGroup() default true;
long max() default 100;
long min() default 0;
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdateCheck {
CheckTypeEumn type() default CheckTypeEumn.NOT_CHECK;
long max() default 100;
long min() default 0;
}
public enum CheckTypeEumn {
NOT_CHECK(1, "不做检测"),
MOBILE(2, "手机"),
MONEY(3, "金钱"),
RANGE(4, "范围"),
NUll(5,"空值")
;
}
可以看到普通组中加入了 allGroup这个 boolean变量,这是为了可以让一个注解,同时在两个组都能进行验证。并且本验证模块,和本人写这个模块的最大原因就是,空值检查自动检查。因为几乎所有变量默认都是检查空值。。。(标题吹比框架....,,容我做次标题党 O(∩_∩)O哈哈)
基本工作准备完毕,下面就开始写切面类,对方法进行增强
public class ValidaAspect {
@Pointcut("@annotation(com.myvalida.anntion.BetterCheck)")
public void checkPoint() {
}
@Around("checkPoint()")
public Object arount(ProceedingJoinPoint point) throws Throwable {
//获取当前方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
//获取当前注解的Group环境
BetterCheck betterCheck = method.getAnnotation(BetterCheck.class);
CheckGroupEumn group = betterCheck.group();
//获取所有的参数,和所有的参数变量
Object[] objects = point.getArgs();
Parameter[] parameters = method.getParameters();
//进入检验函数
check(objects, parameters, group);
return point.proceed(objects);
}
}
//首先对所有参数和变量逐个检查,检查函数用迭代,目前只嵌套2次
private void check(Object[] objs, Parameter[] paras, CheckGroupEumn group) throws Throwable {
for (int i = 0; i < objs.length; i++) {
Parameter para = paras[i];
Object obj = objs[i];
Annotation[] annotations = para.getAnnotations();
String paraName = para.getName();
doCheck(paraName, obj, annotations, group, 2);
}
}
//doCheck函数
private void doCheck(String paraName, Object obj, Annotation[] annotations, CheckGroupEumn group, int floor) throws Throwable {
if (annotations == null || annotations.length == 0) {
checkNotNull(paraName, obj, group, floor);
} else {
for (Annotation annotation : annotations) {
NormalCheck checkType = checkGroup(group, annotation);
if (checkType != null) {
switch (checkType.type()) {
case NOT_CHECK:
break;
case MONEY:
checkMoney(obj, checkType.type().getLabel());
break;
。。。。。。。。
}
} else {
checkNotNull(paraName, obj, group, 1);
}
}
}
}
当变量没有注解时候,就默认做空值检查。
private void checkNotNull(String paraName, Object obj, CheckGroupEumn group, int floor) throws Throwable {
if (ObjectUtils.isEmpty(obj)) {
throw new OperationNotAllowException(paraName + "值不能为空");
} else if (floor > 1) {
Class> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Annotation[] annotations = field.getAnnotations();
Object o = field.get(obj);
doCheck(field.getName(), o, annotations, group, floor - 1);
}
}
}
当对象不为空的时候,对实体类内部所有变量进行检查。只重复一次。
//检查分组功能,默认NormalCheck就包含了两个组的检测。
private NormalCheck checkGroup(CheckGroupEumn group, Annotation annotation) {
if (group.equals(CheckGroupEumn.NORMAL)) {
if (annotation instanceof NormalCheck) {
return (NormalCheck) annotation;
}
} else {
if (annotation instanceof UpdateCheck) {
return modelMapper.map(annotation, NormalCheck.class);
} else if (annotation instanceof NormalCheck && ((NormalCheck) annotation).allGroup()) {
return (NormalCheck) annotation;
}
}
return null;
}
检测方法举例
private void checkNull(String paraName, Object obj) throws Throwable {
if (!ObjectUtils.isEmpty(obj)) {
throw new OperationNotAllowException("不能传入" + paraName + ",值必须为空");
}
}
1. 内聚性强,所有的检测方法都在一个类,有复杂方法,可以引入实现类。
2. 不用记多个注解,只要三个注解 @BetterCheck @NormalCheck @UpdateCheck
3. 异常可控性强,想怎么玩就怎么玩
4. 代码量少,如果默认非空检测,只要一个@BetterCheck,不会影响原来任何代码
5. 注解更清晰,枚举类说明,清晰介绍检查是干啥子的
6. 性能更强,少了反射调用方法的代码,性能更上n层楼
7. 轻量级结构,仅仅依赖于Spring
@ApiOperation(value = "添加组")
@BetterCheck
@GetMapping("testadd")
public void testAdd(
TestClass cmd
, @NormalCheck(type = CheckTypeEumn.RANGE, max = 13L, min = 11L)
Integer size
, String normal
) {
System.out.println("因为有了无奈");
}
@ApiOperation(value = "更新组")
@BetterCheck(group = CheckGroupEumn.UPDATE)
@GetMapping("testUpdate")
public void testUpdate(
TestClass cmd
, @UpdateCheck(type = CheckTypeEumn.NUll) String mustNull
, String normal
) {
System.out.println("这就是人生");
}
public class TestClass {
private String defaultCheck;
@NormalCheck(type = CheckTypeEumn.NUll,allGroup = false)
//UpdateCheck组中默认做空值检测
private Long id;
@NormalCheck(type = CheckTypeEumn.RANGE )
private Long range;
@NormalCheck(type = CheckTypeEumn.MOBILE)
private String mobile;
@NormalCheck(type = CheckTypeEumn.MONEY)
private BigDecimal money;
@NormalCheck
private String notCheck;
}
两个组,添加组和更新组,个人感觉还是比较清晰,简洁。