/**
作者:Willpower
来源:Rifoo Technology(http://www.rifoo.com)
时间:2006-01-13
备注:转载请保留以上声明
**/
上一节我们用Spring MVC实现了一个简单的应用,我们实现了列表显示,新增/编辑页面。在本节中,我们对原有程序进行加强,提供页面的验证逻辑,一个解析器(resolver)和输入表单。这样可以更好的组织我们的代码,使得我们可以在后期的维护中节省很多时间,同时,也可以使得逻辑流程比较清晰。
解析器(Resolvers)
随着我们的应用程序不断的扩大,将来我们可能会希望访问每个页面都能通过一个逻辑名称而不是一个完成的路径。解析器用来管理这些详细信息,比如扩展名和路径等,我们只需要在我们的配置文件中指定这些详细内容即可。
表单(Form)
表单可以很容易的管理用户的输入,方便的给数据提供存储,表单被MVC中V和C分开,也就是说它位于视图层和控制层之间。这种特殊的视图可以利用特殊的Spring标签库来将POJO属性和form字段进行绑定。
验证器(Validators)
这里所说的验证器其实是一些javabean,这些bean是伴随着输入表单一起的,可以很容易的实现业务逻辑验证的工作。它们和Spring Web MVC没有任何依赖,我们只需要实现一个接口就可以很容易将它们加入到我们的程序里来验证我们的业务逻辑。
下面,我们开始结合代码来学习如何改进我们的程序。
首先,我们需要配置一个解析器,这里要修改到上一节的rentaBikeApp-Servlet.xml文件,加入以下代码:
Example 2-13. rentaBikeApp-Servlet.xml
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="suffix"><value>.jsp</value></property>
</bean>
然后,我们需要修改上一节完成的控制器,该控制器目的还是去处理表单提交。在上一节里,我们在控制器里是通过HttpServletRequest的参数中去获得表单的值,但是现在我们要改为使用POJO的方式来获取。代码如下:
Example 2-14. SubmitBikeController.java
public class SubmitBikeController extends SimpleFormController {
private RentABike facade;
public RentABike getFacade( ) { return facade; }
public void setFacade(RentABike facade) { this.facade = facade; }
public ModelAndView onSubmit(Object command)
throws ServletException{
Bike bike = (Bike)command;
facade.saveBike(bike);
return new ModelAndView(new RedirectView(getSuccessView( )),
"rentaBike", facade);
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
Bike bike = new Bike( );
if(request.getParameter("bikeSerialNo") != null)
bike = facade.getBike(request.getParameter("bikeSerialNo"));
return bike;
}
}
这个控制器有一个onSubmit方法来替换原来的handleRequest方法。该方法直接获取包含表单值的POJO。另一个方法formBackingObject是在我们第一次请求表单时初始化POJO。一个SimpleFormController的运作全过程:
servlet请求->SimpleFormController.formBackingObject()->显示页面->提交页面-> SimpleFormController的Validator.validate()->SimpleFormController. onSubmit()->view导向
下面我们要编写一个输入表单,这里我们使用Spring的标签来实现,代码如下:
Example 2-15. editBike.jsp
<%@ page import="com.springbook.*"%>
<%@ include file="include.jsp" %>
<%@ taglib prefix="spring" uri="/spring" %>
<html>
<head>
<title>
Edit Bike
</title>
</head>
<body>
<h1>Edit Bike</h1>
<form method="POST">
<spring:hasBindErrors name="bike">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<table border="1" cellspacing="2" cellpadding="2">
<tr>
<td align="right">Manufacturer:</td>
<td>
<spring:bind path="bike.manufacturer">
<input type="text" name="manufacturer" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Model:</td>
<td>
<spring:bind path="bike.model">
<input type="text" name="model" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Frame:</td>
<td>
<spring:bind path="bike.frame">
<input type="text" name="frame" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Serial Number:</td>
<td>
<spring:bind path="bike.serialNo">
<input type="text" name="serialNo" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Weight:</td>
<td>
<spring:bind path="bike.weight">
<input type="text" name="weight" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Status:</td>
<td>
<spring:bind path="bike.status">
<input type="text" name="status" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
</body>
</html>
大家看看被<spring:bind>标签包起来的那些表单字段,这个可以让Spring自动映射这些表单字段的值到POJO对象中,如果表单验证失败,也会显示错误的消息。再看看最上面的<spring:hasBindErrors>标签,这个就是用来在验证错误时显示错误信息的。
下面,要让Spring来验证这个表单,我们需要编写一个验证器,代码如下:
Example 2-16. BikeValidator.java
public class BikeValidator implements Validator {
public boolean supports(Class aClass) {
return aClass.equals(Bike.class);
}
public void validate(Object o, Errors errors) {
Bike bike = (Bike)o;
if(bike == null) {
errors.rejectValue("manufacturer", "Error!",
null, "Value required.");
} else {
if(bike.getManufacturer( ) == null ||
"".equals(bike.getManufacturer( )))
errors.rejectValue("manufacturer", "Value not present.",
null, "Manufacturer required.");
if(bike.getModel( ) == null || "".equals(bike.getModel( )))
errors.rejectValue("model", "Value not present.", null,
"Model is required.");
}
}
}
当用户提交form时,servlet不是先运行PriceIncreaseFormController. onSubmit(),而是先运行了priceIncreaseValidator的support()和validate (Object obj, Errors errors)方法。如果在校验过程中(即validate (Object obj, Errors errors)方法中),如果发现有数据错误,那么就errors.rejectValue()方法给errors赋值。servlet一旦发现errors里面有值,就会中止程序运行而直接返回原来的页面,在页面的某处可以利用errors里面存储的值显示错误信息。如果校验通过,那么SimpleFormController的onSubmit方法将启动,其返回值就是将要去的页面。
我们现在修改上一节中完成的上下文配置文件,将/editBike.bikes指向editBikeForm,而不指向原来的editBikeController了。代码如下:
Example 2-17. rentaBikeApp-Servlet.xml, editBike.bikes should point to editBikeForm
<bean id="bikeValidator" class="com.springbook.BikeValidator"/>
<bean id="editBikeForm" class="com.springbook.SubmitBikeController">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>bike</value></property>
<property name="commandClass">
<value>com.springbook.Bike</value>
</property>
<property name="validator"><ref bean="bikeValidator"/></property>
<property name="formView"><value>editBike</value></property>
<property name="successView"><value>bikes.bikes</value></property>
<property name="facade">
<ref bean="rentaBike"/>
</property>
</bean>
配置文件修改完后,我们就需要修改上节中RentABike接口的实现类ArrayListRentABike了。
Example 2-18. ArrayListRentABike.java
package com.springbook;
import java.util.*;
public class ArrayListRentABike implements RentABike {
private String storeName;
final List bikes = new ArrayList( );
public void setStoreName(String name) {
this.storeName = name;
}
public String getStoreName( ) {
return storeName;
}
private void initBikes( ) {
bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));
bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222", 12,
"Excellent"));
bikes.add(new Bike("Trek", "6000", 19, "33333", 12.4, "Fair"));
}
public ArrayListRentABike( ) {
initBikes( );
}
public ArrayListRentABike(String storeName) {
this.storeName = storeName;
initBikes( );
}
public String toString( ) { return "com.springbook.RentABike: " + storeName; }
public List getBikes( ) { return bikes; }
public Bike getBike(String serialNo) {
Iterator iter = bikes.iterator( );
while(iter.hasNext( )) {
Bike bike = (Bike)iter.next( );
if(serialNo.equals(bike.getSerialNo( ))) return bike;
}
return null;
}
public void saveBike(Bike bike) {
deleteIfContains(bike);
bikes.add(bike);
}
public void deleteBike(Bike bike) {
deleteIfContains(bike);
}
private void deleteIfContains(Bike bike) {
Iterator iter = bikes.iterator( );
while(iter.hasNext( )) {
Bike comp = (Bike)iter.next( );
if(comp.getManufacturer( ).equals(bike.getManufacturer( )) &&
comp.getModel( ).equals(bike.getModel( ))) {
bikes.remove(comp);
return;
}
}
}
}
最后,我们需要在web.xml中加入Spring标签的引用代码即可:
Example 2-19. web.xml
<taglib>
<taglib-uri>/spring</taglib-uri>
<taglib-location>/WEB-INF/lib/spring.tld</taglib-location>
</taglib>
现在我们编译部署修改过的应用。大家不输入制造商和模型这两个字段,直接提交,会看到下面的结果。在字段的右边就是Spring标签显示的错误信息。

