Android自定义注解实现一键校验实体类参数

news2024/11/25 22:57:12

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();
    }
}

写在最后

代码未经大量测试,也没写太多复杂场景,大家可以自己根据思路修改,我应该讲的很清楚了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/980342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小红书种草推广步骤是怎样的,小红书种草效果好吗?

小红书作为一个以美妆、时尚和生活为主题的社交电商平台&#xff0c;引起了广大用户的关注。作为品牌或商家&#xff0c;通过在小红书上进行种草推广能够有效提升品牌曝光度并吸引潜在客户。小红书的种草推广步骤是怎样的&#xff1f;种草推广效果又如何呢&#xff1f;下面伯乐…

OpenCV(二十四):可分离滤波

目录 1.可分离滤波的原理 2.可分离滤波函数sepFilter2D() 3.示例代码 1.可分离滤波的原理 可分离滤波的原理基于滤波器的可分离性。对于一个二维滤波器&#xff0c;如果它可以表示为水平方向和垂直方向两个一维滤波器的卷积&#xff0c;那么它就是可分离的。也就是说&#x…

jmeter测试

装java环境配置环境变量 装jmeter 设置中文

《存储IO路径》专题:不同IO调度器的差异

在计算机世界中&#xff0c;有一个神秘的王国&#xff0c;叫做IO王国。这个王国里有四种奇怪的生物&#xff0c;它们分别是Noop调度器、Anticipatory调度器、Deadline调度器和CFQ调度器。IO调度器负责管理计算机中的IO请求&#xff0c;确保它们有序地通过。就像一个交警会根据车…

3.3 【MySQL】字符集和比较规则的应用

3.3.1 各级别的字符集和比较规则 MySQL 有4个级别的字符集和比较规则&#xff0c;分别是&#xff1a; 服务器级别 数据库级别 表级别 列级别 3.3.1.1 服务器级别 MySQL 提供了两个系统变量来表示服务器级别的字符集和比较规则&#xff1a; 系统变量 描述 character_se…

VIRTIO-BLK代码分析(4)VIRTIO设备中断的注入

VIRTIO设备中断包括vq中断和config中断。Vq中断用于通知Guest已完成数据的处理&#xff0c;虚拟机中可以完成IO请求。Vq中断的模拟是通过irqfd实现的&#xff0c;它将虚拟机中断与irqfd关联上&#xff08;KVM_SET_GSI_ROUTING和KVM_IRQFD&#xff09;&#xff0c;当虚拟机中访问…

Centos7设置设置时间与windows同步

CentOS7设置时间与windows同步 使用ntp使CentOS服务器的系统时间与机器时间同步于windows服务器centOS机器安装ntpwindows安装ntp同步时间 使用ntp使CentOS服务器的系统时间与机器时间同步于windows服务器 思路是将windows服务器作为ntp服务端&#xff0c;centOS服务器作为ntp…

border-image和border-radius一起使用border-radius不起作用

关于border-image和border-radius一起使用border-radius不起作用 解决方案&#xff1a;最外层父盒子假如叫A添加 background为border-image的值&#xff0c;以及添加 boder-radius。 再添加一层父盒子假如叫B 添加padding值为border的宽度。 C和D都是子盒子 单独设置其border-…

展厅显示大屏怎么选,展厅使用哪种显示大屏好?

近年来&#xff0c;随着信息化科技快速发展&#xff0c;越来越多的企事业单位的展厅都会采用显示大屏来展现各种信息。那么&#xff0c;展厅显示大屏怎么选&#xff0c;哪种显示大屏更好&#xff1f;接下来&#xff0c;小灰从专业角度&#xff0c;用通俗易懂的文字为大家逐一分…

视频集中存储/云存储/磁盘阵列EasyCVR平台分组批量绑定/取消设备功能详解

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台视频能力丰富灵活&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。视频汇聚融合管理平台EasyCVR既具备传…

打造低碳社区,践行环保理念

