自定义校验型注解

时间:2021-11-27 11:56:58

        在开发过程中,我们经常调用别人的接口,或者发布接口被别人调用,在这过程中肯定会涉及到很多的参数,ip地址,电话号码,字符是否可空,字符的最小长度,字符的最长长度,电话号码,中文,特殊字符串等等,那怎么让参数校验更加的方便呢,我们可以自定义一个注解来做这些事情,通过注解,我们就不需要再写多余的if else语句了,节省我们开发的工作量,提高开发效率,下面我们看看使用方式。

使用方式预览

继承BaseDto,继承了BaseDto可以让Dto类加上注解就拥有属性校验的功能,当Dto类实例化完成之后调用validate(父类的方法)方法就可以知道自己的属性值有没有按照规则初始化,例如:
public class Person extends BaseDto{
	@Validate(regexType=RegexType.EMAIL)
	private String email;
	@Validate(maxLength=10,minLength=10)
	private String name;
	@Validate(regexType=RegexType.PHONENUMBER)
	private String  phone;
	@Validate(nullable=false)
	private String  address;
       //get set 方法
}

该类初始化完成,设置各个属性值之后,便可以调用validate方法,看看email是否符合邮件格式,name的长度是否为10位,phone是不是电话号码,address是不是不为空。
如果其中有意向不符合要求,validate则返回校验出错的信息,如果校验通过,validate方法则返回null。

下面我们具体来看实现方式。

工具类

定义注解

package utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/** 
 * @author Fighter168
 * @date 2017-03-17
 * @description:校验注解类
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface Validate {
//是否可以为空
boolean nullable() default false;
//最大长度
int maxLength() default 0;
//最小长度
int minLength() default 0;
//提供几种常用的正则验证
RegexType regexType() default RegexType.NONE;
//自定义正则验证
String regexExpression() default "";
//参数或者字段描述,这样能够显示友好的异常信息
String description() default "";
}

定义正则枚举

package utils;

/** 
 * @author Fighter168
 * @date 2017-03-17
 * @description: 校验枚举类型
 */
public enum RegexType {
NONE,
SPECIALCHAR,
CHINESE,
EMAIL,
IP, 
NUMBER,
NO_SPECIALCHAR,
PHONENUMBER
}

定义正则工具类

package utils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;



/**
 * @author Fighter168
 * @date 2017-03-17
 * @description:TODO
 */

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();
	}
	/***
	 * 中文当作3个字符
	 * @description 
	 * @param text
	 * @return  
	 * @author lyg
	 * @date 2016-7-13
	 */
	public static int strLen(String text) {
		if (null == text || "".equals(text))
			return 0;
		int len=0;
		char[] ch = text.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			if (isChinese(c)) {
				len++;
			}
		}
		return len*2+text.length();
	}
	/***
	 * 字符串截取,1个中文当3个字符
	 * @description 
	 * @param text
	 * @param len
	 * @return  
	 * @author lyg
	 * @date 2016-7-13
	 */
	public static String subStr(String text,int len)
	{
		if (null == text || "".equals(text))
			return "";
		StringBuilder result=new StringBuilder();
		int index=0;
		char[] ch = text.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			if (isChinese(c)) {
				index+=3;
			}
			else {
				index++;
			}
			if(len<index)
				break;
			result.append(c);
		}
		return result.toString();
	}
	/***
	 * 字符串截取,1个中文当3个字符是否补省略号
	 * @description 
	 * @param text
	 * @param len
	 * @param ellipsis
	 * @return  
	 * @author lyg
	 * @date 2016-8-3
	 */
	public static String subStr(String text,int len,boolean ellipsis)
	{
		if (null == text || "".equals(text))
			return "";
		if(ellipsis)
		{
			len=len-3;
		}
		StringBuilder result=new StringBuilder();
		int index=0;
		char[] ch = text.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			if (isChinese(c)) {
				index+=3;
			}
			else {
				index++;
			}
			if(len<index)
				break;
			result.append(c);
		}
		if(ellipsis)
		{
			result.append("...");
		}
		return result.toString();
	}
	/**
	 * 判断是否正整数
	 * 
	 * @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 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|7|5|8][0-9]\\d{8}$";
		return phoneNumber.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;
	}
	public static void main(String[] args) {
		System.out.println(strLen("我是中文123,"));
		System.out.println(subStr("我1是中文123,",5));
	}

	/**
	 * 适应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;
	}

定义DTO基类

package common;

import java.io.Serializable;
import java.lang.reflect.Field;

import javax.xml.bind.ValidationException;

import utils.RegexType;
import utils.RegexUtils;
import utils.Validate;

/**
 * @author Fighter168
 * @date 2017-03-17
 * @description:DTO基类
 */

