- 浏览: 12678 次
- 性别:
- 来自: 上海
最新评论
Struts2提供的客户端校验
尽管这种支持比较弱,但采用Struts2中的客户端校验时需要注意以下几点
1..将<s:form validate="true">的validate属性设置为TRUE
2..不能将<s:form theme="">的theme属性设置为simple
3..建议将<s:form/>的action和namespace属性分开写
4..可以在页面中使用<s:head/>标签来引入样式
5..最好不要使用Struts2提供的客户端校验
设置validate="true"之前和之后的JSP页面在运行时的源代码是不同的
设置该属性之前,表单的onsubmit="return true;"
设置该属性之后,表单的onsubmit="return validateForm_register();"
并且在源码中多出了一段函数名为validateForm_register()的JavaScript代码
这个函数的具体内容与ValidateFrameAction-validation.xml中设置的内容很相似
并且Struts2的客户端验证的提示信息都是显示在表格中各个字段正上方的
这种显示是很死板的,远远不如我们自己编写JavaScript代码来的强大
Struts2为我们提供的客户端校验,在使用起来很不方便,故不推荐使用
所以尽管Struts2是很好的一个框架,但并不是说它的所有功能都是非常棒的
它所提供的客户端校验犹如鸡肋一般,所以我们更多的还是自己编写JavaScript代码
在实际开发中,也几乎不会使用到Struts2提供的客户端校验
使用的更多的还是它的服务端的校验,这也是Strtus2的强项
Struts2提供的校验框架
对于一个健壮的项目应用来说,仅仅提供一个客户端校验,是不安全的
因为有些恶意的用户可能会尝试着不通过Web网页的方式来访问
可以通过远程登录的方式访问某台主机的某个资源,这时就完全脱离了浏览器,脱离了HTML
直接通过HTTP底层的协议来发送信息,此时就可以绕过输入信息的页面而直接向服务器发送请求
也就是说,服务器端的验证,是整个请求的最后一道防线
而Struts2恰好为我们提供了一个校验框架,用于在服务端验证前台提交过来的表单
在Struts2中,它的每一个校验框架都对应着每一个Action,而且它的命名是有限制的
比如相对于validateFrameAction而言,它的验证框架就必须为validateFrameAction-validation.xml
其中-validation.xml是固定不变的。然后在它前面加上Action名字之后,Struts2就可以识别了
并且校验文件必须与它所对应的Action类处于同一目录下
使用该验证框架的两个条件:1..使用validation拦截器,2..它所验证的Action需要继承ActionSupport类
并且当验证失败出现message时,它会转向到INPUT页面来显示message消息
字段校验和非字段校验
所谓的字段校验,即校验ValidateFrameAction中的属性。一个<field>表示对Action中的一个属性的校验
字段校验,属于是字段优先。即我去校验谁,然后是我用谁去校验
非字段校验,则校验器优先。即我用谁去校验,然后是我去校验谁
即字段校验中先定义name="username"对哪个字段校验,再定义type="requiredstring"如何校验
而非字段校验先定义type="requiredstring"确定校验器,再定义name="fieldName"指定所校验的属性
字段校验和非字段校验的本质都是一样的,只是说话的顺序不同罢了。反映到底层后,二者实质上都是一样的
相对来说不推荐使用非字段验证,实际应用中如果需要验证的字段非常多的话,那么在维护的时候就比较困难了
深剖Struts2校验框架的本质
展开xwork-2.0.4.jar中的com.opensymphony.xwork2.validator.validators包里面发现有很多的校验类
因此,我们知道,我们现在所使用的这种校验框架,实际上就是由Struts2已经给我们提供好的一些类
这些类来校验客户端的表单的输入请求,这些类,这些校验,都是由Struts2已经内置好的了,我们可以直接使用
然后打开包里面的default.xml文件,就可以知道<field-validator>中type是取值于default.xml文件的
expression和fieldexpression都是用于OGNL表达式的判断,分别返回Action级别和Field级别的错误
conversion用于格式转换出现错误时的判断。stringlength判断字符串长度。regex用于正则表达式的判断
对Action中某一方法的校验
假设在validateFrameAction中存在test()方法,如果对test()方法进行校验的话
那么就可以提供一个类似于validateFrameAction-test-validation.xml的文件
但是,validateFrameAction-validation.xml仍会被校验
也就是说,如果validateFrameAction-validation.xml和validateFrameAction-test-validation.xml共存的话
那么Struts2会先校验validateFrameAction-validation,然后再校验validateFrameAction-test-validation
但前提是,配置<action/>时,所调用的不是execute()方法,而是method="test",即调用的Action中的是test()方法
所以,假如一个Action中存在多个处理逻辑,那么就不要再提供validateFrameAction-validation.xml全局校验
而是建议为具体的每一个方法提供对应的类似于validateFrameAction-test-validation.xml的局部的校验文件
校验文件与Action中的validate()共存时
如果在提供了校验文件的同时,也重写了Action中的validate()方法,那么二者都会执行
当校验的信息不是特别复杂的时候,推荐尽量使用校验文件的方式
当校验的信息特别特别复杂的时候,可以使用validate()方法,即硬编码的方式进行校验
如果validateFrameAction-validation.xml与validateFrameAction中的validate()共存的话
那么执行顺序是:校验文件先执行,validate()后执行。假设二者都对username进行了校验设置
那么后执行的validate()并不会冲刷掉先执行的校验文件中设置的信息,二者都会输出
由于先执行校验文件,所以首先显示的是校验文件中的错误提示信息,后显示validate()的信息
另外,我们可以在Debug模式下运行项目,然后判断出校验文件和validate()的执行顺序
首先在validationAwareSupport类的addFieldError()方法中设置断点
然后以Debug模式运行,通过按F6在Debug中的Variables视图里观察errorMessage显示的信息的先后来判断
最后得到的结果就是校验文件先执行,而validate()方法是后执行的
而Struts2会先将校验文件中<message/>放到与username的Key对应Value的ArrayList中,即增加一行错误消息
当Action中的validate()方法中的this.addFieldError("username","aaaaaaaaaaa")执行时
它就会将aaaaaaaaaaa直接放到Key为userame所对应的ArrayList中
因此当执行完validate()中的this.addFieldError()方法时,内存中的情况是这样的
一个username作为一个Key,它的Value是一个ArrayList,而此时的ArrayList里面包含了两个元素
第一个元素是校验文件里面<message/>中的信息,第二个元素就是aaaaaaaaaaa
由于在页面中采用的是Struts2的标签库,所以,它会将ArrayList中的信息全部都迭代取出来
因此在页面中就会先显示校验文件中的<message/>信息,然后显示addFieldError()所设定的错误信息
备注:这里所说的东西,如果看不明白的话,可以先看看下面的剖析addFieldError()方法
子类Action中的方法的校验细节
甚至还存在一种特殊的情况,比如ParentAction{test}和ChildAction extends ParentAction{test}
如果同时提供了ParentAction-validation.xml和ParentAction-test-validation.xml文件的话
并且对应子类也提供了ChildAction-validation.xml和ChildAction-test-validation.xml文件
我们想对子类中的test()方法进行校验,那么它的执行流程如下
先执行ParentAction-validation.xml文件,再执行ParentAction-test-validation.xml文件
再执行ChildAction-validation.xml文件,最后执行ChildAction-test-validation.xml文件
就好似类的继承,在New子类的一个实例的时候,会先New出父类的对象,然后才New出子类的对象
先有父亲,再有孩子嘛。校验也是这样的。但是我们不推荐将Action定义成这样
仅仅是对子类的test()方法进行校验,竟然需要调用四个校验文件,很不合理
就这种情况而言,轻易不会发生,仅供了解
Struts2中的两种类型的错误信息
在Struts2中,提示的错误信息有两种,一种是Field级别的,一种是Action级别的
将错误信息放到ActionError的过程中,实际上是把错误信息放到Arraylist()中了
将错误信息放到FieldError的过程中,实际上是把错误信息放到Map()中了
这个Map的key就是属性的名字,它的value就是增加的错误消息
当FileError或者ActionError中包含错误信息时,Struts2就会认为类型转换是错误的
然后就会转向到INPUT页面,而不会执行execute()方法
如果在Action中将某一个出错信息放到了addFieldError()里面的话
那么就可以在JSP页面中使用<s:fielderror/>标签将FieldError中的信息输出
另外,尽管Struts2内置了错误信息显示的功能,但是它也只能显示FileError中的错误信息
但我们可以通过手工添加<s:actionerror/>标签在页面中显示ActionError里的错误信息
剖析addFieldError()方法
这里的fieldErrors是validationAwareSupport类定义的私有的Map类型的成员变量
LinkedHashMap是Map的一个具体的实现,它使用列表的方式来实现的
也就是说,首先判断fieldErrors是否为空。起初fieldErrors肯定为空
那么既然它是空的,那么就实例一个LinkedHashMap对象,赋给fieldErrors并返回它
接着在addFieldError()方法中用final的Map类型的errors接收LinkedHashMap类型的fieldErrors
所以说真正存放Field级别错误信息的对象是LinkedHashMap
然后通过ListthisFieldErrors=(List)errors.get(fieldName);代码可以发现
它是使用LinkedHashMap类型的errors.get()一个String类型的Key,然后将返回的Value值再转换成List类型
因此我们可以很明确的大胆的说出来:对于LinkedHashMap,它的key是一个String,它的value是一个List
接下来执行if()语句。如果它得到的value值是null,那么它就会New一个ArrayList()
换句话说,这个List类型的value是用一个ArrayList()来实现的
所以说该LinkedHashMap的key是String类型的,value是ArrayList类型的
由于它的value是ArrayList类型的,所以它的一个key所对应的value里面就可以存放多个值
只要调用了validationAwareSupport类中的addFieldError()方法的话,就说明已经有一个错误发生了
那么它首先会针对于fieldName的名字,找到它对应的value
如果value为空,说明与fieldName所对应的value根本就不存在
也就是说LinkedHashMap中并没有这一行映射,因此它就直接给我们New出来一个新的ArrayList
然后将fieldName和刚刚生成好的ArrrayList放置到LinkedHashMap中
此时的LinkedHashMap中的Key就是表单里的输入域的name值,Value是一个空的ArrayList
然后再执行thisFielsErrors.add(errorMessage);也就是在ArrayList中增加一条错误信息
剖析addFieldError()方法
所以说:对于Action级别的错误信息,实际上是放置在ArrayList中的
关于getFieldErrors()的使用误区
com.opensymphony.xwork2.ActionSupport类之所以非常重要
就是因为它已经实现了很多很多的接口,而这些接口都是用来处理不同的事情的
在ActionSupport中有很多的方法,其实只要明白了ActionError和FieldError的区别的话
对于这里的很多关于Error的方法,直接看一下API文档,然后再跟踪一下代码,就都一目了然了
有一个在日常开发中,很容易犯错误的一个地方,需要强调一下,那就是getFieldErrors()方法
它是这样的:public Map getFieldErrors()
它的描述是:得到Field级别的错误消息,并关联到当前Action,不应该直接在这里增加错误消息
因为这个实现,可以随意的返回一个新的Collection或者是不可修改的Collection
先举个例子:正常情况下是通过this.addFieldError("username","22");往Field中增加一个错误信息
但是有人可能有这样一种思路:先通过this.getFieldErrors()得到一个与Field所对应的LinkedHashMap
然后再执行this.getFieldErrors().put("username", "88");往LinkedHashMap中增加一条信息
但是通过实际操作,运行项目后,发现没有输出"88"错误信息。这时就需要研究一下getFieldErrors()方法
剖析getFieldErrors()方法
这时,我们就可以很清楚的知道,它实际上是返回了一个LinkedHashMap的一个副本
实际上this.getFieldErrors().put("username", "88")所增加的信息
是增加到了以前的FieldError的一个副本里面了,根本没有对以前的FieldError有任何影响
这里需要注意一下,实际上它并不是真正的return出来了一个原来的集合
而是return出来了一个用原来集合的内容作为参数的一个LinkedHashMap的一个副本
很多人都会犯这个错误
因为根据我们通常意义上的理解,this.getFieldErrors()应该返回的是一个LinkedHashMap本身
但是实际上它返回的是LinkedHashMap的一个副本。所以在Struts2的API中对该方法有这样的一段描述
Error messages should not be added directly here
as implementations are free to return a new Collection or an Unmodifiable Collection
这个getFieldErrors就相当于返回了一个只读的属性
我们只可以把它get出来,然后读它的内容,但是不能再往里面增加或者修改里面的任何内容
--------------------------------------------------------------------------------
下面是示例工程,这是一个Struts2.0.11应用
首先是web.xml文件
然后是用于提供表单输入的validateFrame.jsp页面
当表单输入域均正确时的validateSuccess.jsp页面
然后是struts.xml文件
然后是用到的Action类
最后是用到的验证框架文件ValidateFrameAction-validation.xml
另附:ValidateFrameAction-validation.xml的补充说明
尽管这种支持比较弱,但采用Struts2中的客户端校验时需要注意以下几点
1..将<s:form validate="true">的validate属性设置为TRUE
2..不能将<s:form theme="">的theme属性设置为simple
3..建议将<s:form/>的action和namespace属性分开写
4..可以在页面中使用<s:head/>标签来引入样式
5..最好不要使用Struts2提供的客户端校验
设置validate="true"之前和之后的JSP页面在运行时的源代码是不同的
设置该属性之前,表单的onsubmit="return true;"
设置该属性之后,表单的onsubmit="return validateForm_register();"
并且在源码中多出了一段函数名为validateForm_register()的JavaScript代码
这个函数的具体内容与ValidateFrameAction-validation.xml中设置的内容很相似
并且Struts2的客户端验证的提示信息都是显示在表格中各个字段正上方的
这种显示是很死板的,远远不如我们自己编写JavaScript代码来的强大
Struts2为我们提供的客户端校验,在使用起来很不方便,故不推荐使用
所以尽管Struts2是很好的一个框架,但并不是说它的所有功能都是非常棒的
它所提供的客户端校验犹如鸡肋一般,所以我们更多的还是自己编写JavaScript代码
在实际开发中,也几乎不会使用到Struts2提供的客户端校验
使用的更多的还是它的服务端的校验,这也是Strtus2的强项
Struts2提供的校验框架
对于一个健壮的项目应用来说,仅仅提供一个客户端校验,是不安全的
因为有些恶意的用户可能会尝试着不通过Web网页的方式来访问
可以通过远程登录的方式访问某台主机的某个资源,这时就完全脱离了浏览器,脱离了HTML
直接通过HTTP底层的协议来发送信息,此时就可以绕过输入信息的页面而直接向服务器发送请求
也就是说,服务器端的验证,是整个请求的最后一道防线
而Struts2恰好为我们提供了一个校验框架,用于在服务端验证前台提交过来的表单
在Struts2中,它的每一个校验框架都对应着每一个Action,而且它的命名是有限制的
比如相对于validateFrameAction而言,它的验证框架就必须为validateFrameAction-validation.xml
其中-validation.xml是固定不变的。然后在它前面加上Action名字之后,Struts2就可以识别了
并且校验文件必须与它所对应的Action类处于同一目录下
使用该验证框架的两个条件:1..使用validation拦截器,2..它所验证的Action需要继承ActionSupport类
并且当验证失败出现message时,它会转向到INPUT页面来显示message消息
字段校验和非字段校验
所谓的字段校验,即校验ValidateFrameAction中的属性。一个<field>表示对Action中的一个属性的校验
字段校验,属于是字段优先。即我去校验谁,然后是我用谁去校验
非字段校验,则校验器优先。即我用谁去校验,然后是我去校验谁
即字段校验中先定义name="username"对哪个字段校验,再定义type="requiredstring"如何校验
而非字段校验先定义type="requiredstring"确定校验器,再定义name="fieldName"指定所校验的属性
字段校验和非字段校验的本质都是一样的,只是说话的顺序不同罢了。反映到底层后,二者实质上都是一样的
相对来说不推荐使用非字段验证,实际应用中如果需要验证的字段非常多的话,那么在维护的时候就比较困难了
深剖Struts2校验框架的本质
展开xwork-2.0.4.jar中的com.opensymphony.xwork2.validator.validators包里面发现有很多的校验类
因此,我们知道,我们现在所使用的这种校验框架,实际上就是由Struts2已经给我们提供好的一些类
这些类来校验客户端的表单的输入请求,这些类,这些校验,都是由Struts2已经内置好的了,我们可以直接使用
然后打开包里面的default.xml文件,就可以知道<field-validator>中type是取值于default.xml文件的
expression和fieldexpression都是用于OGNL表达式的判断,分别返回Action级别和Field级别的错误
conversion用于格式转换出现错误时的判断。stringlength判断字符串长度。regex用于正则表达式的判断
对Action中某一方法的校验
假设在validateFrameAction中存在test()方法,如果对test()方法进行校验的话
那么就可以提供一个类似于validateFrameAction-test-validation.xml的文件
但是,validateFrameAction-validation.xml仍会被校验
也就是说,如果validateFrameAction-validation.xml和validateFrameAction-test-validation.xml共存的话
那么Struts2会先校验validateFrameAction-validation,然后再校验validateFrameAction-test-validation
但前提是,配置<action/>时,所调用的不是execute()方法,而是method="test",即调用的Action中的是test()方法
所以,假如一个Action中存在多个处理逻辑,那么就不要再提供validateFrameAction-validation.xml全局校验
而是建议为具体的每一个方法提供对应的类似于validateFrameAction-test-validation.xml的局部的校验文件
校验文件与Action中的validate()共存时
如果在提供了校验文件的同时,也重写了Action中的validate()方法,那么二者都会执行
当校验的信息不是特别复杂的时候,推荐尽量使用校验文件的方式
当校验的信息特别特别复杂的时候,可以使用validate()方法,即硬编码的方式进行校验
如果validateFrameAction-validation.xml与validateFrameAction中的validate()共存的话
那么执行顺序是:校验文件先执行,validate()后执行。假设二者都对username进行了校验设置
那么后执行的validate()并不会冲刷掉先执行的校验文件中设置的信息,二者都会输出
由于先执行校验文件,所以首先显示的是校验文件中的错误提示信息,后显示validate()的信息
另外,我们可以在Debug模式下运行项目,然后判断出校验文件和validate()的执行顺序
首先在validationAwareSupport类的addFieldError()方法中设置断点
然后以Debug模式运行,通过按F6在Debug中的Variables视图里观察errorMessage显示的信息的先后来判断
最后得到的结果就是校验文件先执行,而validate()方法是后执行的
而Struts2会先将校验文件中<message/>放到与username的Key对应Value的ArrayList中,即增加一行错误消息
当Action中的validate()方法中的this.addFieldError("username","aaaaaaaaaaa")执行时
它就会将aaaaaaaaaaa直接放到Key为userame所对应的ArrayList中
因此当执行完validate()中的this.addFieldError()方法时,内存中的情况是这样的
一个username作为一个Key,它的Value是一个ArrayList,而此时的ArrayList里面包含了两个元素
第一个元素是校验文件里面<message/>中的信息,第二个元素就是aaaaaaaaaaa
由于在页面中采用的是Struts2的标签库,所以,它会将ArrayList中的信息全部都迭代取出来
因此在页面中就会先显示校验文件中的<message/>信息,然后显示addFieldError()所设定的错误信息
备注:这里所说的东西,如果看不明白的话,可以先看看下面的剖析addFieldError()方法
子类Action中的方法的校验细节
甚至还存在一种特殊的情况,比如ParentAction{test}和ChildAction extends ParentAction{test}
如果同时提供了ParentAction-validation.xml和ParentAction-test-validation.xml文件的话
并且对应子类也提供了ChildAction-validation.xml和ChildAction-test-validation.xml文件
我们想对子类中的test()方法进行校验,那么它的执行流程如下
先执行ParentAction-validation.xml文件,再执行ParentAction-test-validation.xml文件
再执行ChildAction-validation.xml文件,最后执行ChildAction-test-validation.xml文件
就好似类的继承,在New子类的一个实例的时候,会先New出父类的对象,然后才New出子类的对象
先有父亲,再有孩子嘛。校验也是这样的。但是我们不推荐将Action定义成这样
仅仅是对子类的test()方法进行校验,竟然需要调用四个校验文件,很不合理
就这种情况而言,轻易不会发生,仅供了解
Struts2中的两种类型的错误信息
在Struts2中,提示的错误信息有两种,一种是Field级别的,一种是Action级别的
将错误信息放到ActionError的过程中,实际上是把错误信息放到Arraylist()中了
将错误信息放到FieldError的过程中,实际上是把错误信息放到Map()中了
这个Map的key就是属性的名字,它的value就是增加的错误消息
当FileError或者ActionError中包含错误信息时,Struts2就会认为类型转换是错误的
然后就会转向到INPUT页面,而不会执行execute()方法
如果在Action中将某一个出错信息放到了addFieldError()里面的话
那么就可以在JSP页面中使用<s:fielderror/>标签将FieldError中的信息输出
另外,尽管Struts2内置了错误信息显示的功能,但是它也只能显示FileError中的错误信息
但我们可以通过手工添加<s:actionerror/>标签在页面中显示ActionError里的错误信息
剖析addFieldError()方法
//通过查看父类ActionSupport.java的源码,查找到了该方法的原形,代码如下 public void addFieldError(String fieldName,String errorMessage){ validationAware.addFieldError(fieldName,errorMessage); } //fieldName代表属性的名字,errorMessage代表错误的消息 //validationAware是一个validationAwareSupport类型的一个实例 //validationAwareSupport类实现了validationAware和Serializable接口 //validationAwareSupport类中的addFieldError()方法的代码如下所示 public synchronized void addFieldError(String fieldName,String errorMessage){ final Map errors = internalGetFieldErrors(); List thisFieldErrors = (List) errors.get(fieldName); if (thisFielsErrors == null){ thisFielsErrors = new ArrayList(); errors.put(fieldName, thisFielsErrors); } thisFielsErrors.add(errorMessage); } //这是非常非常重要的方法,通过它的代码就足以显示出FieldError中的Map里面到底是什么东西 //而internalGetFieldErrors方法也是在validationAwareSupport类中定义的,它的代码如下 private Map internalGetFieldErrors(){ if (fieldErrors == null){ fieldErrors = new LinkedHashMap(); } return fieldErrors; }
这里的fieldErrors是validationAwareSupport类定义的私有的Map类型的成员变量
LinkedHashMap是Map的一个具体的实现,它使用列表的方式来实现的
也就是说,首先判断fieldErrors是否为空。起初fieldErrors肯定为空
那么既然它是空的,那么就实例一个LinkedHashMap对象,赋给fieldErrors并返回它
接着在addFieldError()方法中用final的Map类型的errors接收LinkedHashMap类型的fieldErrors
所以说真正存放Field级别错误信息的对象是LinkedHashMap
然后通过ListthisFieldErrors=(List)errors.get(fieldName);代码可以发现
它是使用LinkedHashMap类型的errors.get()一个String类型的Key,然后将返回的Value值再转换成List类型
因此我们可以很明确的大胆的说出来:对于LinkedHashMap,它的key是一个String,它的value是一个List
接下来执行if()语句。如果它得到的value值是null,那么它就会New一个ArrayList()
换句话说,这个List类型的value是用一个ArrayList()来实现的
所以说该LinkedHashMap的key是String类型的,value是ArrayList类型的
由于它的value是ArrayList类型的,所以它的一个key所对应的value里面就可以存放多个值
只要调用了validationAwareSupport类中的addFieldError()方法的话,就说明已经有一个错误发生了
那么它首先会针对于fieldName的名字,找到它对应的value
如果value为空,说明与fieldName所对应的value根本就不存在
也就是说LinkedHashMap中并没有这一行映射,因此它就直接给我们New出来一个新的ArrayList
然后将fieldName和刚刚生成好的ArrrayList放置到LinkedHashMap中
此时的LinkedHashMap中的Key就是表单里的输入域的name值,Value是一个空的ArrayList
然后再执行thisFielsErrors.add(errorMessage);也就是在ArrayList中增加一条错误信息
剖析addFieldError()方法
//通过查看父类ActionSupport.java的源码,查找到了该方法的原形,代码如下 public void addActionError(String anErrorMessage){ validationAware.addActionError(anErrorMessage); } //而validationAwareSupport类中的addActionError()方法的代码如下所示 public synchronized void addActionError(String anErrorMessage){ internalGetActionErrors.add(anErrorMessage); } //而internalGetActionErrors方法也是在validationAwareSupport类中定义的,它的代码如下 private Collection internalGetActionErrors(){ if (actionErrors == null){ actionErrors = new ArrayList(); } return actionErrors; }
所以说:对于Action级别的错误信息,实际上是放置在ArrayList中的
关于getFieldErrors()的使用误区
com.opensymphony.xwork2.ActionSupport类之所以非常重要
就是因为它已经实现了很多很多的接口,而这些接口都是用来处理不同的事情的
在ActionSupport中有很多的方法,其实只要明白了ActionError和FieldError的区别的话
对于这里的很多关于Error的方法,直接看一下API文档,然后再跟踪一下代码,就都一目了然了
有一个在日常开发中,很容易犯错误的一个地方,需要强调一下,那就是getFieldErrors()方法
它是这样的:public Map getFieldErrors()
它的描述是:得到Field级别的错误消息,并关联到当前Action,不应该直接在这里增加错误消息
因为这个实现,可以随意的返回一个新的Collection或者是不可修改的Collection
先举个例子:正常情况下是通过this.addFieldError("username","22");往Field中增加一个错误信息
但是有人可能有这样一种思路:先通过this.getFieldErrors()得到一个与Field所对应的LinkedHashMap
然后再执行this.getFieldErrors().put("username", "88");往LinkedHashMap中增加一条信息
但是通过实际操作,运行项目后,发现没有输出"88"错误信息。这时就需要研究一下getFieldErrors()方法
剖析getFieldErrors()方法
//通过查看父类ActionSupport.java的源码,查找到了该方法的原形,代码如下 public Map getFieldErrors(){ return validationAware.getFieldErrors(); } //而validationAwareSupport类中的getFieldErrors()方法的代码如下所示 public synchronized Map getFieldErrors(){ return new LinkedHashMap(internalGetFieldErrors()); }
这时,我们就可以很清楚的知道,它实际上是返回了一个LinkedHashMap的一个副本
实际上this.getFieldErrors().put("username", "88")所增加的信息
是增加到了以前的FieldError的一个副本里面了,根本没有对以前的FieldError有任何影响
这里需要注意一下,实际上它并不是真正的return出来了一个原来的集合
而是return出来了一个用原来集合的内容作为参数的一个LinkedHashMap的一个副本
很多人都会犯这个错误
因为根据我们通常意义上的理解,this.getFieldErrors()应该返回的是一个LinkedHashMap本身
但是实际上它返回的是LinkedHashMap的一个副本。所以在Struts2的API中对该方法有这样的一段描述
Error messages should not be added directly here
as implementations are free to return a new Collection or an Unmodifiable Collection
这个getFieldErrors就相当于返回了一个只读的属性
我们只可以把它get出来,然后读它的内容,但是不能再往里面增加或者修改里面的任何内容
--------------------------------------------------------------------------------
下面是示例工程,这是一个Struts2.0.11应用
首先是web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>validateFrame.jsp</welcome-file> </welcome-file-list> </web-app>
然后是用于提供表单输入的validateFrame.jsp页面
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags"%> <s:form action="validateFrame" theme="simple"> <table border="9"> <tr> <td>姓名</td> <td><s:textfield name="username" id="usernameId"/></td> <%-- 在字段的后面显示错误信息,如此稍显人性化 --%> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>username</s:param> </s:fielderror> </td> </tr> <tr> <td>密码</td> <td><s:password name="password" id="passwordId"/></td> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>password</s:param> </s:fielderror> </td> </tr> <tr> <td>重复密码</td> <td><s:password name="repassword" id="repasswordId"/></td> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>repassword</s:param> </s:fielderror> </td> </tr> <tr> <td>年龄</td> <td><s:textfield name="age"/></td> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>age</s:param> </s:fielderror> </td> </tr> <tr> <td>生日</td> <td><s:textfield name="birthday"/></td> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>birthday</s:param> </s:fielderror> </td> </tr> <tr> <td>毕业时间</td> <td><s:textfield name="graduation"/></td> <td> <s:fielderror cssStyle="font-size:20px;color:red;text-align:left;font-weight:bold"> <s:param>graduation</s:param> </s:fielderror> </td> </tr> <tr> <td> </td> <td><s:submit value="校验框架测试"/></td> </tr> </table> </s:form>
当表单输入域均正确时的validateSuccess.jsp页面
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib prefix="s" uri="/struts-tags"%> 姓名:<s:property value="username"/><br/> 密码:<s:property value="password"/><br/> 年龄:<s:property value="age"/><br/> 生日:<s:property value="birthday"/><br/> 毕业:<s:property value="graduation"/>
然后是struts.xml文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="struts2" extends="struts-default"> <action name="validateFrame" class="com.jadyer.action.ValidateFrameAction" method="test"> <result>/validateSuccess.jsp</result> <result name="input">/validateFrame.jsp</result> </action> </package> </struts>
然后是用到的Action类
package com.jadyer.action; import java.util.Date; import com.opensymphony.xwork2.ActionSupport; @SuppressWarnings({"serial", "unused"}) public class ValidateFrameAction extends ActionSupport { private String username; private String password; private String repassword; private int age; private Date birthday; private Date graduation; /* 以上六个属性的setter和getter略 */ public String execute() throws Exception { System.out.println("------execute is invoked------"); return SUCCESS; } public String test() throws Exception { System.out.println("------test is invoked------"); return SUCCESS; } }
最后是用到的验证框架文件ValidateFrameAction-validation.xml
1.<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> <!-- *************************************************************************************************** --> <!-- 一个<field>表示对Action中的一个属性的校验,它的name值要与Action的属性名一致 --> <!-- <field-validator/>表示对属性校验的方式 --> <!-- requiredstring用来校验String的属性是必填的,此外都要用required来验证必填 --> <!-- 校验失败时,即用户输入的信息不符合要求时,就会显示<message/>中设定的提示信息 --> <!-- *************************************************************************************************** --> <!-- 而<param name="trim">true</param>则表示忽略掉字符串左右两边出现的空格 --> <!-- 事实上requiredstring会自动将字符串左右两边的空格去掉,所以它可写可不写 --> <!-- 因为在RequiredStringValidator源码中的doTrim属性默认的就是TRUE --> <!-- 虽然RequiredStringValidator中的属性名是doTrim,但不可以写成name="doTrim" --> <!-- 因为它真正的值取决于public void setTrim(boolean trim)中的参数的值 --> <!-- *************************************************************************************************** --> <!-- <field-validator/>还有一个short-circuit属性,用来表示短路,默认值为FALSE --> <!-- 对于逻辑与来说,前面如果为假,后面就不再判断了,直接短路 --> <!-- 对于逻辑或来说,前面如果为真,后面也不再判断了,直接短路 --> <!-- 所以,如果short-circuit的值为TRUE的话 --> <!-- 当username的requiredstring验证失败,就不再执行stringlength验证了 --> <!-- *************************************************************************************************** --> <!-- 可以使用stringlength验证字符串的长度,并用minLength和maxLength限定长度范围 --> <!-- 运行时${minLength}就会自动被6替换掉,${maxLength}就会被10替换掉 --> <!-- 也可以利用<message/>进行国际化,如<message key="username.xml.invalid"/> --> <!-- 无论是字段验证还是非字段验证,产生的<message/>信息都会放到FieldError中 --> <!-- 所以需要在前台页面中使用<s:fielderror/>来显示错误提示信息 --> <!-- *************************************************************************************************** --> <validators> <field name="username"> <field-validator type="requiredstring" short-circuit="true"> <param name="trim">true</param> <message>username should not be blank</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">10</param> <message>username should be between ${minLength} and ${maxLength}</message> </field-validator> </field> <!-- **********这里只列举出username的非字段校验写法,其余字段写法与其类似********* --> <!-- <validator type="requiredstring"> <param name="fieldName">username</param> <message>username should not be blank!</message> </validator> <validator type="stringlength"> <param name="fieldName">username</param> <param name="minLength">6</param> <param name="maxLength">10</param> <message>username should be between ${minLength} and ${maxLength}</message> </validator> --> <field name="password"> <field-validator type="requiredstring"> <message>password should not be blank</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">10</param> <message>password should be between ${minLength} and ${maxLength}</message> </field-validator> </field> <field name="age"> <field-validator type="int"> <param name="min">1</param> <param name="max">150</param> <message>age should be between ${min} and ${max}</message> </field-validator> </field> <field name="birthday"> <field-validator type="required"> <message>birthday should not be blank</message> </field-validator> <field-validator type="date"> <param name="min">2002-09-09</param> <param name="max">2003-08-28</param> <message>birthday should be between ${min} and ${max}</message> </field-validator> </field> </validators> <!-- **********关于repassword属性的校验,与password类似,故略去********** --> <!-- **********关于graduation属性的校验,与birthday类似,故略去********** -->
另附:ValidateFrameAction-validation.xml的补充说明
1.<!-- ****************** 【type="fieldexpression"】************************************************************************ --& gt; <!-- 下面的password.equals(repassword)不是字符串,而是OGNL表达式 --> <!-- 因为Action中的属性会被自动放到ValueStack里面,所以才可以在这里使用OGNL --> <!-- 通常人们不会将错误放到Action级别中,而是普遍性的都放到FieldError中 --> <!-- 在进行数据库验证,比如用户名已存在时等等,才把错误消息放到ActionError中 --> <!-- 另外,如果在Action使用的是领域模型接收表单参数的话,那么这里就应该写成user.password等等 --> <!-- 因为放在ValueStack里面的是user对象,而不是password属性 --> <field name="password"> <field-validator type="fieldexpression"> <param name="expression">password.equals(repassword)</param> <message>密码不一致</message> </field-validator> </field> <!-- ****************** 【type="visitor"】******************************************************************************** --& gt; <!-- 使用visitor验证类型可以实现验证框架的复用,所以人们比较喜欢这个验证类型 --> <!-- 在使用的时候,首先<field name="">的name不是某一个字段,而是一个对象 --> <!-- 而且该对象必须在validateFrameAction中出现,并提供setXxx和getXxx方法 --> <!-- 比如User对象,在validateFrameAction中提供了private User user;及对应的setUser()和getUser()方法 --> <!-- 然后新建在com.zhangbing.bean.User.java的包中,即在com.zhangbing.bean包中建立User-validation.xml文件 --> <!-- 也就是说此时是对对象进行验证,然后Struts2会根据Action中的对象找到与之对应的Bean类,也称VO类 --> <!-- 然后就会找到与之同名的User-validation.xml文件,这个文件里面写的就是公共的验证信息 --> <!-- 而User-validation.xml返回的信息会通过type="visitor"被嵌套在我们当前的文件中 --> <!-- 然后我们在页面中使用<s:fielderror/>标签就可以输出验证框架所产生的错误信息了 --> <field name="user"> <field-validator type="visitor"> <message>用户:</message> </field-validator> </field> <!-- **********【下面简单示例一下User-validation.xml中的内容】********** --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd"> <validators> <field name="username"> <field-validator type="requiredstring" short-circuit="true"> <param name="trim">true</param> <message>username should not be blank</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">10</param> <message>username should be between ${minLength} and ${maxLength}</message> </field-validator> </field> </validators> <!-- ****************** 【<param name="context">user</param& gt;】************************************************************ --> <!-- context参数用来指定上下文的名字,比如这里定义的上下文是user,则对应User-user-validation.xml校验文件 --> <!-- 其中<param name="appendPrefix">true</param>和<message>user's </message>经常成对出现 --> <!-- 参数appendPrefix用来指定是否在显示给用户的错误消息上添加前缀,而<message>用来指定所添加的文本 --> <field name="user"> <field-validator type="visitor"> <param name="context">user</param> <param name="appendPrefix">true</param> <message>user's </message> </field-validator> </field> <!-- ******************************************************************************************************************** -->
相关推荐
本文将深入探讨Struts2校验框架的应用,包括其核心概念、配置方式、以及如何在实际项目中实现数据验证。 **1. Struts2校验框架基础** Struts2的校验框架是基于Apache Commons Validator库的,它允许开发者定义校验...
Struts2 验证框架详解 Struts2 验证框架是基于 Java 语言的 Web 应用程序框架,提供了一个强大的验证机制,以确保用户输入的数据满足业务逻辑的要求。在 Struts2 中,验证机制是通过 validator 来实现的,该机制...
Struts2提供了内置的验证框架,允许开发者自定义校验规则,以满足特定业务需求。下面将详细介绍Struts2自定义校验框架的相关知识点。 1. **Struts2验证框架概述** Struts2的验证框架主要负责处理用户提交的数据,...
在Struts2中,校验框架是基于Action类的,每个Action类可以关联一个或多个校验配置文件,这些文件通常以.xml或.properties格式存在。这些文件定义了字段级别的验证规则,如非空检查、长度限制、数据类型检查等。当...
Struts2的校验框架提供了一种灵活而强大的方式来处理Web应用程序中的验证需求。通过合理地配置Struts2框架和正确地实现验证逻辑,可以显著提高应用程序的质量和用户体验。此外,Struts2还支持动态验证和国际化等多种...
在Struts2中,校验器(Validator)是处理用户输入验证的核心组件,确保提交到服务器的数据符合预设的业务规则。这篇博客文章可能是关于如何使用Struts2的内置校验机制以及自定义校验规则的探讨。 Struts2的校验框架...
在Struts2框架中,验证框架是其一个重要组成部分,用于确保输入数据的准确性和完整性,从而防止因无效数据导致的程序异常或错误。本实例将带你深入理解Struts2验证框架的使用,以及在实际操作中可能遇到的问题和解决...
在Struts2中,验证框架是其核心特性之一,它允许开发者对用户输入的数据进行校验,确保数据的完整性和安全性。这个“struts2验证框架示例”提供了深入理解并实际操作Struts2验证功能的机会。 首先,让我们了解一下...
struts2.0的数据校验框架struts2.0的数据校验框架struts2.0的数据校验框架struts2.0的数据校验框架
在传统的MVC模式中,数据验证通常在控制器或模型层进行,但在Struts2中,校验逻辑被移到了专门的校验框架中,这样可以提高代码的可复用性和可维护性。Struts2的校验框架支持两种验证方式:基于XML的验证和基于注解的...
Struts2应用开发系列Struts2的校验框架
在Struts2中,验证框架是处理用户输入验证的关键部分,它确保了从客户端接收到的数据的质量和准确性。本示例将深入探讨Struts2验证框架的基本用法,特别适合初学者理解和掌握。 ### 一、Struts2验证框架概述 ...
输入校验的流程在Struts2中是结构化的,涉及多个步骤: 1. **类型转换**:首先,Struts2框架使用类型转换器将HTTP请求中的字符串参数转换为相应的Java类型,如Integer、Date等,并将这些值设置为Action类的属性。 ...
在Struts 2中,模型通常由JavaBean或领域对象表示,它们处理数据的增删改查,并通过Action类与控制器交互。 2. **视图(View)**:视图是用户界面,负责呈现模型中的数据。Struts 2支持多种视图技术,如JSP、...
在Struts2中,校验框架是用于确保用户输入数据有效性和完整性的关键部分。这篇博文主要讨论了Struts2的验证框架及其工作原理。 Struts2的验证框架允许开发者定义一套规则来验证用户提交的数据,这些规则通常存储在...
5. **拦截器**:在Struts2中,验证过程是通过Interceptor(拦截器)机制实现的。`ValidationInterceptor`是执行验证的核心拦截器,它会在Action执行前检查数据,如果验证失败,则不会执行Action,而是直接跳转到错误...
在Struts2中,可以通过配置不同的校验器来实现对各种数据类型的校验。 3. **Field Validator**:字段校验器,用于校验表单中的具体字段。每个字段校验器都有特定的类型,如`requiredstring`表示必填字段,`string...
Struts2应用开发系列Struts2的校验框架
Struts2应用开发系列Struts2的校验框架