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 (); productForm.setName( request.getParameter("name" )); productForm.setDescription( request.getParameter("description" )); productForm.setPrice(request.getParameter("price" )); Product product = new Product (); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(Float.parseFloat( productForm.getPrice())); } catch (NumberFormatException e) {} request.setAttribute("product" , product); return "/WEB-INF/jsp/ProductDetails.jsp" ; } }
如果校验发现有错误,则SaveProductController
的handleRequest
方法会转发到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
页面,使其可以显示错误信息以及错误的输入。
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
页面。