简单实现Bean字段校验
原理总的来说是,反射+自定义函数接口(Java 8)+Map 关联注解与验证实现,比较简单,顶多 100 行代码搞定,都是本着咱够用就行的要求,其他的不想 BB 那么多,要是真有问题,到时再说。
首先写个单测,Bean 如下:
class News { @NotNull
private long id;
@NotBlank(message = "请输入名称")
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
可以看到,JSR 的 @NotNull
和 @NotBlank
分别绑定在 id 和 name 属性上,约束 id 字段不能为 null,由于 id 是 long 类型,故也不能为 0;name 属性不能为空字符串。name 属性还自定义了出错时的信息 message。
自定义函数接口
当 JDK 自带的函数接口类型不能满足时,就要自定义函数接口。BiFunction
只能支持两个参数,当下我们的场景是一个是 Bean 属性的值,一个是 Bean 属性对象本身(Field,又称字段对象,反射得来的),最后是约束条件,即注解,——一共三个参数,故 BiFunction
不能满足,只能自己另外写,如下所示。
package com.ajaxjs.validator;import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
@FunctionalInterface
public interface Validator {
/**
* 执行验证
*
* @param value Bean 字段的值
* @param field Bean 字段对象
* @param ann Bean 字段上面的注解
* @return 错误信息,如果为 null 表示完全通过
*/
public String valid(Object value, Field field, Annotation ann);
}
返回值是 String,指错误信息,如果通过则返回 null,非 null 说明哪一个属性(字段)不符合要求,这个 String 就是不符合要求的原因了。
验证器存储结构
不知如何更好地表达,存储结构——好像怪怪的,反正,就是一个简单 Map:key 是注解类,value 是验证码,这样它们构成了一对一的关系,作为静态成员保存着。怎么用?下面反射的时候会说,你看了就明白 Map 那样用的,一点都不复杂。
package com.ajaxjs.validator;import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import com.ajaxjs.util.logger.LogHelper;
public class BeanValidator {
private static final LogHelper LOGGER = LogHelper.getLog(BeanValidator.class);
/**
* 构成注解与验证器一一对应的关系
*/
private static final Map<Class<?>, Validator> cache = new HashMap<>();
/**
* 注册一个验证器
*
* @param clzs 注解类
* @param validator 验证器 lambda
*/
public static void register(Class<?> clzs, Validator validator) {
cache.put(clzs, validator);
}
static {
register(NotNull.class, BuiltinValidator.NOT_NULL_VALIDATOR);
register(NotBlank.class, BuiltinValidator.NOT_BLANK_VALIDATOR);
}
……
}
验证器用之前需要注册,无非就是 put 进 Map 里面去,例如 register(NotNull.class, BuiltinValidator.NOT_NULL_VALIDATOR);
。NOT_NULL_VALIDATOR 就是应用函数接口的验证器,对应 @NotNull
的情况。NOT_NULL_VALIDATOR 就是一个普通的 lambda,前面已经说了,就是把握函数的参数和返回值,具体用途是什么,为什么要传那些参数?够不够用?返回哪种类型结果?NOT_NULL_VALIDATOR 源码如下。
public static final Validator NOT_NULL_VALIDATOR = (value, field, ann) -> { if (value == null) {
NotBlank n = (NotBlank) ann;
return n.message() != null ? n.message() : field.getName() + " 不能为 null";
} else if (value != null && value instanceof Number) {
Number num = (Number) value;
if (num.equals(0) || num.equals(0L)) {
NotNull n = (NotNull) ann;
return n.message() != null ? n.message() : field.getName() + " 不能为 null";
} else
return null;
} else
return null;
};
执行验证
如果只是学怎么用,那么上面原理性的内容是不用看的,只是学会调用者 API 唯一的暴露方法 BeanValidator.validate(Object bean)
即可。这里就是对 Bean 反射操作,获取所需的信息用于判读是否符合 Bean 要求。
/** * 校验实体
*
* @param bean 实体
* @return 错误集合,若数组为 length=0表示完全通过
*/
public static String[] validate(Object bean) {
List<String> list = new ArrayList<>();
Class<?> cls = bean.getClass();
Field[] fields = cls.getDeclaredFields();
try {
// 获取实体字段集合
for (Field f : fields) {// 通过反射获取该属性对应的值
f.setAccessible(true);
Object value = f.get(bean);// 获取字段值
Annotation[] arrayAno = f.getAnnotations();// 获取字段上的注解集合
for (Annotation annotation : arrayAno) {
Class<?> clazz = annotation.annotationType();// 获取注解类型(注解类的Class)
Validator validator = cache.get(clazz);
if (validator == null) // 不是验证器的注解
continue;
String result = validator.valid(value, f, annotation);
if (result != null)
list.add(result);
}
}
} catch (Exception e) {
LOGGER.warning(e, "验证出错");
}
return list.toArray(new String[list.size()]);
}
小结
BuiltinValidator 内建的验证码考虑了一般情况,如非空、Max/Min/Size等,也就是 JSR 默认那些。用户可以继续扩展,给出自己的验证码,然后注册一下即可,比说试试写个身份证验证的……就留给读者去做吧~
以上所有源码可以在 https://gitee.com/sp42_admin/ajaxjs 找到。
以上是 简单实现Bean字段校验 的全部内容, 来源链接: utcz.com/z/518955.html