基于注解的通用数据验证

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JavaZWT/article/details/82828587

前言

在项目开发交互过程中,难免会遇到一些数据校验。以校验客户端发送数据的合法性,对于一些非空校验,我们也许可以使用@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的反射机制。

以上。

今天就到这里啦,中秋节快乐~~

猜你喜欢

转载自blog.csdn.net/JavaZWT/article/details/82828587