16.5 校验器

16.5 校验器

Web应用执行action时,很重要的一个步骤就是进行输入校验。校验的内容可以是简单的,如检查一个输入是否为空,也可以是复杂的,如校验信用卡号。实际上,因为校验工作如此重要,Java社区专门发布了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,将Java的输入检验进行标准化。现代的MVC框架通常同时支持编程式和申明式两种校验方法。在编程式中,需要通过编码进行用户输入校验,而在声明式中,则需要提供包含校验规则的XML文档或者属性文件。
本节的新应用(app16c)扩展自app16b。图16.6展示了app16c的目录结构。
app16c应用的结构与app16b应用的结构基本相同,但多了一个ProductValidator类以及两个JSTL jar包(位于WEB-INF/lib目录下)。关于JSTL,将留到第9章“JSTL”中深入讨论。本节,我们仅需知道 JSTL 的作用是在ProductForm.jsp页面中显示输入校验的错误信息。
关于ProductValidator类,详见清单16.10。

ProductValidator类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package app16c.validator;
import java.util.ArrayList;
import java.util.List;
import app16c.form.ProductForm;
public class ProductValidator
{
public List<String> validate(ProductForm productForm)
{
List<String> errors = new ArrayList<String>();
String name = productForm.getName();
if(name == null || name.trim().isEmpty()) {
errors.add("Product must have a name");
}
String price = productForm.getPrice();
if(price == null || price.trim().isEmpty()) {
errors.add("Product must have a price");
} else {
try {
Float.parseFloat(price);
} catch (NumberFormatException e) {
errors.add("Invalid price value");
}
}
return errors;
}
}

注意:
ProductValidator类中有一个操作ProductForm对象的validate方法,确保产品的名字非空,其价格是一个合理的数字。validate方法返回一个包含错误信息的字符串列表,若返回一个空列表,则表示输入合法。
应用中唯一需要用到产品校验的地方是保存产品时,即SaveProductController类。现在,我们为SaveProductController类引入ProductValidator类。

新版的SaveProductController类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package app16b.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import app16b.domain.Product;
import app16b.form.ProductForm;
public class SaveProductController
implements
Controller
{
@Override
public String handleRequest(HttpServletRequest request,
HttpServletResponse response)
{
ProductForm productForm = new ProductForm();
// populate form properties
productForm.setName(
request.getParameter("name"));
productForm.setDescription(
request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// create model
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
try {
product.setPrice(Float.parseFloat(
productForm.getPrice()));
} catch (NumberFormatException e) {}
// insert code to add product to the database
request.setAttribute("product", product);
return "/WEB-INF/jsp/ProductDetails.jsp";
}
}

如果校验发现有错误,则SaveProductControllerhandleRequest方法会转发到ProductForm.jsp页面。若没有错误,则创建一个Product对象,设置属性,并转到/WEB-INF/jsp/ ProductDetails.jsp页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        if (errors.isEmpty()) {
// create Product from ProductForm
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription()
);
product.setPrice(Float.parseFloat(
productForm.getPrice()));
// no validation error, execute action method
// insert code to save product to the database
// store product in a scope variable for the view
request.setAttribute("product", product);
return "/WEB-INF/jsp/ProductDetails.jsp";
} else {
//store errors and form in a scope variable for the
view
request.setAttribute("errors", errors);
request.setAttribute("form", productForm);
return "/WEB-INF/jsp/ProductForm.jsp";
}

当然,实际应用中,这里会有把Product保存到数据库或者其他存储类型的代码,但现在我们仅关注输入校验。
现在,需要修改app16c应用的ProductForm.jsp页面,使其可以显示错误信息以及错误的输入。

ProductForm.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<%@ 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(css/main.css);
</style>
</head>
<body>
<div id="global">
<c:if test="${requestScope.errors != null}">
<p id="errors">Error(s)!
<ul>
<c:forEach var="error" items="${requestScope.errors}">
<li>${error}</li>
</c:forEach>
</ul>
</p>
</c:if>
<form action="product_save.action" method="post">
<fieldset>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label> <input
type="text" id="name" name="name" tabindex="1">
</p>
<p>
<label for="description">Description: </label> <input
type="text" id="description" name="description"
tabindex="2">
</p>
<p>
<label for="price">Price: </label> <input
type="text" id="price" name="price" tabindex="3">
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5"
value="Add Product">
</p>
</fieldset>
</form>
</div>
</body>
</html>

现在访问product_input,测试app16c应用:
http://localhost:8080/app16c/product_input.action
若产品表单提交了非法数据,页面将显示相应的错误信息。图16.7显示了包含2条错误信息的ProductForm页面。