在城市内推行低碳经济&#xff0c;实现城市的低碳排放&#xff0c;甚至是零碳排放。而社区的结构是城市结构的细胞&#xff0c;社区结构与密度对城市能源及二氧化碳排放起了关键的作用。所以要想实现低碳城市建设&#xff0c;改变原有能源结构&#xff0c;改造低碳社区是第一大…

第 2 章 线性表 (线性表的单链表存储结构实现)

1. 背景说明 2. 示例代码 1) status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H/* 函数结果状态码 */ #define TRUE 1 /* 返回值为真 */ #define FALSE 0 /* 返回值为假 */ #define RET_OK 0 /* 返回值正确 */ #d…

武汉凯迪正大—接触电阻测试仪器

一、凯迪正大智能回路电阻测试仪产品概述 KDHL-100A操作面板采用人体工学设计&#xff0c;符合操作习惯&#xff0c;采用高频开关电源和数字电路技术&#xff0c;适用于开关控制设备回路电阻的测量。测试电流采用国家标准推荐的直流100A。可在直流100A的情况下直接测得回路电阻…

图像色彩空间的改变

图像色彩空间的改变 OpenCV中有150多种颜鱼空间转换方法。 最广泛使用的转换方法有两种&#xff0c; BGR →Gray 和 BGR→HSV。 cv.cvtColor(input_image&#xff0c;flag) 参数: input_image:进行颜色空间转换的图像. flag:转换类型 cv.COLOR_BGR2GRAY : BGR → GRAY cv.…

恒运资本:沪指震荡跌0.34%,医药、酿酒等板块走低,光刻胶概念等活跃

6日早盘&#xff0c;两市股指再度走低&#xff0c;创业板指跌近1%&#xff1b;成交额有所下降&#xff0c;北向资金小幅净流出。 到午间收盘&#xff0c;沪指跌0.34%报3143.62点&#xff0c;深成指跌0.68%&#xff0c;创业板指跌0.89%&#xff0c;上证50指数跌0.51%&#xff1…

运行Android Automotive模拟器

在windows系统中安装MobaXterm MobaXterm free Xserver and tabbed SSH client for Windows 运行MobaXterm&#xff0c;在宿主机中进入编译后的源码根目录并执行如下命令&#xff0c;若未编译&#xff0c;请参照如下链接&#xff0c;编译车机模拟器Android Automotive编译_IT…

Linux之DNS域名解析服务

目录 Linux之DNS域名解析服务 概述 产生原因 作用 连接方式 因特网的域名结构 拓扑 分类 域名服务器类型 ​编辑 DNS域名解析过程 分类 解析图 搭建DNS域名解析服务器 概述 安装软件 bind服务中三个关键文件 主配置文件分析 一般需要修改三部分&#xff1a;…

ubuntu 20.04 设置 authorized_keys 让 VS Code ssh 远程免密连接

相关文章 VSCode SSH 连接远程ubuntu Linux 主机 前言 前面记录了 VS Code 可以通过 SSH 远程连接 ubuntu Linux 主机&#xff0c;比如代码放在远程 ubuntu 主机上&#xff0c; windows 端 VS Code 通过 ssh 远程连接 ubuntu&#xff0c;并打开 远程主机上的 代码 如果不设置…

手游折扣平台app排行,打折手游平台排行

随着手游市场的不断发展&#xff0c;出现了越来越多的手游折扣平台。在这些平台中&#xff0c;有些提供各种各样的手机游戏&#xff0c;并提供丰厚的福利。本文将向您介绍手游折扣平台app排行&#xff0c;打折手游平台排行。对于目前的游戏来说&#xff0c;无非是哪里玩更划算&…

Eclipse安装及配置tomcat

1.Eclipse安装 1.java -version检查一下有没有jdk 如没有则下载 下载路径&#xff1a;https://www.oracle.com/java/technologies/javase-jdk8-downloads.html 2.Eclipse下载链接&#xff1a;https://www.eclipse.org/downloads/ 3.安装完之后就启动你会看见一个欢迎页面&am…