1. 程式人生 > >如何編寫一個自己的校驗框架

如何編寫一個自己的校驗框架

引數校驗是一個健壯的程式所必要的,那麼如何將引數校驗做到簡單,直觀。

假設有一個 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

接下來說說如何實現這樣的校驗框架

我的實現邏輯是這樣的

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

以上只是部分程式碼,個人文筆太差,表達不能,原始碼奉上,有興趣的可以看看。

最後奉上目錄圖

目錄

以及與hibernate-validtor框架的速度對比

對比

同樣的校驗下要比hibernate-validator快一半