用户输入校验是无法避免的工作。一个页面输入,我们要校验一些内容不能为空,一些内容长度必须满足条件,一些内容必须是整型---虽然没啥技术含量,但是还得做,而且还得做仔细了。
那么能不能让这部分的工作量减少一点?光说不练怎么行?下面就做做看!
看看下面这个类:
这是一个普通的form,页面提交的内容会自动填充到这里----地球人都知道。
那么,针对这个form的校验,怎么才能简单点呢?最初的想法是这样:
这样,如果有 @NotNull 标注的,就会检查是不是空。不过这样不好。因为变量在不同的页面里,检查的条件有可能不一样。所以,就弄出了这么一个接口:
这个接口里的方法应该与 form 的方法一致,这样就可以对应着去校验。
好,下面我们首先来编写测试方法,看看我的目的是什么:
嗯,我先假设使用了一个叫 AutoValidator 的类来做这个工作,然后还假设了一个 ValidateEntry 类来保存错误信息。假设是没用的,现在来创建这两个类。
这个ValidateEntry就是保存了一个错误信息,包括错误的项目的名字以及错误的信息的内容。
AutoValidator 类有点长,贴出来影响阅读,可以在附件中下载。这里就说说思路,通过传入的 IValidateTestForm, 根据里面的方法,去得到 form 里对应的 get 方法,然后检查方法所具有的标注,再调用标注里面的变量,进行校验。例如这个 NotNull 标注应该是这个样子:
这里又出来了 IItemValidator 接口:
这个接口有2个方法:一个是进行校验(咦,最后为啥带个不确定的参数类型?后面会分析到);还有一个方法是返回错误的信息内容。
而具体的、各种各样的 Validator,都必须要实现这个接口。
例如这个 NotNullValidator 就是下面这个样子:
再回头看看那个 NotNull 注解:
注解中必须定一个 IItemValidator 的变量,而且名字必须是 validator,在 AutoValidator 类中,正是通过 "validator" 这个名字来找到这个变量的。
itemName 是错误项目的名称,如果为空,那么就使用变量的名字。这个方法的名字也必须是“itemName”。
errorMsg 是错误消息的内容。如果为空,那么就使用 validator里的错误消息。这个方法的名字也必须是“errorMsg”。
恩,这个注解必须要遵从一些约定。
其实,我也想过在注解中再添加注解,例如
可是jdk在运行时死活就是发现不了注解中的注解,很是无奈呀!
好,下面看看我们究竟是如何做到减少校验的工作量的。
1、我们需要准备一些 validator,例如 NotNullValidator,这些类必须实现 IItemValidator 接口。
2、我们需要准备一些annotation,例如 NotNull, MoreThanZero 等等。这些 annotation 必须遵守一定的规则。
------- 以上这些活应该是一个负责搭建环境或者架构的人来做的 ---------------
------- 而且也希望以上这些东西可以重复用在不同的项目中 -------------------
3、针对一个 form 的校验,我们需要创建一个接口,类似于上面的 IValidateTestForm。需要校验的字段增加相应的注解。
4、最后,我们进行如下这样的调用:
开发者可以得到list 中的错误项目,自己组织错误信息的显示方式。
-------- 以上这些活就是一个开发者要做的 -------------------------------
开发者的工作量是不是减轻了不少?不过目前这个校验还不完善,有的校验我们需要传递给 validator 参数,例如 MaxLength 这样的校验,validator 需要知道一个 max length 是多少。所以,我们再次改造注解:
我们增加了一个方法叫 params。并且我们约定这个方法必须叫 "params"。类型任意,只要 annotation 允许就可了。然后我们再修改一下校验用的接口:
还记得我们的 validator 中,校验方法中有一个不定长的参数吧?嗯,就是为了传递这个参数。
好了,说了这么多,不知道大伙是否能感觉到减轻了校验的工作量呢?我想还是看代码最实际,附件中有完整的代码,可以运行测试类来看看效果。
如果您对这样的小东西感兴趣,欢迎给提出改进的意见,我可以继续完善,因为我实在是受够了做校验这样的无聊的活 :wink:
那么能不能让这部分的工作量减少一点?光说不练怎么行?下面就做做看!
看看下面这个类:
public class TestForm {
private String userName;
private int userAge;
private String userBirth;
省略 get/set 方法
}
这是一个普通的form,页面提交的内容会自动填充到这里----地球人都知道。
那么,针对这个form的校验,怎么才能简单点呢?最初的想法是这样:
public class TestForm {
@NotNull
public String getUserBirth() {
return userBirth;
}
这样,如果有 @NotNull 标注的,就会检查是不是空。不过这样不好。因为变量在不同的页面里,检查的条件有可能不一样。所以,就弄出了这么一个接口:
public interface IValidateTestForm {
@NotNull
public String userName();
@MoreThanZero
public int userAge();
@NotNull
public String userBirth();
}
这个接口里的方法应该与 form 的方法一致,这样就可以对应着去校验。
好,下面我们首先来编写测试方法,看看我的目的是什么:
public void testValidate() {
TestForm form = new TestForm();
List<ValidateEntry> list = AutoValidator.validate(form, IValidateTestForm.class);
assertEquals(3, list.size());
assertTrue(errorExist("userAge", "必须是大于等于0的有效数字。", list));
assertTrue(errorExist("userBirth", "不能为空。", list));
assertTrue(errorExist("userName", "不能为空。", list));
}
private boolean errorExist(String entryName, String entryValue, List<ValidateEntry> list) {
for(ValidateEntry entry : list) {
if(entryName.equals(entry.getEntryName()) &&
entryValue.equals(entry.getEntryValue())) {
return true;
}
}
return false;
}
嗯,我先假设使用了一个叫 AutoValidator 的类来做这个工作,然后还假设了一个 ValidateEntry 类来保存错误信息。假设是没用的,现在来创建这两个类。
public class ValidateEntry {
private String entryName;
private String entryValue;
public String getEntryName() {
return entryName;
}
public void setEntryName(String entryName) {
this.entryName = entryName;
}
public String getEntryValue() {
return entryValue;
}
public void setEntryValue(String entryValue) {
this.entryValue = entryValue;
}
}
这个ValidateEntry就是保存了一个错误信息,包括错误的项目的名字以及错误的信息的内容。
AutoValidator 类有点长,贴出来影响阅读,可以在附件中下载。这里就说说思路,通过传入的 IValidateTestForm, 根据里面的方法,去得到 form 里对应的 get 方法,然后检查方法所具有的标注,再调用标注里面的变量,进行校验。例如这个 NotNull 标注应该是这个样子:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
public IItemValidator validator = new NotNullValidator();
public String itemName() default "";
public String errorMsg() default "";
}
这里又出来了 IItemValidator 接口:
public interface IItemValidator {
public boolean validate(Object obj, Object... params);
public String getErrorMessage();
}
这个接口有2个方法:一个是进行校验(咦,最后为啥带个不确定的参数类型?后面会分析到);还有一个方法是返回错误的信息内容。
而具体的、各种各样的 Validator,都必须要实现这个接口。
例如这个 NotNullValidator 就是下面这个样子:
public class NotNullValidator implements IItemValidator{
public boolean validate(Object obj, Object... params) {
if(obj == null) {
return false;
}
if(obj instanceof String) {
if(BtmsUtil.isBlank((String)obj)) {
return false;
}
} else if(obj instanceof String[]) {
if(BtmsUtil.isBlank((String[]) obj)) {
return false;
}
}
return true;
}
public String getErrorMessage() {
return "不能为空。";
}
}
再回头看看那个 NotNull 注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
public IItemValidator validator = new NotNullValidator();
public String itemName() default "";
public String errorMsg() default "";
}
注解中必须定一个 IItemValidator 的变量,而且名字必须是 validator,在 AutoValidator 类中,正是通过 "validator" 这个名字来找到这个变量的。
itemName 是错误项目的名称,如果为空,那么就使用变量的名字。这个方法的名字也必须是“itemName”。
errorMsg 是错误消息的内容。如果为空,那么就使用 validator里的错误消息。这个方法的名字也必须是“errorMsg”。
恩,这个注解必须要遵从一些约定。
其实,我也想过在注解中再添加注解,例如
public @interface NotNull {
public IItemValidator validator = new NotNullValidator();
@ItemName
public String itemName() default "";
public String errorMsg() default "";
}
可是jdk在运行时死活就是发现不了注解中的注解,很是无奈呀!
好,下面看看我们究竟是如何做到减少校验的工作量的。
1、我们需要准备一些 validator,例如 NotNullValidator,这些类必须实现 IItemValidator 接口。
2、我们需要准备一些annotation,例如 NotNull, MoreThanZero 等等。这些 annotation 必须遵守一定的规则。
------- 以上这些活应该是一个负责搭建环境或者架构的人来做的 ---------------
------- 而且也希望以上这些东西可以重复用在不同的项目中 -------------------
3、针对一个 form 的校验,我们需要创建一个接口,类似于上面的 IValidateTestForm。需要校验的字段增加相应的注解。
4、最后,我们进行如下这样的调用:
List<ValidateEntry> list = AutoValidator.validate(form, IValidateTestForm.class);
开发者可以得到list 中的错误项目,自己组织错误信息的显示方式。
-------- 以上这些活就是一个开发者要做的 -------------------------------
开发者的工作量是不是减轻了不少?不过目前这个校验还不完善,有的校验我们需要传递给 validator 参数,例如 MaxLength 这样的校验,validator 需要知道一个 max length 是多少。所以,我们再次改造注解:
public @interface MaxLength {
public String itemName() default "";
public IItemValidator validator = new MaxLengthValidator();
public int[] params();
public String errorMsg() default "";
}
我们增加了一个方法叫 params。并且我们约定这个方法必须叫 "params"。类型任意,只要 annotation 允许就可了。然后我们再修改一下校验用的接口:
@NotNull(itemName="用户名称")
@MaxLength(itemName="用户名称", params = {20})
public String userName();
还记得我们的 validator 中,校验方法中有一个不定长的参数吧?嗯,就是为了传递这个参数。
好了,说了这么多,不知道大伙是否能感觉到减轻了校验的工作量呢?我想还是看代码最实际,附件中有完整的代码,可以运行测试类来看看效果。
如果您对这样的小东西感兴趣,欢迎给提出改进的意见,我可以继续完善,因为我实在是受够了做校验这样的无聊的活 :wink: