简单实现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

回到顶部