public void validate(Object obj, Errors errors) {
⑶ 通过rejectValue方法将错误信息加入Error列表,此错误信息将被页面捕获并 显示在错误提示界面上。
errors.rejectValue("password2",
"notsame",
⑴ RegisterInfo.class.isAssignableFrom方法用于判定参数类别,当传入 Class对象与当前类类别相同,或是当前类的父类(或当前类实现的接口)时返回真。这 里我们将其用于对校验对象的数据类型进行判定(这里的判定条件为:校验对象必须是 RegisterInfo类的实例)。
⑵ RegisterInfo regInfo = (RegisterInfo) obj;
将输入的数据对象转换为我们预定的数据类型。
⑶ 通过rejectValue方法将错误信息加入Error列表,此错误信息将被页面捕获并 显示在错误提示界面上。
rejectVlaue方法有4个参数:
1. Error Code
显示错误时,将根据错误代码识别错误信息类型。
2. Message Key
上 面 关 于 ApplicationContext 的 国 际 化 支 持 时 , 我 们 曾 经 谈 及 MessageSource的使用,这里我们可以通过引入MessageSource实现提示信息 的参数化,此时,本参数将用作.properties文件中的消息索引。
3. Error Argument
如果提示信息中需要包含动态信息,则可通过此参数传递需要的动态信息对象。具 体参见ApplicationContext中关于国际化实现的描述。
4. Default Message
如果在当前MessageSource中没有发现Message Key对应的信息数据,则以此 默认值返回。
这里我们暂时尚未考虑国际化支持,所有的信息都将通过Default Message返 回。关于国际化支持请参见稍后章节。
另外rejectValue还有另外几个简化版本,可根据情况选用。
c) 注册界面
register.jsp提供了注册操作界面。它同时提供了最初的注册界面,当输入参数非 法时,同时也会显示错误信息,提示用户检查输入。
register.jsp:
<!-- 页面中使用了JSTL Core taglib 和Spring lib-->
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!-- 设定页面编译时采用gb2312编码,同时指定浏览器显示时采取gb2312解码-->
<%@ page pageEncoding="gb2312"
contentType="text/html;charset=gb2312"%>
<html>
<head>
<title>用户注册</title>
</head>
<body style="text-align: center">
<form method="POST" action="/register.do">
<spring:bind path="command.*">
<font color="#FF0000">
<c:forEach
items="${status.errorMessages}"
var="error">
错误: <c:out value="${error}"/><br>
</c:forEach>
</font>
</spring:bind>
<table border="0" width="450" height="101"
cellspacing="0" cellpadding="0" >
<tr>
<td height="27" width="408" colspan="2">
<p align="center"><b>用户注册</b></td>
</tr>
<tr>
<td height="23" width="104">用户名:</td>
<td height="23" width="450">
<spring:bind path="command.username">
<input type="text"
value="<c:out value="${status.value}"/>"
name="<c:out value="${status.expression}"/>"
>
(必须大于等于4个字符)
<br>
<c:if test="${status.error}">
<font color="#FF0000">
错误:
<c:forEach
items="${status.errorMessages}"
var="error">
<c:out value="${error}"/>
</c:forEach>
</font>
</c:if>
</spring:bind>
</td>
</td>
</tr>
<tr>
<td height="23" width="104">密码:</td>
<td height="23" width="450">
<spring:bind path="command.password1">
<input
type="password"
value="<c:out value="${status.value}"/>"
name="<c:out value="${status.expression}"/>"
>
(必须大于等于6个字符)
<br>
<c:if test="${status.error}">
<font color="#FF0000">
错误:
<c:forEach
items="${status.errorMessages}"
var="error">
<c:out value="${error}"/>
</c:forEach>
</font>
</c:if>
</spring:bind>
</td>
</tr>
<tr>
<td height="23" width="104">重复密码:</td>
<td height="23" width="450">
<spring:bind path="command.password2">
<input
type="password"
value="<c:out value="${status.value}"/>"
name="<c:out value="${status.expression}"/>"
>
<br>
<c:if test="${status.error}">
<font color="#FF0000">
错误:
<c:forEach
items="${status.errorMessages}"
var="error">
<c:out value="${error}"/>
</c:forEach>
</font>
</c:if>
</spring:bind>
</td>
</tr>
</table>
<p>
<input type="submit" value="提交" name="B1">
<input type="reset" value="重置" name="B2">
</p>
</form>
</body>
</html>
页面起始部分指定了页面中引入的taglib和页面编码方式,实际开发时应该将其独立到一个单 独的jsp文件中,并在各个jsp文件中include便于统一维护。
页面中关键所在,也就是<spring:bind> 标记的使用:
<spring:bind path="command.*">
<font color="#FF0000">
<c:forEach
items="${status.errorMessages}"
var="error">
错误: <c:out value="${error}"/><br>
</c:forEach>
</font>
</spring:bind>
spring.bind标记通过path参数与CommandClass对象相绑定。之后我们就可以对绑定的
CommandClass 对象的状 态信息进行 访问。上 面的片断 中,我们通 过通配符 “*” 将当前 spring.bind语义与command对象的所有属性相绑定,用于集中的错误信息显示,对应最终提 示界面中的(蓝框标注部分):
而在下面每个输入框下方,我们也提供了对应的错误提示,此时我们绑定到了特定的command 属性,如"command.username"。
这 里 的
"command"
是 Spring 中 的 默 认 CommandClass 名 称 , 用 于 引 用 当 前 页 面 对 应 的 CommandClass实例(当前语境下,也就是net.xiaxin.reqbean.RegisterInfo)。我们 也 可 以 配 置 CommandClass 引 用 名 称 , 在 Config.xml 中 RegisterAction 配 置 中 增 加 CommandName配置,如下:<bean id="RegisterAction"
class="net.xiaxin.action.RegisterAction">
<property name="commandName">
<value>RegisterInfo</value>
</property>
………
</bean>
之后我们就可以在页面中使用“RegisterInfo”替代现在的“command”对数据对象进 行引用。
(为了保持前后一致,下面我们仍旧以“command”为例)
绑定到username属性的<spring:bind>标记:
<spring:bind path="command.username">
<input
type="text"
value="<c:out value="${status.value}"/>"
name="<c:out value="${status.expression}"/>"
>
(必须大于等于4个字符)
<br>
<c:if test="${status.error}">
<font color="#FF0000">
错误:
<c:forEach
items="${status.errorMessages}"
var="error">
<c:out value="${error}"/>
</c:forEach>
</font>
</c:if>
</spring:bind>
可以看到,<spring:bind>语义内,可以通过${status.*}访问对应的状态属性。
${status.*} 对应的实际是类
org.springframework.web.servlet.support.BindStatus BindStatus类提供了与当前CommandClass对象绑定的状态信息,如:
${status.errorMessages}对应绑定对象属性的错误信息。
${status.expression}对应绑定对象属性的名称。
${status.value}对应绑定对象属性当前值。
具体描述可参见BindStatus类的Java Doc 文档。
下面是RegisterAction.java和成功返回界面RegisterSuccess.jsp,出于演示目的,这 两个文件都非常简单:
RegisterAction.java:
public class RegisterAction extends SimpleFormController {
protected ModelAndView onSubmit(Object cmd, BindException ex) throws Exception {
Map rsMap = new HashMap();
rsMap.put("logininfo",cmd);
return new ModelAndView(this.getSuccessView(),rsMap);
} }
RegisterSuccess.jsp:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<%@ page pageEncoding="gb2312"
contentType="text/html;charset=gb2312"%>
<html>
<body>
<p align="center">
<c:out value="${logininfo.username}"/> 注册成功!
</p>
</body>
</html>
可以看到,结合JSTL Core Taglib和Spring Taglib,我们实现了一个拥有数据校验 功能的注册界面。界面显示、数据校验、逻辑处理三大模块被清晰隔离互不干扰,相对传统的 jsp解决方案。系统的可维护性得到了大大提升。
不过,我们还必须注意到,spring:bind标记对界面代码的侵入性较大,可以看到页面中 混杂了大量的Tag调用,这将对界面的修改和维护带来一定的困难。相对WebWork2而言,
Spring在这方面还是显得有些繁琐。
异常处理
Web应用中对于异常的处理方式与其他形式的应用并没有太大的不同――通过try/catch 语句针对不同的异常进行相应处理。
但是在具体实现中,由于异常层次、种类繁杂,我们往往很难在Servlet、JSP层妥善的处 理好所有异常情况,代码中大量的try/catch代码显得尤为凌乱。
我们通常面对下面两个主要问题:
对于Unchecked Exception而言,由于代码不强制捕获,往往被程序员所忽略,如果 运行期产生了Unchecked Exception,而代码中又没有进行相应的捕获和处理,则我 们可能不得不面对尴尬的500服务器内部错误提示页面。
Spring MVC中提供了一个通用的异常处理机制,它提供了一个成熟的,简洁清晰的异常处 理方案。如果基于Spring MVC开发Web应用,那么利用这套现成的机制进行异常处理也更加自 然和有效。
Spring MVC中的异常处理:
以 前 面 的 注 册 系 统 为 例 , 首 先 , 在Dispatcher 配 置 文 件 Config.xml 中 增 加 id 为
“exceptionResolver”的bean定义:
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingEx ceptionResolver">
<property name="defaultErrorView">
<value>failure</value>
</property>
<property name="exceptionMappings">
<props>
<prop key="java.sql.SQLException">showDBError</prop>
<prop key="java.lang.RuntimeException">showError</prop>
</props>
</property>
</bean>
通过SimpleMappingExceptionResolver我们可以将不同的异常映射到不同的jsp页 面(通过exceptionMappings属性的配置),同时我们也可以为所有的异常指定一个默认的异 常提示页面(通过defaultErrorView属性的配置),如果所抛出的异常在exceptionMappings 中没有对应的映射,则Spring将用此默认配置显示异常信息(注意这里配置的异常显示界面均 仅包括主文件名,至于文件路径和后缀已经在viewResolver中指定)。
一个典型的异常显示页面如下:
<html>
<head><title>Exception!</title></head>
<body>
<% Exception ex = (Exception)request.getAttribute("Exception"); %>
<H2>Exception: <% ex.getMessage();%></H2>
<P/>
<% ex.printStackTrace(new java.io.PrintWriter(out)); %>
</body>
</html>
如果 SimpleMappingExceptionResolver 无法满足异常处理的需要,我们可以针对
HandlerExceptionResolver接口实现自己异常处理类,这同样非常简单(只需要实现一个
resolveException方法)。
国际化支持
回忆之前章节中关于ApplicationContext 国际化支持的讨论,可以发现,如果在我们的 Web 应用中结合ApplicationContext 的国际化支持功能,就可以轻松实现 Web 应用在不同语言环境中 的切换,这对项目的可移植性(特别对于涉外项目)带来了极大的提升。
得益于Spring 良好的整体规划,在 Web 应用中实现国际化支持非常简单。下面我们就围绕这 个专题进行一些探讨。
首先,在配置文件 Config.xml 中增加如下节点:
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSo urce">
<property name="basename"><value>messages</value></property>
</bean>
非常简单,上面我们设定了一个messageSource 资源,并指定资源文件基名为“messages”。
关于messageSource 的介绍,请参见前面 ApplicationContext 国际化支持中的内容。
在/WEB-INF/classes/目录下,新建两个 properties 文件:
1) messages_zh_CN.properties 2) messages_en_US.properties 内容如下:
messages_zh_CN.properties
less4chars=用户名长度必须大于4个字符!
less6chars=密码长度必须大于6个字符!
notsame=两次密码输入不一致!
register_title=用户注册 username_label=用户名 password_label=密码 rep_pass_label=重复密码 error_msg=错误:
(注意messages_zh_CN.properties文件在部署时候须使用JDK工具native2ascii转码,
具体请参见ApplicationContext章节中国际化支持部分内容)
messages_en_US.properties:
less4chars=User name length must greater than 4 chars!
less6chars=Password length must greater than 6 chars!
notsame=Tow passwords are not consistent!
register_title=User Register username_label=Username password_label=Password
rep_pass_label=Repeat password error_msg=Error:
经过以上配置, “用户注册”实例的输入数据验证类RegisterValidator就可以自动使用 message_*.properties中的配置数据,如:
errors.rejectValue("username",
"less4chars",
null,
"用户名长度必须大于等于4个字母!");
会自动从当前的messageSource中寻找"less4chars"对应的键值。如果当前Locale为
“en_US”2,则界面上的错误信息将显示为“User name length must greater than 4 chars!”。(rejectValue方法总是先从当前messageSource配置中寻找符合目前Locale 配置的信息,如果找不到,才会返回参数中的默认描述信息。具体请参见“输入验证与数据绑定”
一节中的描述)
错误信息已经完成了国际化,那么,界面上的提示信息如何处理?
我们需要对原本的register.jsp 文件进行一些改造,通过<spring:message>标记将其中硬编 码的提示信息替换为动态Tag。如对于页面上方的“用户注册”标题,我们可做如下修改:
……
<tr>
<td height="27" width="408" colspan="2">
<p align="center">
<!--用户注册-->
<spring:message code="register_title"/>
</td>
</tr>
……
<spring:message>标记会自动从当前messageSource中根据code读取符合目前Locale
设置的配置数据。此时如果当前Locale为“en_US”,则原界面上方标题“用户注册”将显 示为messages_en_US.properties中配置的“User Register”。Locale的切换
实际操作中,针对不同语言要求进行切换的方式大多有以下几种:
Spring中目前提供了以下几种语言自动切换机制的实现(均实现了LocaleResolver接口):
Ø AcceptHeaderLocaleResolver
根据浏览器Http Header中的
accept-language
域判定(accept-language域中 一般包含了当前操作系统的语言设定,可通过HttpServletRequest.getLocale方法 获得此域的内容
)。Ø SessionLocaleResolver
2Windows 可以通过控制面板中的“区域和语言选项”快速切换系统 Locale 设定,Linux 可通 过 export LANG=zh_CN; LC_ALL=zh_CN.GBK 命令修改当前 Locale。
根据用户本次会话过程中的语言设定决定语言种类(如:用户登录时选择语言种 类,则此次登录周期内统一使用此语言设定)。
Ø CookieLocaleResolver
根据Cookie判定用户的语言设定(Cookie中保存着用户前一次的语言设定参 数)。
使用也非常简单,对于AcceptHeaderLocaleResolver而言,只需在配置文件 (Config.xml)中增加如下节点:
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.AcceptHeaderLocale Resolver">
</bean>
之后,Spring就会根据客户端浏览器的Locale设定决定返回界面所采用的语言种类。可通 过AcceptHeaderLocaleResolver.resolveLocale方法获得当前语言设定。
resolveLocale和setLocale方法是LocaleResolver接口定义的方法,用于提供对 Locale信息的存取功能。而对于AcceptHeaderLocaleResolver,由于客户机操作系统的 Locale为只读,所以仅提供了resolveLocale方法。
SessionLocaleResolver的配置类似与上例类似:
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResol ver">
</bean>
SessionLocaleResolver会自动在Session中维护一个名为
“org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE”的 属性,其中保存了当前会话所用的语言设定信息,同时对外提供了resolveLocale和
setLocale两个方法用于当前Locale信息的存取操作。
CookieLocaleResolver配置则包含了三个属性,cookieName、cookiePath 和 cookieMaxAge,分别指定了用于保存Locale设定的Cookie的名称、路径和最大保存时间。
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver"
>
<property name="cookieName">
<value>browserLocale</value>
</property>
<property name="cookiePath">
<value>mypath</value>
</property>
<property name="cookieMaxAge">
<value>999999</value>
</property>
</bean>
这几个属性并非必须配置,其默认值为分别为:
Ø cookieName
org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE Ø cookiePath
/
Ø cookieMaxAge
Integer.MAX_VALUE [2147483647]
之后,我们即可通过:CookieLocaleResolver.setLocale (HttpServletRequest request, HttpServletResponse response, Locale locale)方法保存Locale设定,
setLocale方法会自动根据设定在客户端浏览器创建Cookie并保存Locale信息。这样,下次 客户机浏览器登录的时候,系统就可以自动利用Cookie中保存的Locale信息为用户提供特定 语种的操作界面。
另外,Spring还提供了对语言切换事件的捕获机制(LocaleChangeInterceptor),由于 实际开发中使用较少,就不多做介绍,具体可参见Spring-Reference。