1 验证概述
输入验证是Spring处理的最重要Web开发任务之一。
在Spring MVC中,有两种方式可以验证输入,利用Spring自带的验证框架,或者利用JSR 303实现。
验证器作用于Object级。它决定某一个对象中的所有field是否均是有效的,以及是否遵循某些规则。
如果一个应用程序中既使用了Formatter,又有validator(验证器),那么,调用的顺序是这样的:在调用controller期间,将会有一个或者多个Formatter,试图将输入字符串换成domain对象中的field值,一旦格式化成功,验证器就会介入。
例如,Order对象可能会有一个shippingDate属性(其类型显然为Date),它的值绝对不可能早于今天的日期。当调用OrderController时,DateFormatter会将字符串转化成Date,并将它赋予Order对象的shippingDate属性。如果转换失败,用户就会被转回到前一个表单。如果转换成功,则会调用转换器,查看shippingDate是否早于今天的日期。
现在,你或许会问,将验证逻辑移到DataFormatter中是否更加明智?因为比较一下日期并非难事,但答案却是否定的。
首先,DataFormatter还可以将其他字符串格式化成日期,如birthDate或者purchaseDate,这两个日期的规则都不同于shippingDate。事实上,比如,员工的出生日期绝对不可能睌于今日。
其次,验证器可以检验两个或更多字段之间的关系,各个字段均受不同Formatter的支持。例如,假设Employee对象有BirthDate属性和startDate,验证器就可以设定规则,使任何员工的入职日期均不可能早于他或她的出生日期。因此有效的Employee对象必须让它的birthDate属性值早于其startDate值。这就是验证器的任务。
2 Spring验证器
从一开始,Spring就设计了输入验证,甚至早于JSR 303(Java验证规范)。因此,Spring的Validation框架至今都很普遍,尽管对于新项目,一般建议使用JSR 303验证器。
为了创建Spring验证器,要实现org.sprin gfram ework.validation.Validator接口。这个接口有supports和validate两个方法。
package org.springframework.validation;
public interface Validator{
boolean supports<Class<?> clazz>
void validate(Object target, Errors errors);
}
如果验证器可以处理指定的Class,supports方法将返回true。Validate方法会验证目标对象,并将验证错误填入Errors对象。
Errors对象是org.springframework.validation.Errors接口的一个实例。Errors对象中包含了一系列FieldError和ObjectError对象。FieldError表示与被验证对象中的某介属性相关的一个错误。例如,如果产品的price属性必须为负数,并且Product对象被验证为负数,那么就需要创建一个FieldError。例如,在欧洲出售的一本Book,却在美国的网店上购买,那么就会出现一个ObjectError。
编写验证器时,不需要直接创建Error对象,因为实例化ObjectError或FieldError花费了大量的编程精力。这是由于ObjectError类的构造器需要4个参数,FieldError类的构造器则需要7个参数,如以下构造器签名所示:
ObjectError(String objectName, String[] codes, Object[] arguments, String defaultMessage)
FieldError(String objectName,String field, Oject rejectedValue, Boolean bindingFailure,
String[] codes, Object[] arguments, String defaultMessage)
给Errors对象添加错误的最容易的方法是:在Errors对象上调用一个reject或者蝎ectValue方法。调用reject,往FieldError中添加一个ObjectError和rejectValue。
3 Spring的Validator案例
3.1 案例的程序目录
3.2 domain
package app07a.domain;
import java.io.Serializable;
import java.util.Date;
public class Product implements Serializable {
private static final long serialVersionUID = 748392348L;
private String name;
private String description;
private Float price;
private Date productionDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public Date getProductionDate() {
return productionDate;
}
public void setProductionDate(Date productionDate) {
this.productionDate = productionDate;
}
}
3.3 controller控制器
package app07a.controller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import app07a.domain.Product;
import app07a.validator.ProductValidator;
@Controller
public class ProductController {
private static final Log logger = LogFactory.getLog(ProductController.class);
@RequestMapping(value = "/product_input")
public String inputProduct(Model model) {
model.addAttribute("product", new Product());
return "ProductForm";
}
@RequestMapping(value = "/product_save")
public String saveProduct(@ModelAttribute Product product,
BindingResult bindingResult, Model model) {
logger.info("product_save");
System.out.println("prod save");
ProductValidator productValidator = new ProductValidator();
productValidator.validate(product, bindingResult);
if (bindingResult.hasErrors()) {
FieldError fieldError = bindingResult.getFieldError();
logger.info("Code:" + fieldError.getCode() + ", field:"
+ fieldError.getField());
return "ProductForm";
}
// save product here
model.addAttribute("product", product);
return "ProductDetails";
}
}
3.4 formatter格式化器
package app07a.formatter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.format.Formatter;
public class DateFormatter implements Formatter<Date> {
private String datePattern;
private SimpleDateFormat dateFormat;
public DateFormatter(String datePattern) {
this.datePattern = datePattern;
dateFormat = new SimpleDateFormat(datePattern);
dateFormat.setLenient(false);
}
@Override
public String print(Date date, Locale locale) {
return dateFormat.format(date);
}
@Override
public Date parse(String s, Locale locale) throws ParseException {
try {
return dateFormat.parse(s);
} catch (ParseException e) {
// the error message will be displayed when using <form:errors>
throw new IllegalArgumentException(
"invalid date format. Please use this pattern\""
+ datePattern + "\"");
}
}
}
3.5 validator验证器
package app07a.validator;
import java.util.Date;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import app07a.domain.Product;
public class ProductValidator implements Validator {
@Override
public boolean supports(Class<?> klass) {
return Product.class.isAssignableFrom(klass);
}
@Override
public void validate(Object target, Errors errors) {
Product product = (Product) target;
ValidationUtils.rejectIfEmpty(errors, "name", "productname.required");
ValidationUtils.rejectIfEmpty(errors, "price", "price.required");
ValidationUtils.rejectIfEmpty(errors, "productionDate", "productiondate.required");
Float price = product.getPrice();
if (price != null && price < 0) {
errors.rejectValue("price", "price.negative");
}
Date productionDate = product.getProductionDate();
if (productionDate != null) {
// The hour,minute,second components of productionDate are 0
if (productionDate.after(new Date())) {
System.out.println("salah lagi");
errors.rejectValue("productionDate", "productiondate.invalid");
}
}
}
}
3.6 Spring MVC配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="app07a.controller" />
<context:component-scan base-package="app07a.formatter" />
<mvc:annotation-driven conversion-service="conversionService" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/*.html" location="/" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/resource/messages" />
</bean>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<set>
<bean class="app07a.formatter.DateFormatter">
<constructor-arg type="java.lang.String" value="MM-dd-yyyy" />
</bean>
</set>
</property>
</bean>
</beans>
3.7 View视图层
3.7.1 ProductForm.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<form:form commandName="product" action="product_save" method="post">
<fieldset>
<legend>Add a product</legend>
<p class="errorLine">
<form:errors path="name" cssClass="error"/>
</p>
<p>
<label for="name">*Product Name: </label>
<form:input id="name" path="name" tabindex="1"/>
</p>
<p>
<label for="description">Description: </label>
<form:input id="description" path="description" tabindex="2"/>
</p>
<p class="errorLine">
<form:errors path="price" cssClass="error"/>
</p>
<p>
<label for="price">*Price: </label>
<form:input id="price" path="price" tabindex="3"/>
</p>
<p class="errorLine">
<form:errors path="productionDate" cssClass="error"/>
</p>
<p>
<label for="productionDate">*Production Date: </label>
<form:input id="productionDate" path="productionDate" tabindex="4"/>
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="5">
<input id="submit" type="submit" tabindex="6" value="Add Product">
</p>
</fieldset>
</form:form>
</div>
</body>
</html>
3.7.2 ProductDatails.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br/>
Description: ${product.description}<br/>
Price: $${product.price}
</p>
</div>
</body>
</html>
3.7.3 ProductView.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>View Product</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
<h4>${message}</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br/>
Description: ${product.description}<br/>
Price: $${product.price}
</p>
</div>
</body>
</html>
3.8 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>YJYSpring_MVC_Validator_PaulDeckCH07</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/springmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4 运行和测试
在浏览器中输入
http://localhost:8080/YJYSpring_MVC_Validator_PaulDeckCH07/product_input
如果不输入任何信息,就提交显示如下界面
如果输入部分错误信息
输入正确信息,显示如下界面,其中日期不能晚于今天