输入验证与数据绑定
实例目标:实现用户注册功能。
流程:
1. 提供一个界面供用户输入注册信息,下面是一个简化的注册界面,仅提供了用
户名和密码的设置
2. 如果用户注册信息有误,显示错误界面,要求用户检查输入后重新注册。
3. 注册成功,显示操作成功提示。
实例内容
a) 配置文件
首先,web.xml文件配置分发器如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/Config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/WEB-INF/jsp/errorpage.jsp</location>
</error-page>
<error-page>
<exception-type>500</exception-type>
<location>/WEB-INF/jsp/errorpage.jsp</location>
</error-page>
</web-app>
在这个实例中,我们选用JSTLView作为我们的表现层实现。对应的配置文件如下。
Config.xml:
<beans>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalRes
ourceViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.JstlView
</value>
</property>
<property name="prefix">
<value>/WEB-INF/view/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="RegisterValidator" ⑴
class="net.xiaxin.validator.RegisterValidator"/>
<bean id="RegisterAction"
class="net.xiaxin.action.RegisterAction">
<property name="commandClass">
<value>net.xiaxin.reqbean.RegisterInfo</value>
</property>
<property name="validator"> ⑵
<ref local="RegisterValidator"/>
</property>
<property name="formView"> ⑶
<value>register</value>
</property>
<property name="successView"> ⑷
<value>RegisterSuccess</value>
</property>
</bean>
<!--Request Mapping -->
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUr
lHandlerMapping">
<property name="mappings">
<props>
<prop key="/register.do">RegisterAction</prop>
</props>
</property>
</bean>
</beans>
这个配置文件与篇首MVC介绍中所用实例大同小异。不同之处在于我们在这里引入了数
据验证配置节点:
⑴ 配置了一个数据验证Bean: RegisterValidator
net.xiaxin.validator.RegisterValidator
⑵ 为逻辑处理单元RegisterAction定义输入数据校验Bean
这里通过一个Bean引用,将RegisterValidator配置为本Action的数据校验
类。
⑶ 指定本处理单元的显示界面。
formView是RegisterAction的父类SimpleFormController中定义的属
性,指定了本处理单元的显示界面。
这里即用户访问register.do时将显示的注册界面。
要注意的是,完成此界面后,我们必须通过“…/register.do”访问注册界面,
而不是“…/register.jsp”,因为我们必须首先借助Spring完成一系列初始
化工作(如创建对应的状态对象并与之关联)之后,register.jsp才能顺利执
行,否则我们会得到一个应用服务器内部错误。
⑷ 指定成功返回界面。
successView同样是RegisterAction的父类SimpleFormController中定
义的属性,它指向成功返回界面。
b) 数据验证类
在Spring中,所有的数据验证类都必须实现接口:
org.springframework.validation.Validator
Validator接口定义了两个方法:
Ø boolean supports(Class clazz);
用于检查当前输入的数据类型是否符合本类的检验范围。Spring调用
Validator实现类时,首先会通过这个方法检查数据类型是_____否与此Validator
相匹配。
Ø void validate(Object obj, Errors errors);
数据校验方法。Validator实现类通过实现这个方法,完成具体的数据校验逻辑。
RegisterValidator.java:
public class RegisterValidator implements Validator {
public boolean supports(Class clazz) {
return RegisterInfo.class.isAssignableFrom(clazz); ⑴
}
public void validate(Object obj, Errors errors) {
RegisterInfo regInfo = (RegisterInfo) obj; ⑵
//检查注册用户名是否合法
if (regInfo.getUsername().length() < 4) {
errors.rejectValue("username", ⑶
"less4chars",
null,
"用户名长度必须大于等于4个字母!");
}
/*检查用户名是否已经存在
if (UserDAO.getUser(regInfo.getUsername()) != null) {
errors.rejectValue("username",
"existed",
null,
"用户已存在!");
}
*/
if (regInfo.getPassword1().length() < 6) {
errors.rejectValue("password1",
"less6chars",
null,
"密码长度必须大于等于6个字母");
}
if (!regInfo.getPassword2().equals(regInfo.getPassword1()))
{
errors.rejectValue("password2",
"notsame",
null,
"两次输入的密码不一致!");
}
}
}
⑴ 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还有另外几个简化版本,可根据情况选用。
其中RegisterInfo类定义如下:
public class RegisterInfo {
private String username;
private String password1;
private String password2;
public String getPassword1() {
return password1;
}
public void setPassword1(String password1) {
this.password1 = password1;
}
public String getPassword2() {
return password2;
}
public void setPassword2(String password2) {
this.password2 = password2;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
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在这方面还是显得有些繁琐。