前面讲到了一种验证方法,是在Action的validate方法中通过代码的方式来完成的。而struts2提供了另外一种 方式来实现输入验证。
这种方式就是使用validate框架来实现输入校验,这种方式是基于XML的验证。
文件名为XXXAction-validation.xml。
那么校验xml文件格式该如何写呢?
可以使用firefox查看此xml的DTD定义,地址为 http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd
在此列出此DTD的内容
<?xml version="1.0" encoding="UTF-8"?>
<!--
XWork Validators DTD.
Used the following DOCTYPE.
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!ELEMENT validators (field|validator)+>
<!ELEMENT field (field-validator+)>
<!ATTLIST field
name CDATA #REQUIRED
>
<!ELEMENT field-validator (param*, message)>
<!ATTLIST field-validator
type CDATA #REQUIRED
short-circuit (true|false) "false"
>
<!ELEMENT validator (param*, message)>
<!ATTLIST validator
type CDATA #REQUIRED
short-circuit (true|false) "false"
>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param
name CDATA #REQUIRED
>
<!ELEMENT message (#PCDATA)>
<!ATTLIST message
key CDATA #IMPLIED
>
由此DTD的定义可知,此XML文件的根元素为validators。
在根元素下可以有若干个field或validator子元素,即分别代表着字段校验和非字段校验,它们的区别将在后面介绍。
字段校验
字段校验代表着field,标签有个name属性石必填的,它和表单中的name属性值是一样的。
这里我填入username。
<validators>
<field name="username">
</field>
</validators>
field下面有个子元素叫field-validator,代表着要用什么方式来进行校验,其有个属性叫type,也是必填的。
<validators>
<field name="username">
<field-validator type="">
</field-validator>
</field>
</validators>
type应该填入什么内容呢?
可以查看xwork的源文件,在包com.opensymphony.xwork2.validator.validators下有个文件default.xml,在这个文件中定义了type属性值。
<validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
<validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>
还有很多定义好的type值,这里不一一列举
在validator元素中,name属性表示可以定义的type值,class表示用哪个类来进行校验,这是struts2默认设置好的校验器,我们可以直接使用。
在这个例子中,我们将type设置为requiredstring
<validators>
<field name="username">
<field-validator type="requiredstring">
</field-validator>
</field>
</validators>
在field-validator下面有两个子元素,param和message
param可以任意个,可有可无,但是message有且只有一个。
param表示传入的参数,message表示出错时显示的信息。也就是说message可以是任意的字符串,但是param却是特定的。
在继续之前还是看看com.opensymphony.xwork2.validator.validators.RequiredStringValidator这个类的源代码。首先确定已经下载了源代码,并且在MyEclipse中关联了源代码,这是一个好习惯。
在这个类中有一个成员变量
private boolean doTrim = true;
因此,我们在param中要做的就是,将param元素的name属性设置为doTrim,值为true。
<validators>
<field name="username">
<field-validator type="requiredstring">
<param name="trim"<true>/param>
<message<username should not be blank!</message>
</field-validator>
</field>
</validators>
trim表示是否忽略空格,默认是true,因此在此也可以省略掉param元素。
其实在 field-validator元素中还有一个属性short-circuit,其默认值是false,表示的意思是短路,即前面验证失败,后面就不做验证了。
然而这仅仅只是一个校验条件,还是以username为例,在上次的例子中,还要求username的长度要在6到10之间,因此,继续看default.xml文件
在其中找到这样一个元素
<validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>
从字面上可以看出是限制字符串长度的,因此查看这个实现类
它有3个成员变量:
private boolean doTrim = true;
private int maxLength = -1;
private int minLength = -1;
其中最主要的是maxLength和minLength分别代表最大长度和最小长度
因此在这个校验器中内容应该如下:
<field name="username">
<field-validator type="stringlength">
<param name="minLength">6</param>
<param name="maxLength">10&lr;/param>
<message>username should be between ${minLength} and ${maxLength}</message>
</field-validator>
</field>
有个问题就是
<validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
<validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>
required和requiredstring有什么区别吗?
requiredstring是指这个字符串是必须的,而required表示这个字段是必须的,而没有指明这个字段是否必须是字符串。
因此对于字符串,可以使用requiredstring,而对于非字符串类型就必须使用required了。如在这个示例中的age,birthday。注意birthday是Date类型的,不是String。
还是回顾一下register2.jsp的页面内容。
body中的内容为:
<s:actionerror />
<s:form action="register2" theme="simple">
……
……
</s:form>
但是输入数据提交后,仍然显示的是以前的出错信息,并不是今天所设置的信息。
这就涉及到一些知识点了,因为校验框架产生的错误是fielderror,不会在actionerror中显示。
因此,需要将 theme="simple"去掉,并把那些表格标签页去掉,然后执行时会看到在username上方的错误信息。
还有一个问题就是validate校验框架和Action中validate方法是否冲突。
实际上虽然会使用框架验证,但是也会调用Action的validate方法,通过上面的显示结果应该可以清楚的看到,在表单上面集中的显示了actionerror。
但是如果把Action中的validate方法的出错信息add到field中会有什么效果呢??
修改RegisterAction中validate方法,将addActionError改为addFieldError。
当没有输入用户名时,会显示如下错误信息:
username should not be blank!
username invalid
为什么会显示这样的结果呢???
首先肯定的是在validate中增加的fielderror,和xml中不会冲突
即错误信息不会被覆盖,而是两者都有,而且先显示xml中定义的错误信息,然后才是validate中定义的错误信息。实际上是所有的xml执行完毕后,在执行validate。
为什么会产生这样的效果呢???那么你就必须知道fielderror到底是什么!继续查看源代码。
因为RegisterAction是继承的ActionSupport,addFieldError是继承自ActionSupport,所以先看看ActionSupport中addFieldError的实现方法。
在ActionSupport中addFieldError是这么实现的,
public void addActionError(String anErrorMessage) {
validationAware.addActionError(anErrorMessage);
}
即通过调用validationAware对象的addActionError,而validationAware是ValidationAwareSupport的一个实例,在ValidationAwareSupport中定义了fielderror是什么。
private Map>String, List<String>> fieldErrors;
public synchronized void addFieldError(String fieldName, String errorMessage) {
final Map<String, List<String>> errors = internalGetFieldErrors();
List<String> thisFieldErrors = errors.get(fieldName);
if (thisFieldErrors == null) {
thisFieldErrors = new ArrayList<String>();
errors.put(fieldName, thisFieldErrors);
}
thisFieldErrors.add(errorMessage);
}
通过源代码,可以看到fieldErrors实际上是一个Map>String, List<String>>。key是String类型的,而value是List<String>
而实际上这个List是通过ArrayList<String>来实现的,也就是说,key是String类型的,而value是ArrayList<String>。
虽然一个key只能对应一个value,但是在这里value并不是一个字符串,而是一个数组。所以错误信息不会被覆盖掉。
在ActionSupport类中存在一个方法getFieldErrors,按照方法名可以猜的出该方法返回的是fieldError这个数组,既然如此那么是否可以通过getFieldErrors直接添加呢??
List<String> list = new ArrayList<String>();
list.add("username should be between 6 and 10");
this.getFieldErrors().put("username",list);
虽然编译器没有报错,但是实际上是不行的。查看API文档:
getFieldErrors,其解释如下:
public Map<String,List<String>> getFieldErrors()
Description copied from interface: ValidationAware
Get the field specific errors associated with this action. Error messages should not be added directly here, as implementations are free to return a new Collection or an
Unmodifiable Collection.
注释:
这个方法返回与这个action相关的具体的fielderrors,错误信息不能直接从这里添加,执行结果返回一个新的集合或一个不可修改的集合
这是什么意思呢?看源代码,这个方法同样是在ValidationAwareSupport中实现的。
public synchronized Map<String, List<String>> getFieldErrors() {
return new LinkedHashMap<String, List<String>>(internalGetFieldErrors());
}
由此可以看到该方法返回的是一个新的LinkedHashMap,只是一个拷贝,而不是原集合,所以这样直接添加是无法显示出来的。
那么何时使用validate验证框架,什么时候使用action中的validate方法呢?
一般来说,简单验证可以使用xml,复杂时用validate
前面讲到了struts2的数据校验,那么为什么要有服务器校验??拥有了客户端校验不是也行吗??
服务端校验是必须的,即使有客户端校验。因为可以不通过browser访问web服务器!!强健的web应用要有客户端和服务器端的验证。
当然,struts2同样支持客户端验证。
要使用struts2的客户端校验,必须满足一下条件:
1.form的主题(theme)一定不能设置为simple
2.将struts2 form标签中validate属性设置为true
但是看到小时效果后就会发现,struts2生成的也是js代码,但是效果却很差,所以一般来说,使用struts2的服务器端校验,而客户端校验自己写。
struts2标签支持事件,可以像使用html标签一样使用。
同样用validate校验框架也应该可以使用局部校验:
当action中有多个方法时,需要在和action同目录下新建文件XXXAction-XXX(方法名)-validation.xml
在实例化子类对象时,会先执行父类的全局校验,然后是局部校验,接着是子类的全局校验,然后是子类的局部校验,因此不要提供全局校验。
字段校验和非字段校验的区别
通俗点讲:
字段校验:校验谁,用什么方法
非字段校验:用什么校验,校验谁
非字段校验示例:
<validator type="requiredstring">
<param name="fieldName">username</param>
<message></message>
</validator>
其中tyoe="fieldName"是不变的。至于其他细节不在这里详细叙述。
但还是建议使用字段校验器。
通过今天的学习,了解到了另外一种通过配置文件进行数据校验的方法。而这种方法显得更加简单,易懂。