表现层数据是由用户输入的,用户输入则是非常复杂的,正常用户的偶然错误,还有Cracker(破坏者)的恶意输入,都可能导致系统出现非正常情况。例如,在如图4.2所示的输入页面中,我们希望用户输入crazyit.org,leegang模式的字符串,希望用户输入的字符串包含一个英文逗号(,)作为用户名和密码的分隔符,如果用户输入多于一个的英文逗号,或者没有输入英文逗号,都将引起系统异常——因为上面的类型转换器将无法正常分解出用户名和密码。
实际上,表现层数据涉及的两个处理:数据校验和类型转换是紧密相关的,只有当输入数据是有效数据时,系统才可以进行有效的类型转换——当然,有时候即使用户输入的数据能进行有效转换,但依然是非法数据(假设需要输入一个人的年龄,输入200则肯定是非法数据)。因此,可以进行有效的类型转换是基础,只有当数据完成了有效的类型转换后,下一步才去做数据校验。
Struts 2提供了一个名为conversionError的拦截器,这个拦截器被注册在默认的拦截器栈中。我们查看Struts 2框架的默认配置文件struts-default.xml,该文件中有如下配置片段:
<interceptor-stack name="defaultStack">
<!-- 省略其他拦截器引用 -->
...
<!-- 处理类型转换错误的拦截器 -->
<interceptor-ref name="conversionError"/>
<!-- 处理数据校验的拦截器 -->
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<!-- 省略其他拦截器 -->
...
</interceptor-stack>
在上面的默认拦截器栈中包含了conversionError拦截器的引用,如果Struts 2的类型转换器执行类型转换时出现错误,该拦截器将负责将对应错误封装成表单域错误(FieldError),并将这些错误信息放入ActionContext中。
显然,conversionError拦截器实际上是AOP中的Throws处理(关于各种处理类型的定义和深入介绍,请参阅本书关于Spring的介绍)。Throws处理当系统抛出异常时启动,负责处理异常。通过这种方式,Struts 2的类型转换器中只完成类型转换逻辑,而无须关心异常处理逻辑。因此,我们看到上面的类型转换器无须进行任何异常处理逻辑。
图4.6显示了Struts 2类型转换中的错误处理流程。
图4.6只显示了类型转换器、conversionError拦截器和控制器之间的顺序图,并未完全刻画出系统中的其他成员。当conversionError拦截器对转换异常进行处理后,系统会跳转到名为input的逻辑视图。
图4.6 Struts 2类型转换中的错误处理流程
为了让Struts 2框架处理类型转换的错误,以及使用后面的数据校验机制,系统的Action类都应该通过继承ActionSupport类来实现。ActionSupport类为完成类型转换错误处理,数据校验实现了许多基础工作。
1.处理类型转换错误
下面将以最简单的局部类型转换器为例,介绍如何处理类型转换错误。
我们重新改写系统的Action类,让系统的Action类继承Struts 2的ActionSupport类。修改后的Action类代码片段如下。
程序清单:codes\04\4.1\errorHandler\WEB-INF\src\org\crazyit\app\action\LoginAction.java
//为了正常使用系统的类型转换错误处理机制,让Action类继承ActionSupport类
public class LoginAction
extends ActionSupport
{
//该类包含一个User类型属性,该属性用于封装名为user的请求参数
private User user;
private String tip;
//省略该类的其他成分
...
}
为了让Struts 2类型转换的错误处理机制生效,包括下一节的输入校验生效,都必须让Action继承Struts 2的ActionSupport基类,因为Struts 2的ActionSupport负责收集类型转换错误、输入校验错误,并将它们封装成FieldError对象,添加到ActionContext中。
前面已经提到,当类型转换出现异常时,conversionError拦截器会处理该异常,然后转入名为input的逻辑视图,因此应该为该Action增加名为input的逻辑视图定义。修改后的struts.xml文件代码如下。
程序清单:codes\04\4.1\errorHandler\WEB-INF\src\struts.xml
<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.1.7 //EN"
"http://struts.apache.org/dtds/struts- 2.1.7 .dtd">
<struts>
<!-- 配置国际化资源文件 -->
<constant name="struts.custom.i18n.resources" value="mess"/>
<package name="lee" extends="struts-default">
<action name="login" class="org.crazyit.app.action.LoginAction">
<!-- 配置名为input的逻辑视图,
当转换失败后转入该逻辑视图 -->
<result name="input">/input.jsp</result>
<result name="success">/welcome.jsp</result>
<result name="error">/welcome.jsp</result>
</action>
<action name="">
<result>.</result>
</action>
</package>
</struts>
上面的粗体字代码为input逻辑视图指定了物理视图资源:input.jsp。经过上面配置,如果用户输入不能成功转换成User对象,系统将转入input.jsp页面,等待用户再次输入。
前面已经讲述过,Struts 2会负责将转换错误封装成FieldError,并将其放在ActionContext中,这样就可以在对应视图中输出转换错误,在页面中使用<s:fielderror/>标签即可输出类型转换错误信息。
在默认情况下,使用<s:fielderror/>标签会输出形如Invalid field value for field xxx的错误提示信息,其中xxx是Action中属性名,也是该属性对应的请求参数的名。
对于中文环境而言,用户通常希望看到中文的提示信息,因此应该改变默认的提示信息。只需在应用的国际化资源文件中增加如下一行代码,即可改变默认的类型转换错误的提示信息。
#改变默认的类型转换失败后的提示信息
xwork.default.invalid.fieldvalue={0}字段类型转换失败!
因为上面的资源文件中包含了非西欧字符,因此必须使用native2ascii命令来处理该文件。
也就是说,Struts 2使用key为xwork.default.invalid.fieldvalue的消息作为标准的提示信息,并在input.jsp页面中增加如下代码:
<!-- 输出类型转换错误、输入校验提示 -->
<s:fielderror/>
改变了默认提示信息后,如果再次提交包含不能合理转换的请求参数,将看到如图4.7所示的页面。
图4.7 类型转换错误的提示信息
提示:
|
在某些时候,可能还需要对特定字段指定特别的提示信息,此时可通过Action的局部资源文件来实现,在文件中增加如下一项:
invalid.fieldvalue.<propName>=<tipMsg>
将其中<propName>替换成需要进行类型转换的属性名(此处的propName可以支持OGNL表达式,例如user.birth代表Action里user属性的birth属性),<tipMsg>替换成转换失败后的提示信息,上面的转换错误提示就会发挥作用了。
对于如图4.8所示的请求页面,其中包含了用户姓名、用户年龄和用户生日三个表单域,它们代表三个请求参数,这三个请求参数由Struts 2采用字符串、整数型和日期类型属性封装,因此必须涉及到类型转换!本应用的类型转换是基于OGNL表达式的类型转换。
图4.8 输入用户信息的输入页面
处理上面请求的Action类代码如下。
程序清单:codes\04\4.1\errorHandler2\WEB-INF\src\org\crazyit\app\action\LoginAction.java
public class LoginAction extends ActionSupport
{
private User user;
//user属性的setter和getter方法
public void setUser(User user)
{
this.user = user;
}
public User getUser()
{
return user;
}
//没有execute方法,直接使用ActionSupport的execute方法
}
因为要改变birth属性的类型转换失败的提示信息,所以我们为该Action提供一个局部资源文件,该文件内只包含如下一行代码。
程序清单:codes\04\4.1\errorHandler2\WEB-INF\src\org\crazyit\app\action\LoginAction.properties
#改变上面的Action中user属性的birth属性类型转换后的提示信息
invalid.fieldvalue.user.birth=生日信息必须满足yyyy-MM-dd格式
该文件的文件名为LoginAction.properties(用native2ascii处理后新文件名为LoginAction_zh_CN. properties),将该文件放在与LoginAction.class相同的位置(例如WEB-INF\classes\org\crazyit\app\action路径下)。如果我们在如图4.8所示的输入页面中输入了不能成功进行类型转换的字符串,将看到如图4.9所示的页面。
在图4.9中可以看到,输入的年龄参数无法正常转换,生日参数也无法正常转换,其中“age字段无效”是全局的转换错误提示,由xwork.default.invalid.fieldvalue消息提供,后面的生日字段的转换错误提示则是单独指定的。
|
上面的转换错误信息是红色的,而不是黑色的,
仅仅是因为笔者增加了一个<s:head/>标签,该标签可以导入xhtml主题所需要的一些CSS样式。
图4.9 输出类型转换的错误提示
2.处理集合属性的转换错误
如果Action里包含一个集合属性,只要Struts 2能检测到集合里元素的类型(可以通过局部类型转换文件指定,也可通过泛型方式指定),类型转换器就可以正常起作用。当类型转换器在执行类型转换过程中出现异常时,系统的conversionError拦截器就会处理该异常,处理结束后返回名为input的逻辑视图。
假设有如下Action处理类,该处理类里包含一个List集合属性。
程序清单:codes\04\4.1\ListErrorHandler\WEB-INF\src\org\crazyit\app\action\LoginAction.java
//使用Struts 2的类型转换的错误机制,应该继承ActionSupport
public class LoginAction extends ActionSupport
{
private List<User> users;
//users属性的setter和getter方法
public void setUsers(List<User> users)
{
this.users = users;
}
public List<User> getUsers()
{
return users;
}
}
对于上面的Action,该Action需要的users属性是一个List集合,我们有两种方式来传入请求参数:
Ø 只传入一个users请求参数,该请求参数的值是字符串数组的形式;
Ø 分别传入多个users[0]、users[1]…形式的请求参数,这种形式将会充分利用OGNL表达式类型转换机制。
对于第一种形式,因为只有一个请求参数,请求参数名为users,只要任何一个users请求参数不能成功转换成User对象,Struts 2都会提示users字段无效,如图4.10所示。
如果将三个请求参数的名字设为users[0]、users[1]…的形式,Struts 2将可以区分每个请求参数,从而显示更友好的转换错误提示。例如,我们将表单页的代码改为如下。
程序清单:codes\04\4.1\ListErrorHandler\ognlInput.jsp
<s:form action="login">
<s:iterator value="{0, 1, 2}" status="stat">
<!-- 将会依次生成多个请求参数 -->
<s:textfield name="users[%{#stat.index}]"
图4.10 集合属性类型转换失败
label="第%{#stat.index}个用户信息"/>
</s:iterator>
<tr>
<td colspan="2"><s:submit value="转换" theme="simple"/>
<s:reset value="重填" theme="simple"/></td>
</tr>
</s:form>
上面的页面代码我们使用了迭代器标签来指定三个表单域的name,三个表单域的name将分别是users[0]、users[1]、users[2],在这种情况下如果任一个表单域类型转换失败,将看到如图4.11所示的页面。
图4.11 集合属性类型转换失败