如何编写一个自己的校验框架

参数校验是一个健壮的程序所必要的,那么如何将参数校验做到简单,直观。

假设有一个 User 对象,我们需要对 用户名密码 进行非null校验

public class User {

        private String uname;

        private String passwd;
    }

普通的校验逻辑编写

if (null != user.getUname() && null != user.getPasswd())
    return true;
return false;

这段代码确实可以正常运行,并且符合我们的需求,但是这段代码读起来却不是那么美观,于是我做们做出下面的修改

return (null != user.getUname() && null != user.getPasswd());

修改之后的代码虽然更加简短,但同样不怎么美观



最后我们将代码改成下面这种形式,只需要在校验的属性上加上注解,最后一个Validator就可以实现我们想要的校验逻辑,既美观,又直观

public class User {

        @NotNull
        private String uname;

        @NotNull
        private String passwd;
    }

    public static void main( String[] args ) {
        User user = new User();
        Validator validator = Validator.newInstance(user);
        validator.validate();
    }



接下来说说如何实现这样的校验框架

我的实现逻辑是这样的

  1. 提供一个抽象类,包含抽象方法validate、init,用于编写校验逻辑和初始化操作
public abstract class AbstractValidate<T> {
    //注解
    public T annotation;
    //错误提示
    private String msg;

    /**
     * author: 一线生
     * explain: 校验逻辑方法
     * @param object 待校验的参数属性
     * date 2016/5/25 - 11:49
     **/
    public abstract boolean validate(Object object);

    /**
     * author: 一线生
     * explain: 初始化方法,所有的校验子类在继承该抽象类后需要编写init方法设置错误提示消息或者其他自定义操作
     * date 2016/5/25 - 11:49
     **/
    public abstract void init();

    /**
     * author: 一线生
     * explain: 设置默认校验不通过提示信息
     * @param annotation 校验注解
     * @throws  MedusaException 当注解没有 value方法时,抛出异常
     * date 2016/5/26 - 10:44
     **/
    private void enabledDefaultMsg(Annotation annotation) {
        Class<? extends Annotation> clazz = AnnotationHelper.choice(annotation);
        try {
            setMsg(String.valueOf(clazz.getMethod("value").getDefaultValue()));
        } catch (NoSuchMethodException e) {
            throw new MedusaException(e);
        }
    }

    /**
     * author: 一线生
     * explain: 设置校验不通过提示信息
     * @param msg 提示内容
     * date 2016/5/26 - 10:26
     **/
    public void setMsg(String msg) {
        this.msg = msg;
    }

    /**
     * author: 一线生
     * explain: 获取校验返回值 {@link Medusa}
     * @param entity {@link Entity} 该参数包含属性的注解,属性Field对象,以及属性的值
     * date 2016/5/25 - 11:50
     **/
    public Medusa result(Entity entity) {
        //设置annotation对象为属性的校验注解
        annotation = (T) entity.getAnnotation();
        //设置默认提示信息
        enabledDefaultMsg(entity.getAnnotation());
        //执行初始化方法
        init();
        //执行校验逻辑
        boolean flag = validate(entity.getValue());
        //返回校验结果
        return new Medusa(flag, entity.getField(), flag ? Message.ALLOW : this.msg);
    }
}

2 - 创建基本校验的注解 如 @NotNull、@Null、@NotEmpty 等

这些注解都包含两个默认值

Class<?> clazz() default NotNullValidate.class;

String value() default Message.NOT_NULL;

clazz 为该注解校验逻辑实现的类

value为默认错误提示

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    Class<?> clazz() default NotNullValidate.class;
    String value() default Message.NOT_NULL;
}

3 - Validator 校验时,通过反射找到注解clazz默认的java类,执行该类的validate方法,

结果正确则标记为true,错误则标记为false并将默认错误提示放入校验结果集

提供result(Object object) 方法提供返回校验结果集

public class Validator {

    private Object object;

    public static Validator newInstance() {
        return new Validator();
    }

    public static Validator newInstance(Object object) {
        return new Validator(object);
    }

    private Validator() {}

    private Validator(Object object) {
        this.object = object;
    }

    /**
     * author: 一线生
     * explain: 校验方法,该方法校验整个对象的所有注解,然后返回true/false
     * @param object 待校验的对象
     * date 2016/5/25 - 11:52
     **/
    public boolean validate(Object object) {
        Set<Medusa> medusaSet = result(object);
        for (Medusa medusa : medusaSet) {
            if (!medusa.isFlag()) return false;
        }
        return true;
    }

    /**
     * author: 一线生
     * explain: 校验方法,该方法校验整个对象的所有注解,然后返回true/false
     *      该方法无参,使用{@link Validator#newInstance(Object)} 方法,可以直接调用此方法得到校验结果
     * date 2016/5/25 - 11:52
     **/
    public boolean validate() {
        return validate(this.object);
    }