接下来我们要在下一篇内容中来测试上面修改过的代码。
作者:Willpower
来源:Rifoo Technology(http://www.rifoo.com)
时间:2006-01-13
备注:转载请保留以上声明
**/
上一节我们用Spring MVC实现了一个简单的应用,我们实现了列表显示,新增/编辑页面。在本节中,我们对原有程序进行加强,提供页面的验证逻辑,一个解析器(resolver)和输入表单。这样可以更好的组织我们的代码,使得我们可以在后期的维护中节省很多时间,同时,也可以使得逻辑流程比较清晰。
解析器(Resolvers)
随着我们的应用程序不断的扩大,将来我们可能会希望访问每个页面都能通过一个逻辑名称而不是一个完成的路径。解析器用来管理这些详细信息,比如扩展名和路径等,我们只需要在我们的配置文件中指定这些详细内容即可。
表单(Form)
表单可以很容易的管理用户的输入,方便的给数据提供存储,表单被MVC中V和C分开,也就是说它位于视图层和控制层之间。这种特殊的视图可以利用特殊的Spring标签库来将POJO属性和form字段进行绑定。
验证器(Validators)
这里所说的验证器其实是一些javabean,这些bean是伴随着输入表单一起的,可以很容易的实现业务逻辑验证的工作。它们和Spring Web MVC没有任何依赖,我们只需要实现一个接口就可以很容易将它们加入到我们的程序里来验证我们的业务逻辑。
下面,我们开始结合代码来学习如何改进我们的程序。
首先,我们需要配置一个解析器,这里要修改到上一节的rentaBikeApp-Servlet.xml文件,加入以下代码:
Example 2-13. rentaBikeApp-Servlet.xml
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="suffix"><value>.jsp</value></property>
</bean>
然后,我们需要修改上一节完成的控制器,该控制器目的还是去处理表单提交。在上一节里,我们在控制器里是通过HttpServletRequest的参数中去获得表单的值,但是现在我们要改为使用POJO的方式来获取。代码如下:
Example 2-14. SubmitBikeController.java
public class SubmitBikeController extends SimpleFormController {
private RentABike facade;
public RentABike getFacade( ) { return facade; }
public void setFacade(RentABike facade) { this.facade = facade; }
public ModelAndView onSubmit(Object command)
throws ServletException{
Bike bike = (Bike)command;
facade.saveBike(bike);
return new ModelAndView(new RedirectView(getSuccessView( )),
"rentaBike", facade);
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
Bike bike = new Bike( );
if(request.getParameter("bikeSerialNo") != null)
bike = facade.getBike(request.getParameter("bikeSerialNo"));
return bike;
}
}
这个控制器有一个onSubmit方法来替换原来的handleRequest方法。该方法直接获取包含表单值的POJO。另一个方法formBackingObject是在我们第一次请求表单时初始化POJO。一个SimpleFormController的运作全过程:
servlet请求->SimpleFormController.formBackingObject()->显示页面->提交页面-> SimpleFormController的Validator.validate()->SimpleFormController. onSubmit()->view导向
下面我们要编写一个输入表单,这里我们使用Spring的标签来实现,代码如下:
Example 2-15. editBike.jsp
<%@ page import="com.springbook.*"%>
<%@ include file="include.jsp" %>
<%@ taglib prefix="spring" uri="/spring" %>
<html>
<head>
<title>
Edit Bike
</title>
</head>
<body>
<h1>Edit Bike</h1>
<form method="POST">
<spring:hasBindErrors name="bike">
<b>Please fix all errors!</b>
</spring:hasBindErrors>
<table border="1" cellspacing="2" cellpadding="2">
<tr>
<td align="right">Manufacturer:</td>
<td>
<spring:bind path="bike.manufacturer">
<input type="text" name="manufacturer" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Model:</td>
<td>
<spring:bind path="bike.model">
<input type="text" name="model" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Frame:</td>
<td>
<spring:bind path="bike.frame">
<input type="text" name="frame" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Serial Number:</td>
<td>
<spring:bind path="bike.serialNo">
<input type="text" name="serialNo" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Weight:</td>
<td>
<spring:bind path="bike.weight">
<input type="text" name="weight" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
<tr>
<td align="right">Status:</td>
<td>
<spring:bind path="bike.status">
<input type="text" name="status" value="<c:out
value="${status.value}"/>">
<font color="red"><c:out
value="${status.errorMessage}"/></font>
</spring:bind>
</td>
</tr>
</table>
<input type="submit" value="Submit">
</form>
</body>
</html>
大家看看被<spring:bind>标签包起来的那些表单字段,这个可以让Spring自动映射这些表单字段的值到POJO对象中,如果表单验证失败,也会显示错误的消息。再看看最上面的<spring:hasBindErrors>标签,这个就是用来在验证错误时显示错误信息的。
下面,要让Spring来验证这个表单,我们需要编写一个验证器,代码如下:
Example 2-16. BikeValidator.java
public class BikeValidator implements Validator {
public boolean supports(Class aClass) {
return aClass.equals(Bike.class);
}
public void validate(Object o, Errors errors) {
Bike bike = (Bike)o;
if(bike == null) {
errors.rejectValue("manufacturer", "Error!",
null, "Value required.");
} else {
if(bike.getManufacturer( ) == null ||
"".equals(bike.getManufacturer( )))
errors.rejectValue("manufacturer", "Value not present.",
null, "Manufacturer required.");
if(bike.getModel( ) == null || "".equals(bike.getModel( )))
errors.rejectValue("model", "Value not present.", null,
"Model is required.");
}
}
}
当用户提交form时,servlet不是先运行PriceIncreaseFormController. onSubmit(),而是先运行了priceIncreaseValidator的support()和validate (Object obj, Errors errors)方法。如果在校验过程中(即validate (Object obj, Errors errors)方法中),如果发现有数据错误,那么就errors.rejectValue()方法给errors赋值。servlet一旦发现errors里面有值,就会中止程序运行而直接返回原来的页面,在页面的某处可以利用errors里面存储的值显示错误信息。如果校验通过,那么SimpleFormController的onSubmit方法将启动,其返回值就是将要去的页面。
我们现在修改上一节中完成的上下文配置文件,将/editBike.bikes指向editBikeForm,而不指向原来的editBikeController了。代码如下:
Example 2-17. rentaBikeApp-Servlet.xml, editBike.bikes should point to editBikeForm
<bean id="bikeValidator" class="com.springbook.BikeValidator"/>
<bean id="editBikeForm" class="com.springbook.SubmitBikeController">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>bike</value></property>
<property name="commandClass">
<value>com.springbook.Bike</value>
</property>
<property name="validator"><ref bean="bikeValidator"/></property>
<property name="formView"><value>editBike</value></property>
<property name="successView"><value>bikes.bikes</value></property>
<property name="facade">
<ref bean="rentaBike"/>
</property>
</bean>
配置文件修改完后,我们就需要修改上节中RentABike接口的实现类ArrayListRentABike了。
Example 2-18. ArrayListRentABike.java
package com.springbook;
import java.util.*;
public class ArrayListRentABike implements RentABike {
private String storeName;
final List bikes = new ArrayList( );
public void setStoreName(String name) {
this.storeName = name;
}
public String getStoreName( ) {
return storeName;
}
private void initBikes( ) {
bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));
bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222", 12,
"Excellent"));
bikes.add(new Bike("Trek", "6000", 19, "33333", 12.4, "Fair"));
}
public ArrayListRentABike( ) {
initBikes( );
}
public ArrayListRentABike(String storeName) {
this.storeName = storeName;
initBikes( );
}
public String toString( ) { return "com.springbook.RentABike: " + storeName; }
public List getBikes( ) { return bikes; }
public Bike getBike(String serialNo) {
Iterator iter = bikes.iterator( );
while(iter.hasNext( )) {
Bike bike = (Bike)iter.next( );
if(serialNo.equals(bike.getSerialNo( ))) return bike;
}
return null;
}
public void saveBike(Bike bike) {
deleteIfContains(bike);
bikes.add(bike);
}
public void deleteBike(Bike bike) {
deleteIfContains(bike);
}
private void deleteIfContains(Bike bike) {
Iterator iter = bikes.iterator( );
while(iter.hasNext( )) {
Bike comp = (Bike)iter.next( );
if(comp.getManufacturer( ).equals(bike.getManufacturer( )) &&
comp.getModel( ).equals(bike.getModel( ))) {
bikes.remove(comp);
return;
}
}
}
}
最后,我们需要在web.xml中加入Spring标签的引用代码即可:
Example 2-19. web.xml
<taglib>
<taglib-uri>/spring</taglib-uri>
<taglib-location>/WEB-INF/lib/spring.tld</taglib-location>
</taglib>
现在我们编译部署修改过的应用。大家不输入制造商和模型这两个字段,直接提交,会看到下面的结果。在字段的右边就是Spring标签显示的错误信息。

接下来我们要在下一篇内容中来测试上面修改过的代码。