萌生写这个代码的原因,是在使用struts2的验证框架时,总觉的有些不太灵活。当一个action中有多个表单需要处理的时候,struts处理起来就稍微麻烦一点,当校验失败时都会return "input"字符串。但我不同表单单校验失败后一般都希望返回不同的result,渲染不同的视图。另外如果我的校验是异步发起的,而我想要的结果是json的时候,也比较麻烦。虽然这些问题可以通过修改result type类型和在result中使用ognl表达式来做到,但绕来绕去实在太过麻烦。而若不适用这校验框架,在action中自己来if else if else 来判断更不可取。于是便回想到yii和 laravel框架的校验,这些校验框架在我看来做的非常优秀,非常灵活,于是我想借鉴它们来自己写一写这个校验插件。
希望的效果
首先,我希望做到两点
- 可以对实体进行校验,不管实体封装的是表单数据还是数据库记录
- 可以直接对request.getParameterMap()获得的map进行校验,这个我暂时还没完成。
-
灵活校验,当校验失败后可以马上通过getErrors拿到错误数据,这样你想咋么响应怎么响应,发json也好,把errors传递到视图也好,都可以。
- 也就是想下面这样,if(!entity.validate()){....}else{Map map = entity.getErrors()}
另外当实体的属性没有被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上,有兴趣看看的可以去,有一起做的更好了。
- http://www.svnchina.com/svn/entity_validator
- https://github.com/lvyahui8/validator.git
我暂时只做了对实体的校验,这里还有个问题,java本身是强类型的语言。对于实体的某些字段,他的类型本身就存在了校验,比如一个字段是Integer类型,那你觉得还有必要去校验他是number类型吗?完全多次一举。只有当属性是字符串,校验它是否是number才有意义,比如手机号码
BaseEntity类
这里我写了一个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类
下面是这个校验的核心类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中去。