public abstract class BaseDto implements Serializable {

	private static final long serialVersionUID = 1L;
 
	public String validate(){
		try {
			valid(this);
		} catch (ValidationException e) {
			return e.getMessage();
		}
		return null;
	}


	public static void valid(Object object) throws ValidationException {
		// 获取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 ValidationException {
		String description;
		Object value=null;
		// 获取对象的成员的注解信息
		Validate dv = field.getAnnotation(Validate.class);
		try {
			value = field.get(object);
		} catch (Exception e) {
			throw new ValidationException("解析错误");
		}
		if (dv == null)
			return ;
		description = dv.description().equals("") ? field.getName() : dv
				.description();
		/************* 注解解析工作开始 ******************/
		if (!dv.nullable()) {
			if (value == null || "".equals(value.toString())) {
				throw new ValidationException(description + "不能为空");
			}
		}
		if (value != null) {
			if (value.toString().length() > dv.maxLength() && dv.maxLength() != 0) {
				throw new ValidationException(description + "长度不能超过" + dv.maxLength());
			}
			if (value.toString().length() < dv.minLength() && dv.minLength() != 0) {
				throw new ValidationException(description + "长度不能小于" + dv.minLength());
			}
			if (dv.regexType() != RegexType.NONE) {
				switch (dv.regexType()) {
				case NONE:
					break;
				case SPECIALCHAR:
					if (RegexUtils.hasSpecialChar(value.toString())) {
						throw new ValidationException(description + "不能含有特殊字符");
					}
					break;
				case CHINESE:
					if (RegexUtils.isChinese2(value.toString())) {
						throw new ValidationException(description + "不能含有中文字符");
					}
					break;
				case EMAIL:
					if (!RegexUtils.isEmail(value.toString())) {
						throw new ValidationException(description + "地址格式不正确");
					}
					break;
				case IP:
					if (!RegexUtils.isIp(value.toString())) {
						throw new ValidationException(description + "地址格式不正确");
					}
					break;
				case NUMBER:
					if (!RegexUtils.isNumber(value.toString())) {
						throw new ValidationException(description + "不是数字");
					}
					break;
				case PHONENUMBER:
					if (!RegexUtils.isPhoneNumber(value.toString())) {
						throw new ValidationException(description + "不是数字");
					}
					break;
				default:
					break;
				}
			}
			if (!dv.regexExpression().equals("")) {
				if (value.toString().matches(dv.regexExpression())) {
					throw new ValidationException(description + "格式不正确");
				}
			}
		}
		
		/************* 注解解析工作结束 ******************/
	}
	
	
	@Override
	public String  toString() {
		Class<?> c=this.getClass();
		StringBuilder sbuilder=new StringBuilder();
		Field[] fields=c.getDeclaredFields();
		sbuilder.append(c.getName()+"[");
		for(int i=0;i<fields.length; i++){
			fields[i].setAccessible(true);
			fields[i].getName();
			try {
				if(i == fields.length-1 ){
					sbuilder.append(fields[i].getName()+":"+fields[i].get(this).toString());
				}else{
					sbuilder.append(fields[i].getName()+":"+fields[i].get(this).toString()+",");
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		sbuilder.append("]");
		return sbuilder.toString();
	}
	 


编写测试

编写Person类

Person类继承 了BaseDto 就拥有了属性格式校验的功能。
package dto;

import common.BaseDto;
import utils.RegexType;
import utils.Validate;
 
 
/**
 * @author Fighter168
 * @date 2017-03-17
 * @description:Person类
 */
public class Person extends BaseDto{@Validate(regexType=RegexType.EMAIL)private String email;@Validate(maxLength=10,minLength=10)private String name;@Validate(regexType=RegexType.PHONENUMBER)private String phone; //get set方法...}
 
 

编写测试类

我们上面的Person的定义了三个属性,三个校验,如果我们把email格式写错,那么调用validate就会返回结果,告诉用户校验发生的错误信息,如下:


自定义校验型注解