1. 程式人生 > >基於註解的通用資料驗證

基於註解的通用資料驗證

前言

在專案開發互動過程中,難免會遇到一些資料校驗。以校驗客戶端傳送資料的合法性,對於一些非空校驗,我們也許可以使用@NonNull,@NotNull 等註解,可是對於一些常規的,如手機號,身份證等等的校驗,我們就還要判斷處理每個請求的引數的合法性。

但是合法性的判斷是難以避免的,我們是否可以精簡工作量、提高工作效率呢。

思考

我們或許應該從@NonNull @NotNull等其他註解那裡受到些啟發。

我們或許可以結合正則表示式及註解對某些通用資料進行驗證。

註解可以設定引數,我們可以設定引數為校驗規則,通過列舉列舉出來,同時也應該允許使用者自定義正則等校驗。

我們知道,註解有三種類型

RetentionPolicy.SOURCE

RetentionPolicy.CLASS

RetentionPolicy.RUNTIME。

SOURCE主要用於編譯之前,編譯過程中會被丟棄如@Override註解。

CLASS主要用於編譯,執行時會被丟棄。

RUNTIME在原始碼,編譯,執行時始終會存在。

可以利用反射,拿到具有特定註解的bean,並處理。所以我們定義的註解應該是RUNTIME型別。同時宣告注作用範圍為FIELD及PARAMETER。

實踐

定義註解

/**
 * 資料驗證註解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.
PARAMETER}) public @interface DataValid { //是否可以為空 boolean nullable() default false; //提供幾種常用的正則驗證 RegexType regexType() default RegexType.NONE; //自定義正則驗證 String regexExpression() default ""; //引數或者欄位描述 String description() default ""; }

定義如上註解,nullable用來校驗引數是否可空,預設不可以為空,false。

同時提供幾種通用的正則校驗,用列舉列出,如手機號碼校驗,身份證資訊校驗等等。

同時如果沒有規定的正則表示式,可以讓使用者自定義自己的正則表示式。

另增加描述欄位,用來說明這個paramer的用途。

定義常用正則列舉

/**
 * 正則型別列舉
 */
public enum RegexType {
	
	NONE,
	SPECIALCHAR,
	CHINESE,
	EMAIL,
	IP, 
	NUMBER,
	NUMBERORNIL,
	PHONENUMBER,
	ID;
	
}

列出幾種常用列舉。非空,特殊字元,中文,郵箱,IP,數字等等

列舉規則

定義了列舉,要定義它們的具體對應的方法,以便後續呼叫。

/**
 * 常用正則表示式
 */
public class RegexUtils {

	/**
	 * 判斷是否是正確的IP地址
	 *
	 * @param ip
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isIp(String ip) {
		if (null == ip || "".equals(ip))
			return false;
		String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
				+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
				+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
				+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
		return ip.matches(regex);
	}

	/**
	 * 判斷是否是正確的郵箱地址
	 *
	 * @param email
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isEmail(String email) {
		if (null == email || "".equals(email))
			return false;
		String regex = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
		return email.matches(regex);
	}

	/**
	 * 判斷是否含有中文,僅適合中國漢字,不包括標點
	 * @param text
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isChinese(String text) {
		if (null == text || "".equals(text))
			return false;
		Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
		Matcher m = p.matcher(text);
		return m.find();
	}

	/**
	 * 判斷是否正整數
	 *
	 * @param number
	 *            數字
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isNumber(String number) {
		if (null == number || "".equals(number))
			return false;
		String regex = "[0-9]*";
		return number.matches(regex);
	}

	/**
	 * 判斷是否正整數(可以為空)
	 *
	 * @param number
	 *            數字
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isNumberOrNil(String number) {
		if(null == number) return true;
		if ("".equals(number.trim())) return true;
		String regex = "[0-9]*";
		return number.matches(regex);
	}

	/**
	 * 判斷幾位小數(正數)
	 *
	 * @param decimal
	 *            數字
	 * @param count
	 *            小數位數
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isDecimal(String decimal, int count) {
		if (null == decimal || "".equals(decimal))
			return false;
		String regex = "^(-)?(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){" + count
				+ "})?$";
		return decimal.matches(regex);
	}

	/**
	 * 判斷是否是手機號碼
	 *
	 * @param phoneNumber
	 *            手機號碼
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isPhoneNumber(String phoneNumber) {
		if (null == phoneNumber || "".equals(phoneNumber))
			return false;
		String regex = "^1[3|4|5|6|7|8|9][0-9]\\d{8}$";
		return phoneNumber.matches(regex);
	}

	/**
	 * 判斷身份證號格式正確性
	 *
	 * @param ID
	 *        身份證號
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean isID(String ID) {
		if (null == ID || "".equals(ID))
			return false;
		String regex = "^(\\d{14}[0-9a-zA-Z])|(\\d{17}[0-9a-zA-Z])$";
		return ID.matches(regex);
	}

	/**
	 * 判斷是否含有特殊字元
	 *
	 * @param text
	 * @return boolean true,通過,false,沒通過
	 */
	public static boolean hasSpecialChar(String text) {
		if (null == text || "".equals(text))
			return false;
		if (text.replaceAll("[a-z]*[A-Z]*\\d*-*_*\\s*", "").length() == 0) {
			// 如果不包含特殊字元
			return true;
		}
		return false;
	}

