前言
在项目开发交互过程中,难免会遇到一些数据校验。以校验客户端发送数据的合法性,对于一些非空校验,我们也许可以使用@NonNull,@NotNull 等注解,可是对于一些常规的,如手机号,身份证等等的校验,我们就还要判断处理每个请求的参数的合法性。
但是合法性的判断是难以避免的,我们是否可以精简工作量、提高工作效率呢。
思考
我们或许应该从@NonNull @NotNull等其他注解那里受到些启发。
我们或许可以结合正则表达式及注解对某些通用数据进行验证。
注解可以设置参数,我们可以设置参数为校验规则,通过枚举列举出来,同时也应该允许用户自定义正则等校验。
我们知道,注解有三种类型
RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIME。
SOURCE主要用于编译之前,编译过程中会被丢弃如@Override注解。
CLASS主要用于编译,运行时会被丢弃。
RUNTIME在源码,编译,运行时始终会存在。
可以利用反射,拿到具有特定注解的bean,并处理。所以我们定义的注解应该是RUNTIME类型。同时声明注作用范围为FIELD及PARAMETER。
实践
定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@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的用途。
定义常用正则枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public enum RegexType { NONE, SPECIALCHAR, CHINESE, EMAIL, IP, NUMBER, NUMBERORNIL, PHONENUMBER, ID; }
|
列出几种常用枚举。非空,特殊字符,中文,邮箱,IP,数字等等
枚举规则
定义了枚举,要定义它们的具体对应的方法,以便后续调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
public class RegexUtils {
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); }
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); }
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(); }
public static boolean isNumber(String number) { if (null == number || "".equals(number)) return false; String regex = "[0-9]*"; return number.matches(regex); }
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); }
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); }
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); }
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); }
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 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类型的注解在程序运行时也会被保留,我们可以利用反射,拿到具体注解参数信息,进行相关处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
public class ValidateService {
public ValidateService() { super(); }
public static void valid(Object object) throws Exception{ Class<? extends Object> clazz=object.getClass(); Field[] fields=clazz.getDeclaredFields(); for(Field field:fields){ 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的反射机制。
以上。
今天就到这里啦,中秋节快乐~~