Android自定义注解实现一键校验实体类参数
- 前言
- 本文代码
- 需求
- 实现
- 新建自定义类注解
- 新建变量注解
- 新建变量注解VerifyParams
- 给变量注解增添方法
- 给实体类增加上注解
- 新建验证方法EntityValidator
- 解析注解字段
- 查看效果
- 遗留问题
- 思考问题
- 解决遗留问题
- 新增注解方法
- 分析空判断的情况
- 提前校验各种空值
- 实现各种校验类型
- 关于空优先级的问题
- 新的问题
- 重新排序
- 写在最后
前言
在阅读这篇文章前您需要先学会反射与类加载
和 注解深入浅出
这两项基础知识,关于这两项技术的解读就不赘述了,因为死记硬背的知识没什么好介绍的,大家可以自行百度。
本文代码
https://github.com/fzkf9225/mvvm-componnent-master/tree/master/common/src/main/java/pers/fz/mvvm/annotations
需求
首先我们想实现下图这样一个效果,一个表单,然后输入各种数据,最后点击按钮校验,在按钮下方输入结果,并且我们不用写一堆if条件语句判断输入是否合格
点击按钮校验
binding.verifySubmit.setOnClickListener(v -> {
showLoading("验证中...");
VerifyResult verifyResult = EntityValidator.validate(binding.getData());
hideLoading();
if (verifyResult.isOk()) {
binding.tvVerifyResult.setTextColor(ContextCompat.getColor(this, pers.fz.mvvm.R.color.theme_green));
} else {
binding.tvVerifyResult.setTextColor(ContextCompat.getColor(this, pers.fz.mvvm.R.color.theme_red));
}
showToast((verifyResult.isOk() ? "验证成功" : "验证失败:") + StringUtil.FilterNull(verifyResult.getErrorMsg()));
binding.tvVerifyResult.setText(String.format("%s,验证结果:%s", binding.getData().toString(), verifyResult.getErrorMsg()));
});
是不是很方便简单,那么先了解反射与类加载
和 注解深入浅出
这两个知识点就可以开始撸码了。
实现
先假如我们需要验证这样一个实体类:
public class Person{
private String name;
public Person() {
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们需要检验这个姓名:
- 是否为空
- 长度是否符合规律
- 姓名中是否包含特殊字符,仅限中文汉字
难么我们是不是需要写很多if判断,现在我们集合自定义注解去实现它
新建自定义类注解
在项目中新建一个包名,然后右键new
---->Java Class
—>Annotation
public @interface VerifyEntity {
}
现在我们利用刚才所学的注解深入浅出
得到的知识,给自定义注解添加系统提供的注解
@Target(value = {ElementType.TYPE,ElementType.METHOD})//具体用法自行学习了,解释他没有意义
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface VerifyEntity {
}
然后我们添加上开启验证方法
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface VerifyEntity {
//默认开启注解,则注解实体类时就不用写了,添加这个方法是便于一键关闭注解验证
boolean enable() default true;
}
新建变量注解
当然我们有了类注解没用呀,对吧。因为我们需要对实体类里面的字段进行验证。这时我们就需要用ElementType.FIELD
枚举字段代表系统提供给我们注解变量的。
新建变量注解VerifyParams
新建变量注解,并添加上系统提供的自定义注解解释器
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyParams {
}
给变量注解增添方法
注解变量我们需要做什么呢?我们想起刚才的问题是不是
那我们新增几个自定义方法
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyParams {
boolean notNull() default false;
//注解参数如果不添加default那么就必须要在注解的时候指定值,我们加上默认值就可以不写,因为有的时候我们用不上
int minLength() default -1;
int maxLength() default -1;
}
给实体类增加上注解
@VerifyEntity
public class Person{
@VerifyParams(minLength = 2, maxLength = 10,notNull = true),
private String name;
public Person() {
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
但是虽然有了注解但是没有实际效果,所以我们想一下,我们什么时机需要调用它?
答案当然是:点击。当然我们还有更多的触发情况,但是总结一句:主动执行!
那么我们就可以写一个方法取触发它
新建验证方法EntityValidator
这个时候我们需要用到我们上面学习的反射与类加载
的知识了
public class EntityValidator {
private final static String TAG = EntityValidator.class.getSimpleName();
//传入实体类
public static VerifyResult validate(Object entity) {
/*
* 实现代码再下面
*/
retrun VerifyResult.ok();
}
}
我们需要返回验证是否成功和提示文字,因此我们需要一个实体类
public class VerifyResult {
private boolean isSuccess;
private String errorMsg;
public VerifyResult() {
}
public VerifyResult(boolean isSuccess, String errorMsg) {
this.isSuccess = isSuccess;
this.errorMsg = errorMsg;
}
public boolean isOk() {
return this.isSuccess;
}
public static VerifyResult ok() {
return new VerifyResult(true, null);
}
public static VerifyResult ok(String errorMsg) {
return new VerifyResult(true, errorMsg);
}
public static VerifyResult fail(String errorMsg) {
return new VerifyResult(false, errorMsg);
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
@Override
public String toString() {
return "VerifyResult{" +
"isSuccess=" + isSuccess +
", errorMsg='" + errorMsg + '\'' +
'}';
}
}
那么现在我们需要获取到类注解了
//接上面注释部分代码
Class<?> clazz = entity.getClass();
VerifyEntity validation = clazz.getAnnotation(VerifyEntity.class);
//如果获取不到则认为不需要验证,直接返回成功
if (validation == null) {
return VerifyResult.ok();
}
//手动关闭验证的情况
if (!validation.enable()) {
return VerifyResult.ok();
}
如果类需要验证那么我们接下来就需要获取字段属性的注解了,那么我们获取字段的注解VerifyParams
Field[] fields = clazz.getDeclaredFields();
if (fields.length == 0) {
return VerifyResult.ok();
}
for (Field field : fields) {
//如果当前字段没有注解则肯定不需要判断对吧,则执行下一个字段的判断
if (!field.isAnnotationPresent(VerifyParams.class)) {
continue;
}
//因为实体类字段都是private因此需要加上它,修改字段不可读写的属性
field.setAccessible(true);
VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
if (validationParam == null) {
return VerifyResult.ok();
}
//接下来就需要进行注解解析验证了
}
这个时候我们已经可以获取注解了,那么接下来干嘛?当然是解析注解
解析注解字段
接着上面往下写
//接下来就需要进行注解解析验证了
//一切的验证都是基于不为空的情况下,所以先验证空,我们先获取当前注解字段的值
Object value = field.get(entity);
if (validationParam .notNull() && value == null) {
return VerifyResult.fail("您的输入为空!");
}
写到这个时候我们听一下,我们这样提示给用户是不是会让用户丈二和尚摸不着头脑?但是我们又不能直接把字段名提示给用户,用户也看不懂,那么我们则可以给VerifyParams注解
增加个方法
/**
* 错误提示信息
* @return String
*/
String errorMsg() default "信息填写错误,请验证后重新输入!";
在使用注解的时候自定义提示内容,然后我们修改下上面的代码
//接下来就需要进行注解解析验证了
//一切的验证都是基于不为空的情况下,所以先验证空,我们先获取当前注解字段的值
Object value = field.get(entity);
if (validationParam.notNull() && value == null) {
return VerifyResult.fail(validationParam.errorMsg());
}
//然后我们再判断下长度
//当最小长度和最大长度都未指定的时候则默认不判断
if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
return VerifyResult.ok();
}
//分为几种情况,1、只指定最小值,2、只指定最大值,3、最大最小值都指定
if (validationParams.minLength() < 0 && value.toString().length() >= validationParams.maxLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.maxLength() < 0 && value.toString().length() <= validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (value.toString().length() >= validationParams.maxLength() || value.toString().length() <= validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
查看效果
VerifyResult verifyResult = EntityValidator.validate(binding.getData());
//判断verifyResult结果
其实这个时候你看效果已经实现了
但是这样就结束了吗???当然没有!!!
遗留问题
我们实际的效果比这个复杂很多,而且上述代码也有很多问题,比如:
- 空判断和长度判断共用了一个提示文字
- 空判断只能判断null,但是有时候我们需要他可以为null有时候不可以,有时候不允许为null但可以为空字符串、空集合、空map等等
- 是否可以支持判断输入内容与某个字符串相匹配才能通过校验
- 是否可以支持判断输入内容是手机号码、电话号码、邮件、url等等
- 是否可以支持判断数字的大小,范围
- 是否可以支持判断有无保留两位有效数字
- 是否可以支持判断正则
那么我们需要解决几个问题:
- 我们不用这么多判断共用一个
errorMsg
- 需要知道当前注解用于判断那种类型,是判断长度、数字、还是正则还是null
- 一个字段上不用直接多条
VerifyParams
注解的问题,那我们怎么实现一个字段同时支持多个校验呢?
思考问题
我们如果想支持多个errorMsg
和同时支持多个校验类型,那么我们会想到:数组、集合、map等等。但是注解只支持数组,那我们可以利用注解嵌套一个注解数组来实现多类型校验的效果。那么思路有了,我们来完善一下
解决遗留问题
新增注解方法
我们先给VerifyParams注解
增加一些需要的方法
首先是校验类型
public enum VerifyType {
/**
* 验证类型
*/
NOTNULL,
//与目标值是否相等
EQUALS,
//数字
NUMBER,
//整数
NUMBER_INTEGER,
//小数
NUMBER_DOUBLE,
//两位有效数字
NUMBER_00,
//email
EMAIL,
//手机号码、固话校验,不包括区号和固化
PHONE,
//手机号码校验,不包括区号和固化
MOBILE_PHONE,
//固话验,不包括区号和手机号码
TEL_PHONE,
//数字范围验证
NUMBER_RANGE,
//数字范围验证,包含最大最小值
NUMBER_RANGE_EQUAL,
//长度验证
LENGTH_RANGE,
//长度验证,包含最大最小值
LENGTH_RANGE_EQUAL,
//自定义正则
REGEX,
;
private String type;
public String getType() {
return type;
}
}
新增一些校验方法字段
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyParams {
VerifyType type();
/**
* 是否允许为空字符串、空集合、空map等
* @return 默认可以为空
*/
boolean notEmpty() default false;
String equalStr() default "";
boolean notNull() default false;
int minLength() default -1;
int maxLength() default -1;
double minNumber() default -Double.MIN_VALUE;
double maxNumber() default Double.MAX_VALUE;
/**
* 错误提示信息
* @return String
*/
String errorMsg() default "信息填写错误,请验证后重新输入!";
/**
* 正则表达式
* @return 正则表达式字符串
*/
String regex() default "";
}
新增注解数组来嵌套VerifyParams
同样的new
---->Java Class
—>Annotation
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyField {
VerifyParams[] value();
}
我们再校验时先获取注解
VerifyField validationField = field.getAnnotation(VerifyField.class);
VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
但是有时候不需要多个类型注解,我们就可以两种注解同时混合用,但是再校验时把他们获取整合为一个整的数组,然后遍历判断校验
VerifyField validationField = field.getAnnotation(VerifyField.class);
VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
if (validationParam == null && validationField == null) {
return VerifyResult.ok();
}
VerifyParams[] verifyParamsList;
if (validationField == null) {
verifyParamsList = new VerifyParams[]{validationParam};
} else if (validationParam == null) {
verifyParamsList = validationField.value();
} else {
verifyParamsList = Arrays.copyOf(validationField.value(), validationField.value().length + 1);
verifyParamsList[verifyParamsList.length - 1] = validationParam;
}
for (VerifyParams params : verifyParamsList) {
//这个跟之前差不多了,也有点区别
}
遍历VerifyParams 数组,我们首先需要知道当前注解属于什么类型
VerifyType verifyType = params.type();
if (verifyType == null) {
return VerifyResult.ok();
}
分析空判断的情况
因为我们所有的校验都和空有关因此,这个至关重要
思考空判断分为几种情况:
- 枚举
VerifyType.NOTNULL
和注解方法boolean notNull() default false;
效果其实一样的,优先级最高,因为一切的校验都基于null
boolean notNull() default false;
注解方法只是有的时候可以null和其他校验一起判断省一个注解语句,但是他的优先级要高于当前校验类型(枚举值不为VerifyType.NOTNULL
)boolean notNull() default false;
和VerifyType.NOTNULL
仅用户判断字段值是否为null
并不适用于空字符串
、空集合
、空map
等等boolean notNull() default false;
和VerifyType.NOTNULL
需要结合注解方法boolean notEmpty() default false;
来实现空字符串
、空集合
、空map
这些空实现的判断- 当校验类型不为
VerifyType.NOTNULL
时,但是boolean notNull() default false;
注解方法为false即可以为空时,即:当前字段允许为空,但是不为空的时候需要校验输入内容
提前校验各种空值
根据上面问题,那么我们先把空的几种情况判断了
VerifyType verifyType = params.type();
if (verifyType == null) {
return VerifyResult.ok();
}
//一切的验证都是基于不为空的情况下,所以先验证空
Object value = field.get(entity);
//当有VerifyType.NOTNULL、notNull为true时不管其他条件只要为空则返回错误
boolean isNullValue = (VerifyType.NOTNULL == verifyType || params.notNull()) && value == null;
if (isNullValue) {
return VerifyResult.fail(params.errorMsg());
}
//当不是NOTNULL但是又允许为空时,即判断某个条件时,有输入则判断,没有输入值则不判断
if (VerifyType.NOTNULL != verifyType && !params.notNull()) {
if (value == null) {
return VerifyResult.ok();
} else if (value instanceof Collection<?> collection && collection.size() == 0) {
return VerifyResult.ok();
} else if (value instanceof Map<?, ?> map && map.size() == 0) {
return VerifyResult.ok();
} else if (StringUtil.isEmpty(value)) {
return VerifyResult.ok();
}
}
//是否允许为空实现,空集合、空map等情况
if (params.notEmpty()) {
if (value instanceof Collection<?> collection && collection.size() == 0) {
return VerifyResult.fail(params.errorMsg());
} else if (value instanceof Map<?, ?> map && map.size() == 0) {
return VerifyResult.fail(params.errorMsg());
} else if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(params.errorMsg());
}
}
但是上面判断还是有漏洞,主要来自于notNull()
和notEmpty()
,因为notEmpty()
为true时notNull()
为false就不应该有效果,但是notEmpty()
为false时,notNull()
为true时就需要有效果,所以我觉得这种情况还是自行约定吧,没必要写那么复杂的判断了
实现各种校验类型
空判断过去了,那么我们就可以获取校验类型了
VerifyType verifyType = params.type();
然后就if判断各种类型,进行数据校验了,没什么技术问题,大家自己写吧
关于空优先级的问题
VerifyType.NOTNULL
最高,主要单独使用;notNull()
其次,主要结合其他的校验类型使用;notEmpty()
最低,主要结合前两个字段用户判断是否允许为空实现,空字符串
、空集合
、空map
他必须结合前两个来使用,单独使用不知道是否有问题,未经过大量测试。
新的问题
我们有了上面的优化后我们可以校验Person
实体类了
@VerifyEntity
public class Person extends BaseObservable {
@VerifyField({
@VerifyParams(type = VerifyType.NOTNULL, notEmpty = true, errorMsg = "姓名为空!"),
@VerifyParams(type = VerifyType.LENGTH_RANGE_EQUAL, minLength = 2, maxLength = 10, errorMsg = "姓名输入错误!"),
@VerifyParams(type = VerifyType.EQUALS, errorMsg = "您只能填张三!", equalStr = "张三")
})
private String name;
@VerifyField({
@VerifyParams(type = VerifyType.NOTNULL, errorMsg = "请填写手机号码!"),
@VerifyParams(type = VerifyType.MOBILE_PHONE, errorMsg = "手机号码格式输入不正确!")
})
private String mobile;
@VerifyField({
@VerifyParams(type = VerifyType.NOTNULL, errorMsg = "请填写固话号码!"),
@VerifyParams(type = VerifyType.TEL_PHONE, errorMsg = "固话号码格式输入不正确!")
})
private String tel;
@VerifyParams(type = VerifyType.NUMBER_RANGE, minNumber = 0, maxNumber = 120, errorMsg = "您是神仙吗?")
private String age;
@VerifyField({
@VerifyParams(type = VerifyType.NOTNULL, notEmpty = true, errorMsg = "体重为空"),
@VerifyParams(type = VerifyType.NUMBER_00, errorMsg = "体重输入格式不正确"),
@VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, maxNumber = 200, errorMsg = "你该减肥了!!!"),
@VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, minNumber = 40, errorMsg = "你已经瘦成竹竿了!!!")
})
private String weight;
@VerifyField({
@VerifyParams(type = VerifyType.NOTNULL, errorMsg = "身高为空"),
@VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, maxNumber = 300, errorMsg = "姚明都没你高!!!"),
@VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, minNumber = 40, errorMsg = "建议您补补钙,多晒晒太阳!!!")
})
private String height;
@VerifyField({
// @VerifyParams(type = VerifyType.NOTNULL, errorMsg = "邮箱地址为空!"),
@VerifyParams(type = VerifyType.EMAIL, notNull = false, errorMsg = "邮箱地址错误!")
})
private String email;
@VerifyParams(type = VerifyType.NOTNULL, notNull = true, errorMsg = "您填填写您的爱好!")
private List<String> hobby;
public Person() {
}
public Person(String name, String mobile, String tel, String age, String weight, String height, String email, List<String> hobby) {
this.name = name;
this.mobile = mobile;
this.tel = tel;
this.age = age;
this.weight = weight;
this.height = height;
this.email = email;
this.hobby = hobby;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Bindable
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Bindable
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
@Bindable
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<String> getHobby() {
return hobby;
}
public void setHobby(List<String> hobby) {
this.hobby = hobby;
}
@Bindable
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
@Bindable
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Bindable
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", mobile='" + mobile + '\'' +
", tel='" + tel + '\'' +
", age='" + age + '\'' +
", weight='" + weight + '\'' +
", height='" + height + '\'' +
", email='" + email + '\'' +
", hobby=" + hobby +
'}';
}
}
但是实际测试你们会发现校验顺序是乱的,这是为什么?
这是因为Java编译器在编译过程中可能会对类的字段进行优化和重排序,
导致反射获取的字段顺序与源代码中的声明顺序不同
这样就会导致一个问题,在我们校验一个表单的时候无法按照顺序去校验,比如我上面图中的表单,他就会先校验age字段,但是我姓名也没校验啊怎么就跳过去了呢?当然我们有一个很简单的办法去解决这个问题,那就是:重排序Field
重新排序
思考:我们如何排序?希望以什么形式去排序?
答案:那肯定是我可以自定义顺序,而且醒目,并不是类文件的行数顺序
这样我们就需要新增一个注解:
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyFieldSort {
int value() default Integer.MAX_VALUE;
}
然后在VerifyEntity
中新增一个方法,用来开启关闭排序
boolean sort() default false;
排序方法就没啥好说的了
private static Field[] sortField(Field[] fields) {
// 使用自定义注解的值进行排序
Arrays.sort(fields, (f1, f2) -> {
int order1 = getFieldOrder(f1);
int order2 = getFieldOrder(f2);
return Integer.compare(order1, order2);
});
return fields;
}
private static int getFieldOrder(Field field) {
VerifyFieldSort verifyFieldSort = field.getAnnotation(VerifyFieldSort.class);
if (verifyFieldSort != null) {
return verifyFieldSort.value();
}
return Integer.MAX_VALUE; // 如果字段没有指定顺序,则将其放在最后
}
在获取clazz.getDeclaredFields()
后排个序
Field[] fields = clazz.getDeclaredFields();
if (fields.length == 0) {
return VerifyResult.ok();
}
if (validation.sort()) {
sortField(fields);
}
在实体类上添加注解
这样就可以按照我们的顺序来执行类
@VerifyEntity(sort = true)
public class Person extends BaseObservable {
@VerifyFieldSort(1)
private String name;
@VerifyFieldSort(2)
private String mobile;
}
好了完成了。
完整代码:
VerifyEntity
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface VerifyEntity {
boolean enable() default true;
boolean sort() default false;
}
VerifyField
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyField {
VerifyParams[] value();
}
VerifyFieldSort
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyFieldSort {
int value() default Integer.MAX_VALUE;
}
VerifyParams
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface VerifyParams {
VerifyType type();
/**
* 是否允许为空字符串、空集合、空map等
* @return 默认可以为空
*/
boolean notEmpty() default false;
String equalStr() default "";
boolean notNull() default false;
int minLength() default -1;
int maxLength() default -1;
double minNumber() default -Double.MIN_VALUE;
double maxNumber() default Double.MAX_VALUE;
/**
* 错误提示信息
* @return String
*/
String errorMsg() default "信息填写错误,请验证后重新输入!";
/**
* 正则表达式
* @return 正则表达式字符串
*/
String regex() default "";
}
VerifyResult
public class VerifyResult {
private boolean isSuccess;
private String errorMsg;
public VerifyResult() {
}
public VerifyResult(boolean isSuccess, String errorMsg) {
this.isSuccess = isSuccess;
this.errorMsg = errorMsg;
}
public boolean isOk() {
return this.isSuccess;
}
public static VerifyResult ok() {
return new VerifyResult(true, null);
}
public static VerifyResult ok(String errorMsg) {
return new VerifyResult(true, errorMsg);
}
public static VerifyResult fail(String errorMsg) {
return new VerifyResult(false, errorMsg);
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
@Override
public String toString() {
return "VerifyResult{" +
"isSuccess=" + isSuccess +
", errorMsg='" + errorMsg + '\'' +
'}';
}
}
VerifyType
public enum VerifyType {
/**
* 验证类型
*/
NOTNULL,
//与目标值是否相等
EQUALS,
//数字
NUMBER,
//整数
NUMBER_INTEGER,
//小数
NUMBER_DOUBLE,
//两位有效数字
NUMBER_00,
//email
EMAIL,
//手机号码、固话校验,不包括区号和固化
PHONE,
//手机号码校验,不包括区号和固化
MOBILE_PHONE,
//固话验,不包括区号和手机号码
TEL_PHONE,
//数字范围验证
NUMBER_RANGE,
//数字范围验证,包含最大最小值
NUMBER_RANGE_EQUAL,
//长度验证
LENGTH_RANGE,
//长度验证,包含最大最小值
LENGTH_RANGE_EQUAL,
//自定义正则
REGEX,
;
private String type;
public String getType() {
return type;
}
}
EntityValidator
public class EntityValidator {
private final static String TAG = EntityValidator.class.getSimpleName();
public static VerifyResult validate(Object entity) {
try {
Class<?> clazz = entity.getClass();
VerifyEntity validation = clazz.getAnnotation(VerifyEntity.class);
if (validation == null) {
return VerifyResult.ok();
}
if (!validation.enable()) {
return VerifyResult.ok();
}
// 无法保证获取的变量顺序与类文件中的声明顺序一致,是因为Java编译器在编译过程中可能会对类的字段进行优化和重排序,
// 导致反射获取的字段顺序与源代码中的声明顺序不同
// 排序只能另外自定义注解
Field[] fields = clazz.getDeclaredFields();
if (fields.length == 0) {
return VerifyResult.ok();
}
if (validation.sort()) {
sortField(fields);
}
for (Field field : fields) {
if (!field.isAnnotationPresent(VerifyParams.class) && !field.isAnnotationPresent(VerifyField.class)) {
continue;
}
field.setAccessible(true);
VerifyField validationField = field.getAnnotation(VerifyField.class);
VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
if (validationParam == null && validationField == null) {
return VerifyResult.ok();
}
VerifyParams[] verifyParamsList;
if (validationField == null) {
verifyParamsList = new VerifyParams[]{validationParam};
} else if (validationParam == null) {
verifyParamsList = validationField.value();
} else {
verifyParamsList = Arrays.copyOf(validationField.value(), validationField.value().length + 1);
verifyParamsList[verifyParamsList.length - 1] = validationParam;
}
for (VerifyParams params : verifyParamsList) {
VerifyType verifyType = params.type();
if (verifyType == null) {
return VerifyResult.ok();
}
//一切的验证都是基于不为空的情况下,所以先验证空
Object value = field.get(entity);
//当有VerifyType.NOTNULL、notNull为true时不管其他条件只要为空则返回错误
boolean isNullValue = (VerifyType.NOTNULL == verifyType || params.notNull()) && value == null;
if (isNullValue) {
return VerifyResult.fail(params.errorMsg());
}
//当不是NOTNULL但是又允许为空时,即判断某个条件时,有输入则判断,没有输入值则不判断
if (VerifyType.NOTNULL != verifyType && !params.notNull()) {
if (value == null) {
return VerifyResult.ok();
} else if (value instanceof Collection<?> collection && collection.size() == 0) {
return VerifyResult.ok();
} else if (value instanceof Map<?, ?> map && map.size() == 0) {
return VerifyResult.ok();
} else if (StringUtil.isEmpty(value)) {
return VerifyResult.ok();
}
}
//是否允许为空实现,空集合、空map等情况
if (params.notEmpty()) {
if (value instanceof Collection<?> collection && collection.size() == 0) {
return VerifyResult.fail(params.errorMsg());
} else if (value instanceof Map<?, ?> map && map.size() == 0) {
return VerifyResult.fail(params.errorMsg());
} else if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(params.errorMsg());
}
}
VerifyResult verifyResult = verifyParam(params, value);
if (!verifyResult.isOk()) {
return verifyResult;
}
}
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.e(TAG, "验证异常:" + e);
return VerifyResult.ok("验证结果发生异常,将自动跳过验证!!!");
}
return VerifyResult.ok();
}
private static Field[] sortField(Field[] fields) {
// 使用自定义注解的值进行排序
Arrays.sort(fields, (f1, f2) -> {
int order1 = getFieldOrder(f1);
int order2 = getFieldOrder(f2);
return Integer.compare(order1, order2);
});
return fields;
}
private static int getFieldOrder(Field field) {
VerifyFieldSort verifyFieldSort = field.getAnnotation(VerifyFieldSort.class);
if (verifyFieldSort != null) {
return verifyFieldSort.value();
}
return Integer.MAX_VALUE; // 如果字段没有指定顺序,则将其放在最后
}
private static VerifyResult verifyParam(VerifyParams validationParams, Object value) {
VerifyType verifyType = validationParams.type();
if (verifyType == VerifyType.EQUALS) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!value.equals(validationParams.equalStr())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isNumber(value.toString()) && !RegexUtils.isDouble(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER_INTEGER) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isInteger(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER_DOUBLE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isDouble(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER_00) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isDoubleTwoDecimals(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.EMAIL) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isEmail(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.PHONE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isPhone(value.toString()) && !RegexUtils.isMobile(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.MOBILE_PHONE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isMobile(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.TEL_PHONE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (!RegexUtils.isPhone(value.toString())) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER_RANGE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
double number = Double.parseDouble(value.toString());
if (number <= validationParams.minNumber() || number >= validationParams.maxNumber()) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.NUMBER_RANGE_EQUAL) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
double number = Double.parseDouble(value.toString());
if (number < validationParams.minNumber() || number > validationParams.maxNumber()) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.LENGTH_RANGE) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
return VerifyResult.ok();
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.minLength() < 0 && value.toString().length() >= validationParams.maxLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.maxLength() < 0 && value.toString().length() <= validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (value.toString().length() >= validationParams.maxLength() || value.toString().length() <= validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.LENGTH_RANGE_EQUAL) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
return VerifyResult.ok();
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.minLength() < 0 && value.toString().length() > validationParams.maxLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (validationParams.maxLength() < 0 && value.toString().length() < validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (value.toString().length() > validationParams.maxLength() || value.toString().length() < validationParams.minLength()) {
return VerifyResult.fail(validationParams.errorMsg());
}
} else if (verifyType == VerifyType.REGEX) {
if (value == null) {
return VerifyResult.fail(validationParams.errorMsg());
}
if (StringUtil.isEmpty(value)) {
return VerifyResult.fail(validationParams.errorMsg());
}
return RegexUtils.regular(value.toString(), validationParams.regex()) ?
VerifyResult.ok() : VerifyResult.fail(validationParams.errorMsg());
}
return VerifyResult.ok();
}
}
写在最后
代码未经大量测试,也没写太多复杂场景,大家可以自己根据思路修改,我应该讲的很清楚了