简单实现 Bean 字段校验

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

©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页