萌生写这个代码的原因,是在使用struts2的验证框架时,总觉的有些不太灵活。当一个action中有多个表单需要处理的时候,struts处理起来就稍微麻烦一点,当校验失败时都会return "input"字符串。但我不同表单单校验失败后一般都希望返回不同的result,渲染不同的视图。另外如果我的校验是异步发起的,而我想要的结果是json的时候,也比较麻烦。虽然这些问题可以通过修改result type类型和在result中使用ognl表达式来做到,但绕来绕去实在太过麻烦。而若不适用这校验框架,在action中自己来if else if else 来判断更不可取。于是便回想到yii和 laravel框架的校验,这些校验框架在我看来做的非常优秀,非常灵活,于是我想借鉴它们来自己写一写这个校验插件。
首先,我希望做到两点
另外当实体的属性没有被struts填充时,也可以使用我写的一个fill方法进行填充,为什么要写这个呢,因为struts的属性驱动, 类驱动,模型驱动都有点局限,但我的action里有多个模型,模型驱动只能为一个模型注值,不够灵活
1 public class BaseEntity{ 2 public void fill(Map<String, String[]> parameterMap) { 3 //System.out.println(this.getClass()); 4 Class clazz = this.getClass(); 5 Field fields[] = clazz.getDeclaredFields(); 6 for (int i = 0; i < fields.length; i++) { 7 String fieldName = fields[i].getName(); 8 //System.out.println(fieldName); 9 try { 10 // 查找参数集合 11 String values [] = parameterMap.get(fieldName); 12 if(values!=null && values.length>0){ 13 String methodName = "set"+fieldName.substring(0, 1).toUpperCase() 14 + fieldName.substring(1); 15 Class fieldType = fields[i].getType(); 16 Method method = clazz.getMethod(methodName,new Class[]{fieldType}); 17 // 设值 18 method.invoke(this,new Object[]{fieldType.cast(values[0])}); 19 } 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } 23 } 24 } 25 ///... 26 }
我把代码放在了 svnchina 和 github 上,有兴趣看看的可以去,有一起做的更好了。
我暂时只做了对实体的校验,这里还有个问题,java本身是强类型的语言。对于实体的某些字段,他的类型本身就存在了校验,比如一个字段是Integer类型,那你觉得还有必要去校验他是number类型吗?完全多次一举。只有当属性是字符串,校验它是否是number才有意义,比如手机号码
这里我写了一个BaseEntity类, 如果一个子类要进行校验,那么这个子类要覆写父类的两个方法,rules和labels方法。这两个方法,一个用来定义对该试题的校验规则,一个定义对实体字段的中文翻译。
1 package org.lyh.validator; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.util.HashMap; 6 import java.util.Map; 7 8 /** 9 * Created by lvyahui on 2015-06-27. 10 */ 11 public class BaseEntity { 12 13 private Validator validator = new Validator(this); 14 15 protected Map<String,String> labelMap = new HashMap<String,String>(); 16 17 { 18 String [][] allLabels = labels(); 19 if(allLabels != null){ 20 for(String [] label : allLabels){ 21 String prop = label[0],propLabel = label[1]; 22 if(prop != null && hasProp(prop) && propLabel != null){ 23 labelMap.put(prop,propLabel); 24 } 25 } 26 } 27 } 28 29 public boolean hasProp(String prop) { 30 try { 31 Field field = this.getClass().getDeclaredField(prop); 32 return true; 33 } catch (NoSuchFieldException e) { 34 return false; 35 } 36 } 37 38 public String getLabel(String prop){ 39 return labelMap.get(prop); 40 } 41 42 public void fill(Map<String, String[]> parameterMap) { 43 //System.out.println(this.getClass()); 44 Class clazz = this.getClass(); 45 Field fields[] = clazz.getDeclaredFields(); 46 for (int i = 0; i < fields.length; i++) { 47 String fieldName = fields[i].getName(); 48 //System.out.println(fieldName); 49 try { 50 // 查找参数集合 51 String values [] = parameterMap.get(fieldName); 52 if(values!=null && values.length>0){ 53 String methodName = "set"+fieldName.substring(0, 1).toUpperCase() 54 + fieldName.substring(1); 55 Class fieldType = fields[i].getType(); 56 Method method = clazz.getMethod(methodName,new Class[]{fieldType}); 57 // 设值 58 method.invoke(this,new Object[]{fieldType.cast(values[0])}); 59 } 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 } 64 } 65 66 /** 67 * 进行校验 68 * @return 校验 69 */ 70 public boolean validate() { 71 return this.validator.validate(); 72 } 73 74 /** 75 * 如果校验失败通过他获取错误信息 76 * @return 错误信息 77 */ 78 public Map<String,Map<String,String>> getErrors(){ 79 return this.validator.getErrors(); 80 } 81 82 /** 83 * 校验规则 84 * @return 校验规则 85 */ 86 87 public String[][] labels(){return null;} 88 89 /** 90 * 字段翻译 91 * @return 字段翻译 92 */ 93 public String [][] rules(){return null;} 94 95 }
下面创建一个子类,重写rules和labels方法。
1 package org.lyh.validator; 2 3 import java.sql.Timestamp; 4 5 /** 6 * Created by lvyahui on 2015-06-26. 7 */ 8 9 public class UserEntity extends BaseEntity { 10 private String username; 11 private String password; 12 private String name; 13 private Integer gold; 14 private Integer progress; 15 private Timestamp createdAt; 16 private String email; 17 private String phone; 18 private String site; 19 /*省略 getter/setter方法*/ 20 @Override 21 public String[][] rules() { 22 return new String [][] { 23 {"username,password,email,gold,progress,phone","required"}, 24 {"username,password","length","6","14"}, 25 {"rePassword","equals","password"}, 26 {"email","email","//w{6,12}"}, 27 {"createdAt","timestamp"}, 28 {"phone","number"}, 29 {"site","url"} 30 }; 31 } 32 33 public String[][] labels() { 34 return new String[][]{ 35 {"username","用户名"}, 36 {"password","密码"}, 37 {"rePassword","确认密码"}, 38 {"email","邮箱"}, 39 {"progress","进度"}, 40 {"phone","电话"}, 41 {"gold","金币"} 42 }; 43 } 44 45 }
可以看到,校验规则的写法,很简单,每一行第一个字符串写了需要校验的字段,第二个字符串写了这些字段应该满足的规则,后面的字符串是这个规则需要的参数
labels 就更简单了。
另外这个实例化validator类的时候,除了传递实体外,还可以传递第二个参数,表示是否是短路校验。
下面是这个校验的核心类Validator
1 package org.lyh.validator; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.Method; 5 import java.sql.Timestamp; 6 import java.text.MessageFormat; 7 import java.util.*; 8 9 /** 10 * Created by lvyahui on 2015-06-27. 11 * 12 */ 13 public class Validator { 14 /** 15 * 校验类型 16 */ 17 public static final String required = "required"; 18 public static final String length = "length"; 19 public static final String number = "number"; 20 public static final String equals = "equals"; 21 public static final String email = "email"; 22 public static final String url = "url"; 23 public static final String regex = "regex"; 24 public static final String timestamp = "timestamp"; 25 26 /** 27 * 附加参数类型 28 */ 29 private static final int PATTERN = 1; 30 private static final int MIN = 2; 31 private static final int MAX = 3; 32 private static final int PROP = 4; 33 34 private Map<Integer,Object> params = new HashMap<Integer,Object>(); 35 /** 36 * 验证失败的错误信息,形式如下 37 * {"username":{"REQUIRED":"用户名必须为空",...},...} 38 */ 39 protected Map<String,Map<String,String>> errors 40 = new HashMap<String,Map<String,String>>(); 41 42 /** 43 * 被校验的实体 44 */ 45 private BaseEntity baseEntity; 46 47 /** 48 * 被校验实体的类型 49 */ 50 private Class entityClass = null; 51 52 /** 53 * 当前正在被校验的字段 54 */ 55 private Field field; 56 /** 57 * 当前执行的校验 58 */ 59 private String validateType ; 60 61 /** 62 * 当前被校验字段的值 63 */ 64 private Object value; 65 66 /** 67 * 是否短路 68 */ 69 private boolean direct; 70 71 private String [][] rules ; 72 73 /** 74 * 75 * @param baseEntity 76 */ 77 public Validator(BaseEntity baseEntity) { 78 this(baseEntity,false); 79 } 80 81 public Validator(BaseEntity baseEntity,boolean direct){ 82 this.baseEntity = baseEntity; 83 entityClass = baseEntity.getClass(); 84 rules = baseEntity.rules(); 85 } 86 87 public Map<String,Map<String,String>> getErrors() { 88 return errors; 89 } 90 91 public Map<String,String> getError(String prop){ 92 return this.errors.get(prop); 93 } 94 95 public void addError(String prop,String validatorType,String message){ 96 Map<String,String> error = this.errors.get(prop); 97 if(error==null || error.size() == 0){ 98 error = new HashMap<String,String>(); 99 errors.put(prop, error); 100 } 101 error.put(validatorType,message); 102 } 103 104 public void setRules(String[][] rules) { 105 this.rules = rules; 106 } 107 108 public boolean required(){ 109 if(value!=null){ 110 if(value.getClass() == String.class && "".equals(((String)value).trim())){ 111 return false; 112 } 113 return true; 114 } 115 return false; 116 } 117 118 public boolean number(){ 119 if(value.getClass().getGenericSuperclass() == Number.class){ 120 return true; 121 }else if(((String)value).matches("^//d+$")){ 122 return true; 123 } 124 return false; 125 } 126 127 public boolean email(){ 128 return ((String) value).matches("^//w+@//w+.//w+.*//w*$"); 129 } 130 131 public boolean url(){ 132 return ((String)value).matches("^([a-zA-Z]*://)?([//w-]+//.)+[//w-]+(/[//w//-//.]+)*[///?%&=]*$"); 133 } 134 135 public boolean regex(){ 136 return ((String)value).matches((String) params.get(regex)); 137 } 138 139 public boolean equals() throws NoSuchFieldException, IllegalAccessException { 140 String prop = (String) params.get(PROP); 141 Field equalsField = entityClass.getDeclaredField(prop); 142 equalsField.setAccessible(true); 143 return value.equals(equalsField.get(baseEntity)); 144 } 145 public boolean timestamp(){ 146 if(field.getType().equals(Timestamp.class)){ 147 return true; 148 } 149 return false; 150 } 151 public boolean length() { 152 String val = (String) value; 153 Integer min = (Integer) params.get(MIN); 154 Integer max = (Integer) params.get(MAX); 155 156 return val.length() > min && (max == null || val.length() < max); 157 158 159 } 160 161 public boolean validate(){ 162 errors.clear(); 163 if(rules==null){ 164 return true; 165 } 166 167 for (int i = 0; i < rules.length; i++) { 168 String [] rule = rules[i],fields = rule[0].split(","); 169 validateType = rule[1]; 170 setParams(rule); 171 try { 172 Method validateMethod = this.getClass() 173 .getMethod(validateType); 174 for (int j = 0; j < fields.length; j++) { 175 if(direct && getError(fields[j]) != null){ continue; } 176 177 field = entityClass.getDeclaredField(fields[j]); 178 field.setAccessible(true); 179 value = field.get(baseEntity); 180 if(value != null || (value == null && validateType == required)){ 181 if(!(Boolean)validateMethod.invoke(this)){ 182 handleError(); 183 } 184 } 185 } 186 } catch (Exception e) { 187 e.printStackTrace(); 188 } 189 } 190 return true; 191 } 192 193 private void setParams(String [] rule) { 194 params.clear(); 195 // 取附加参数 196 switch (validateType){ 197 case regex: 198 params.put(PATTERN,rule[3]); 199 break; 200 case equals: 201 params.put(PROP, rule[2]); 202 break; 203 case length: 204 if(rule[2] != null) { 205 params.put(MIN, Integer.valueOf(rule[2])); 206 } 207 if(rule.length >= 4){ 208 params.put(MAX,Integer.valueOf(rule[3])); 209 } 210 break; 211 default: 212 } 213 } 214 215 private void handleError() { 216 String name = this.baseEntity.getLabel(field.getName()) != null 217 ? this.baseEntity.getLabel(field.getName()) : field.getName(), 218 message = MessageFormat.format(Messages.getMsg(validateType),name); 219 this.addError(field.getName(), validateType, message); 220 } 221 222 }
下面是错误消息模板
1 package org.lyh.validator; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * Created by lvyahui on 2015-06-28. 8 */ 9 public class Messages { 10 private static Map<String,String> msgMap = new HashMap<String,String>(); 11 12 static{ 13 msgMap.put(Validator.required,"{0}必须填写"); 14 msgMap.put(Validator.email,"{0}不合法"); 15 msgMap.put(Validator.equals,"{0}与{1}不相同"); 16 msgMap.put(Validator.length,"{0}长度必须在{1}-{2}之间"); 17 msgMap.put(Validator.regex,"{0}不符合表达式"); 18 msgMap.put(Validator.timestamp,"{0}不是合法的日期格式"); 19 msgMap.put(Validator.number,"{0}不是数字格式"); 20 msgMap.put(Validator.url,"{0}不是合法的url"); 21 } 22 23 public static String getMsg(String validateType) { 24 return msgMap.get(validateType); 25 } 26 }
下面写个main方法测试
1 package org.lyh.validator; 2 3 /** 4 * Created by lvyahui on 2015-06-28. 5 */ 6 public class MainTest { 7 public static void main(String[] args) { 8 9 UserEntity userEntity = new UserEntity(); 10 userEntity.validate(); 11 System.out.println(userEntity.getErrors()); 12 13 userEntity.setUsername("lvyahui"); 14 userEntity.setRePassword("admin888"); 15 userEntity.setPassword("admin888"); 16 userEntity.setEmail("lvyaui82.com"); 17 userEntity.validate(); 18 System.out.println(userEntity.getErrors()); 19 20 userEntity.setEmail("lvyaui8@12.com"); 21 userEntity.setPhone("hjhjkhj7867868"); 22 userEntity.setGold(1); 23 userEntity.setSite("www.baidu.com"); 24 25 userEntity.validate(); 26 27 System.out.println(userEntity.getErrors()); 28 // ([a-zA-Z]*://)?([/w-]+/.)+[/w-]+(/[/w-]+)*[/?%&=]* 29 userEntity.setSite("http://www.baidu.com/index.php"); 30 userEntity.setProgress(123); 31 userEntity.setPhone("156464564512"); 32 userEntity.validate(); 33 34 System.out.println(userEntity.getErrors()); 35 36 } 37 38 }
运行程序,得到这样的输出
1 {gold={required=金币必须填写}, password={required=密码必须填写}, phone={required=电话必须填写}, progress={required=进度必须填写}, email={required=邮箱必须填写}, username={required=用户名必须填写}} 2 {gold={required=金币必须填写}, phone={required=电话必须填写}, progress={required=进度必须填写}, email={email=邮箱不合法}} 3 {phone={number=电话不是数字格式}, progress={required=进度必须填写}} 4 {}
后面要做的就是直接对map进行校验
我的想法是通过validator类添加set方法注入规则和翻译。当然上面的错误信息模板可以到xml文件中进行配置,至于校验规则和翻译个人认为没有必要放到xml中去。