    /**
     * author: 一线生
     * explain: 弹出第一个校验结果
     *     该方法无参,使用{@link Validator#newInstance(Object)} 方法,可以直接调用此方法得到校验结果
     * date 2016/5/25 - 11:53
     **/
    public Medusa pop() {
        return pop(this.object);
    }

    /**
     * author: 一线生
     * explain: 弹出第一个校验结果
     * @param object 待校验的对象
     * date 2016/5/25 - 11:53
     **/
    public Medusa pop(Object object) {
        Set<Medusa> medusaSet = result(object);
        return medusaSet.iterator().next();
    }

    /**
     * author: 一线生
     * explain: 弹出第一个错误的校验结果
     * @param object 待校验的对象
     * date 2016/5/25 - 11:53
     **/
    public Medusa popDeny(Object object) {
        Set<Medusa> medusaSet = result(object);
        for (Medusa medusa : medusaSet) {
            if (!medusa.isFlag()) return medusa;
        }
        return null;
    }

    /**
     * author: 一线生
     * explain: 弹出第一个错误的校验结果
     *      该方法无参,使用{@link Validator#newInstance(Object)} 方法,可以直接调用此方法得到校验结果
     * date 2016/5/25 - 11:53
     **/
    public Medusa popDeny() {
        return popDeny(this.object);
    }

    /**
     * author: 一线生
     * explain: 获取校验返回值 该方法校验所有的注解,并返回校验结果Set<Medusa>
     *     该方法无参,使用{@link Validator#newInstance(Object)} 方法,可以直接调用此方法得到校验结果
     * date 2016/5/25 - 11:54
     **/
    public Set<Medusa> result() {
        return result(this.object);
    }

    /**
     * author: 一线生
     * explain: 获取校验返回值 该方法校验所有的注解,并返回校验结果Set<Medusa>
     *     该方法需要校验对象作为参数,使用{@link Validator#newInstance()} 方法,可以直接调用此方法传入校验对象得到校验结果
     * @param object 待校验的对象
     * date 2016/5/25 - 11:54
     **/
    public Set<Medusa> result(Object object) {
        try {
            //获取待校验对象的注解和Field,Set集合
            Set<Entity> entitySet = ReflectUtils.getSet(object);
            //构建一个空的HashSet,用于存放校验返回结果
            Set<Medusa> medusaSet = new HashSet<Medusa>();
            //循环校验所有field
            for (Entity entity : entitySet) {
                //获取注解
                Annotation annotation = entity.getAnnotation();
                //获取注解的class
                Class<? extends Annotation> clazz = AnnotationHelper.choice(annotation);
                //执行注解clazz方法指向的class.result方法
                Object[] params = {entity};
                Object result = ReflectUtils.invokeMethod((Class<?>) clazz.getMethod("clazz").getDefaultValue(), "result", params);
                //将校验结果放入set
                medusaSet.add((Medusa) result);
            }
            return medusaSet;

        } catch (Exception e) {
            throw new MedusaException(e);
        }
    }

4 - 所有的校验类都需要继承抽象类AbstractValidate,并编写validate逻辑,以及初始化操作

public class NotNullValidate extends AbstractValidate<NotNull> {

    public boolean validate(Object object) {
        return null != object;
    }

    public void init() {
        this.setMsg(annotation.value());
    }
}

5 - 可扩展性,虽然实现了基本的校验注解,但是正常开发中可能会需要自定义的业务注解,为了可以编写自己的业务注解, 只需要创建自己的注解,将clazz指向自己的校验类,并将校验类继承抽象类 AbstractValidate,编写校验逻辑,这里提供了一个AnnotationHelper类,该类会加载指定包下的注解,这个地方可以做成可配置的,实现了可以允许开发者自定义注解。

public class AnnotationHelper {
    //BASE_PACKAGE 包下的所有class集合
    private static Set<Class<?>> ANNOTATION_SET;
    //初始化要加载的注解包路径
    private final static String BASE_PACKAGE = "org.yxs.medusa.annotation";
    //注解Class 对应 Annotation的map集合
    private static Map<Annotation, Class<? extends Annotation>> ANNOTATION_CLASS_SET = new HashMap<Annotation, Class<? extends Annotation>>();

    static {
        loadAnnotation(BASE_PACKAGE);
    }

    /**
     * author: 一线生
     * explain: 初始化加载所有基础校验注解class
     * @param packageName 要加载的包路径
     * date 2016/5/25 - 11:45
     **/
    private static void loadAnnotation(String packageName) {
        ANNOTATION_SET = ClassUtil.getClassSet(packageName);
    }

