简单实现 Bean 字段校验

末蓝、 2023-03-04 03:23 62阅读 0赞

关于 Bean 字段校验,我之前曾用 Apache BVal 探讨过,现在连这货都不想用,少一个依赖是一个。自己做,若完全按照 JSR 303 规范来实现会非常麻烦,没有那个必要。于是取舍一下,还是沿用 JSR 303 的注解作为约束条件,参考这位仁兄的基于反射的做法,自己实现一套 Bean 校验。

原理总的来说是,反射+自定义函数接口(Java 8)+Map 关联注解与验证实现,比较简单,顶多 100 行代码搞定,都是本着咱够用就行的要求,其他的不想 BB 那么多,要是真有问题,到时再说。

首先写个单测,Bean 如下:

  1. class News {
  2. @NotNull
  3. private long id;
  4. @NotBlank(message = "请输入名称")
  5. private String name;
  6. public long getId() {
  7. return id;
  8. }
  9. public void setId(long id) {
  10. this.id = id;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. }

可以看到,JSR 的 @NotNull@NotBlank 分别绑定在 id 和 name 属性上,约束 id 字段不能为 null,由于 id 是 long 类型,故也不能为 0;name 属性不能为空字符串。name 属性还自定义了出错时的信息 message。

自定义函数接口

当 JDK 自带的函数接口类型不能满足时,就要自定义函数接口。BiFunction 只能支持两个参数,当下我们的场景是一个是 Bean 属性的值,一个是 Bean 属性对象本身(Field,又称字段对象,反射得来的),最后是约束条件,即注解,——一共三个参数,故 BiFunction 不能满足,只能自己另外写,如下所示。

  1. package com.ajaxjs.validator;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Field;
  4. @FunctionalInterface
  5. public interface Validator {
  6. /** * 执行验证 * * @param value Bean 字段的值 * @param field Bean 字段对象 * @param ann Bean 字段上面的注解 * @return 错误信息,如果为 null 表示完全通过 */
  7. public String valid(Object value, Field field, Annotation ann);
  8. }

返回值是 String,指错误信息,如果通过则返回 null,非 null 说明哪一个属性(字段)不符合要求,这个 String 就是不符合要求的原因了。

验证器存储结构

不知如何更好地表达,存储结构——好像怪怪的,反正,就是一个简单 Map:key 是注解类,value 是验证码,这样它们构成了一对一的关系,作为静态成员保存着。怎么用?下面反射的时候会说,你看了就明白 Map 那样用的,一点都不复杂。

  1. package com.ajaxjs.validator;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Field;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import javax.validation.constraints.NotBlank;
  9. import javax.validation.constraints.NotNull;
  10. import com.ajaxjs.util.logger.LogHelper;
  11. public class BeanValidator {
  12. private static final LogHelper LOGGER = LogHelper.getLog(BeanValidator.class);
  13. /** * 构成注解与验证器一一对应的关系 */
  14. private static final Map<Class<?>, Validator> cache = new HashMap<>();
  15. /** * 注册一个验证器 * * @param clzs 注解类 * @param validator 验证器 lambda */
  16. public static void register(Class<?> clzs, Validator validator) {
  17. cache.put(clzs, validator);
  18. }
  19. static {
  20. register(NotNull.class, BuiltinValidator.NOT_NULL_VALIDATOR);
  21. register(NotBlank.class, BuiltinValidator.NOT_BLANK_VALIDATOR);
  22. }
  23. ……
  24. }

验证器用之前需要注册,无非就是 put 进 Map 里面去,例如 register(NotNull.class, BuiltinValidator.NOT_NULL_VALIDATOR);。NOT_NULL_VALIDATOR 就是应用函数接口的验证器,对应 @NotNull 的情况。NOT_NULL_VALIDATOR 就是一个普通的 lambda,前面已经说了,就是把握函数的参数和返回值,具体用途是什么,为什么要传那些参数?够不够用?返回哪种类型结果?NOT_NULL_VALIDATOR 源码如下。

  1. public static final Validator NOT_NULL_VALIDATOR = (value, field, ann) -> {
  2. if (value == null) {
  3. NotBlank n = (NotBlank) ann;
  4. return n.message() != null ? n.message() : field.getName() + " 不能为 null";
  5. } else if (value != null && value instanceof Number) {
  6. Number num = (Number) value;
  7. if (num.equals(0) || num.equals(0L)) {
  8. NotNull n = (NotNull) ann;
  9. return n.message() != null ? n.message() : field.getName() + " 不能为 null";
  10. } else
  11. return null;
  12. } else
  13. return null;
  14. };

执行验证

如果只是学怎么用,那么上面原理性的内容是不用看的,只是学会调用者 API 唯一的暴露方法 BeanValidator.validate(Object bean) 即可。这里就是对 Bean 反射操作,获取所需的信息用于判读是否符合 Bean 要求。

  1. /** * 校验实体 * * @param bean 实体 * @return 错误集合,若数组为 length=0表示完全通过 */
  2. public static String[] validate(Object bean) {
  3. List<String> list = new ArrayList<>();
  4. Class<?> cls = bean.getClass();
  5. Field[] fields = cls.getDeclaredFields();
  6. try {
  7. // 获取实体字段集合
  8. for (Field f : fields) { // 通过反射获取该属性对应的值
  9. f.setAccessible(true);
  10. Object value = f.get(bean);// 获取字段值
  11. Annotation[] arrayAno = f.getAnnotations();// 获取字段上的注解集合
  12. for (Annotation annotation : arrayAno) {
  13. Class<?> clazz = annotation.annotationType();// 获取注解类型(注解类的Class)
  14. Validator validator = cache.get(clazz);
  15. if (validator == null) // 不是验证器的注解
  16. continue;
  17. String result = validator.valid(value, f, annotation);
  18. if (result != null)
  19. list.add(result);
  20. }
  21. }
  22. } catch (Exception e) {
  23. LOGGER.warning(e, "验证出错");
  24. }
  25. return list.toArray(new String[list.size()]);
  26. }

小结

BuiltinValidator 内建的验证码考虑了一般情况,如非空、Max/Min/Size 等,也就是 JSR 默认那些。用户可以继续扩展,给出自己的验证码,然后注册一下即可,比说试试写个身份证验证的……就留给读者去做吧~

以上所有源码可以在 https://gitee.com/sp42_admin/ajaxjs 找到。

发表评论

表情:
评论列表 (有 0 条评论,62人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JAVA字段校验(validation)

    在开发业务时,不可避免的需要处理一些校验, 如果是写if-else这种代码去校验, 那会有一大段这样的代码。不过还好有个校验插件:javax.validation.valida