	/**
	 * 適應CJK(中日韓)字符集,部分中日韓的字是一樣的
	 */
	public static boolean isChinese2(String strName) {
		char[] ch = strName.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			if (isChinese(c)) {
				return true;
			}
		}
		return false;
	}

	private static boolean isChinese(char c) {
		Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
		if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
				|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
				|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
				|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
				|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
				|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
				|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
			return true;
		}
		return false;
	}
}

實現及呼叫

基本資料都定義及處理好了,我們應該建立註解與方法之間的關聯,RUNTIME型別的註解在程式執行時也會被保留,我們可以利用反射,拿到具體註解引數資訊,進行相關處理。

/**
 * 註解解析Service
 */
public class ValidateService {
	
	/*private static DataValid dataValid;*/

	public ValidateService() {
		super();
	}

	/**
	 * 解析入口
	 * @param object
	 * @throws Exception
	 */
	public static void valid(Object object) throws  Exception{
		//獲取object的型別
		Class<? extends Object> clazz=object.getClass();
		//獲取該型別宣告的成員
		Field[] fields=clazz.getDeclaredFields();
		//遍歷屬性
		for(Field field:fields){
			//對於private私有化的成員變數,通過setAccessible來修改器訪問許可權
			field.setAccessible(true);
			validate(field,object);
			//重新設定會私有許可權
			field.setAccessible(false);
		}
	}
	
	
	public static void validate(Field field,Object object) throws  Exception{

		String  description = null;
		Object        value = null;
		DataValid dataValid = null;

		//獲取物件的成員的註解資訊
		dataValid=field.getAnnotation(DataValid.class);
		value=field.get(object);
		
		if(dataValid==null)return;
		
		description=dataValid.description().equals("")?field.getName():dataValid.description();
		
		/*************註解解析工作開始******************/
		if(!dataValid.nullable() && dataValid.regexType() != RegexType.NUMBERORNIL){
			if(value==null|| StringUtils.isBlank(value.toString())){
				throw new Exception(description+"不能為空");
			}
		}
		
		if(dataValid.regexType()!=RegexType.NONE){
			switch (dataValid.regexType()) {
				case NONE:
					break;
				case SPECIALCHAR:
					if(RegexUtils.hasSpecialChar(value.toString())){
						throw new Exception(description+"不能含有特殊字元");
					}
					break;
				case CHINESE:
					if(RegexUtils.isChinese2(value.toString())){
						throw new Exception(description+"不能含有中文字元");
					}
					break;
				case EMAIL:
					if(!RegexUtils.isEmail(value.toString())){
						throw new Exception(description+"郵箱地址格式不正確");
					}
					break;
				case IP:
					if(!RegexUtils.isIp(value.toString())){
						throw new Exception(description+"IP地址格式不正確");
					}
					break;
				case NUMBER:
					if(!RegexUtils.isNumber(value.toString())){
						throw new Exception(description+"不是數字");
					}
					break;
				case NUMBERORNIL:
					if(value == null){
						break;
					}
					if(!RegexUtils.isNumberOrNil(value.toString())){
						throw new Exception(description+"格式不正確");
					}
					break;					
				case PHONENUMBER:
					if(!RegexUtils.isPhoneNumber(value.toString())){
						throw new Exception("手機號格式不正確");
					}
					break;
				case ID:
					if(!RegexUtils.isID(value.toString())){
						throw new Exception("身份證號格式不正確");
					}
					break;					
				default:
					break;
			}
		}
		
		if(!dataValid.regexExpression().equals("")){
			if(value.toString().matches(dataValid.regexExpression())){
				throw new Exception(description+"格式不正確");
			}
		}
		/*************註解解析工作結束******************/
	}
}

如上程式碼。

當然,到具體業務層,應該呼叫這個Service的valid方法去校驗引數。

結論

可以看到,經過這樣,我們可以把一些常用的校驗通過這種方式封裝,大大簡化程式碼量,使業務層更注重業務。

這種也可以新增自己的通用型別,靈活性很強。

這個小小的簡單工具最主要的就是利用了Java的反射機制。

以上。

今天就到這裡啦,中秋節快樂~~