    /**
     * author: 一线生
     * explain: 选择属性的校验类型(在基础包下的所有注解)注解
     * @param field 对象属性
     * date 2016/5/25 - 11:46
     **/
    public static Annotation choice(Field field) {
        for (Class<?> clazz : ANNOTATION_SET) {
            Class<? extends Annotation> castClazz = (Class<? extends Annotation>) clazz;
            if (field.isAnnotationPresent(castClazz)) {
                Annotation annotation = field.getAnnotation(castClazz);
                ANNOTATION_CLASS_SET.put(annotation, castClazz);
                return annotation;
            }
        }
        return null;
    }

    /**
     * author: 一线生
     * explain: 根据注解Annotation获取该注解的class 用于反射执行方法
     * @param annotation 注解
     * date 2016/5/25 - 11:47
     **/
    public static Class<? extends Annotation> choice(Annotation annotation) {
        return ANNOTATION_CLASS_SET.get(annotation);
    }
}


以上只是部分代码,个人文笔太差,表达不能,源码奉上,有兴趣的可以看看。

请用力戳我 https://github.com/smallcham/medusa.git

最后奉上目录图

以及与hibernate-validtor框架的速度对比

同样的校验下要比hibernate-validator快一半

时间: 2024-08-01 22:32:40

如何编写一个自己的校验框架的相关文章

Python学习 - 编写一个简单的web框架(二)

在上一篇日志中已经讨论和实现了根据url执行相应应用,在我阅读了bottle.py官方文档后,按照bottle的设计重写一遍,主要借鉴大牛们的设计思想. 一个bottle.py的简单实例 来看看bottle是如何使用的,代码来自http://www.bottlepy.org/docs/0.12/index.html: from bottle import route, run, template @route('/hello/<name>') def index(name): return t

Python学习 - 编写一个简单的web框架(一)

自己动手写一个web框架,因为我是菜鸟,对于python的一些内建函数不是清楚,所以在写这篇文章之前需要一些python和WSGI的预备知识,这是一系列文章.这一篇只实现了如何处理url. 参考这篇文章:http://www.cnblogs.com/russellluo/p/3338616.html 预备知识 web框架主要是实现web服务器和web应用之间的交互.底层的网络协议主要有web服务器完成.譬如监听端口,填充报文等等. Python内建函数__iter__和__call__和WSGI

使用js编写一个简单的运动框架

下班后,,没事捣鼓捣鼓个人的小爱好. 首先,说明我的这个运动框架(css所有属性)也是常见的框架一种,健壮性并不是太好,对于新手学习倒是挺好,,若是大神,老司机请拐弯. 上来,我们先定义一个区块,然后在关联一个可以实时看到属性值发生变化值的标签. html: <body> <div id = "div1"> </div> <input type = "text" id = "txt1"> </

简单的前端校验框架实现

前端表单在提交前总是要进行校验的,而这些工作是繁琐的,所以这儿写了一个简单的校验框架,代码实现如下: function Validate(id){ if(arguments.length != 1){ throw new Error("Argument is required"); } this.errors = []; this.id = id; this.container = $("#" + id); } Validate.prototype = { cons

基于OpenGL编写一个简易的2D渲染框架01——创建窗口

最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的是 vs2013,使用C++语言编写项目.这个小项目叫Simple2D,意味着简易的2D框架.最终的目的是可以渲染几何图形和图片,最后尝试加上一个2D粒子系统和Box2D物理引擎,并编译一个简单的游戏. 第一步,就是创建一个Win32项目. 接下来,生成一个窗口.编写一个RenderWindow类,

如何编写一个企业级Hyperledger Fabric开源框架

Convector(a.k.a Convector Smart Contracts)是为企业区块链框架构建的JavaScript开发框架.它增强了开发体验,同时帮助开发人员创建更强大,更安全的智能合约系统.它通过链代码和后端一直到前端,允许开发人员以库的形式重用相同的代码库.它基于模型/控制器模式,支持Hyperledger Fabric,并沿着Fabric精心设计的模式本地运行. 这篇博客文章介绍了该项目的历史,并重点介绍了沿途开发的挑战和解决方案. 当我们开始研究Tellus时,一切都开始了

Mockito一个采用Java编写用于单元测试的Mocking框架

參考:https://github.com/mockito/mockito Mockito一个采用Java编写用于单元测试的Mocking框架 https://www.ctolib.com/mockito.html 原文地址:https://www.cnblogs.com/highpointengineer/p/10977004.html

手把手写一个基于Spring Boot框架下的参数校验组件

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303) 前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验. 遇到的问题                    在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{co

Java集合框架上机练习题:编写一个Book类,该类至少有name和price两个属性。该类要实现Comparable接口,在接口的compareTo()方法.....

编写一个Book类,该类至少有name和price两个属性.该类要实现Comparable接口,在接口的compareTo()方法中规定两个Book类实例的大小关系为二者的price属性的大小关系.在主函数中,选择合适的集合类型存放Book类的若干个对象,然后创建一个新的Book类的对象,并检查该对象与集合中的哪些对象相等. package javajihe; public class Book implements Comparable{ String name; float price; B