• 沒有找到結果。

通过rejectValue方法将错误信息加入Error列表,此错误信息将被页面捕获并 显示在错误提示界面上。

在文檔中 Spring 开发指南 (頁 55-68)

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。

在文檔中 Spring 开发指南 (頁 55-68)