如何編寫一個自己的校驗框架
引數校驗是一個健壯的程式所必要的,那麼如何將引數校驗做到簡單,直觀。
假設有一個 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();
}
接下來說說如何實現這樣的校驗框架
我的實現邏輯是這樣的
- 提供一個抽象類,包含抽象方法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快一半