第 5 章 chapter 5 数据验证 学习目的与要求本章重点讲解 Spring MVC 框架的输入验证体系 通过本章的学习, 理解输入验证的流程, 能够利用 Spring 的自带验证框架和 JSR 303(Java 验证规范 ) 对数据进行验证 本章主要内容 数据验证概述 Spring 验证 JSR 303 验证所有用户的输入一般都是随意的, 为了保证数据的合法性, 数据验证是所有 Web 应用必须处理的问题 在 Spring MVC 框架中, 有两种方法可以验证输入数据 : 一是利用 Spring 自带的验证框架, 一是利用 JSR 303 实现 5.1 数据验证概述 数据验证分为客户端验证和服务器端验证, 客户端验证主要是过滤正常用户的误操作, 主要通过 JavaScript 代码完成 ; 服务器端验证是整个应用阻止非法数据的最后防线, 主要通过在应用中编程实现 5.1.1 客户端验证 大多数情况下, 使用 JavaScript 进行客户端验证的步骤如下 : (1) 编写验证函数 ; (2) 在提交表单的事件中调用验证函数 ; (3) 根据验证函数来判断是否进行表单提交 客户端验证可以过滤用户的误操作, 是第一道防线, 一般使用 JavaScript 代码实现 仅有客户端验证还是不够的 攻击者还可以绕过客户端验证直接进行非法输入, 这样可能会引起系统的异常, 为了确保数据的合法性, 防止用户通过非正常手段提交错误信息
第 5 章数据验证 71 所以必须加上服务器端的验证 5.1.2 服务器端验证 Spring MVC 的 Converter 和 Formatter 在进行类型转换时, 是将输入数据转换成领域对象的属性值 ( 一种 Java 类型 ), 一旦成功, 服务器端验证器就会介入 也就是说, 在 Spring MVC 框架中, 先进行数据类型转换, 再进行服务器端验证 服务器端验证对于系统的安全性 完整性 健壮性起到了至关重要的作用 在 Spring MVC 框架中, 可以利用 Spring 自带的验证框架验证数据, 也可以利用 JSR 303 实现数据验证 5.2 Spring 验证器 5.2.1 Validator 接口 创建自定义 Spring 验证器, 需要实现 org.springframework.validation.validator 接口 该接口有两个接口方法 : boolean supports(class<?> klass) void validate(object object, Errors errors) supports 方法返回 True 时, 验证器可以处理指定的 Class validate 方法的功能是验证目标对象 object, 并将验证错误消息存入 errors 对象 往 errors 对象存入错误消息的方法是 reject 或 rejectvalue 方法 这两个方法的部分重载方法如下 : void reject(string errorcode) void reject(string errorcode, String defaultmessage) void rejectvalue(string field, String errorcode) void rejectvalue(string field, String errorcode, String defaultmessage) 一般情况下, 只需要给 reject 或 rejectvalue 方法一个错误代码,Spring MVC 框架就会在消息属性文件中查找错误代码, 获取相应错误消息 具体示例如下 : if(goods.getgprice() > 100 goods.getgprice() < 0){ errors.rejectvalue("gprice", "gprice.invalid");//gprice.invalid // 为错误代码 5.2.2 ValidationUtils 类 org.springframework.validation.validationutils 是一个工具类, 该类中有几个方法可以
72 S pring MVC 开发技术指南 帮助判定值是否为空 例如 : if(goods.getgname() == null goods.getgname().isempty()){ errors.rejectvalue("gname", "goods.gname.required") 上述 if 语句可以使用 ValidationUtils 类的 rejectifempty 方法, 代码如下 : // errors 为 Errors 对象 // gname 为 goods 对象的属性 ValidationUtils.rejectIfEmpty(errors, "gname", "goods.gname.required"); 再如 : if(goods.getgname() == null goods.getgname().trim().isempty()){ errors.rejectvalue("gname", "goods.gname.required") 上述 if 语句可以编写成 : //gname 为 goods 对象的属性 ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gname", "goods. gname.required"); 5.2.3 验证示例 本节使用一个应用 ch5a 讲解 Spring 验证器的编写及使用 ch5a 的项目结构如图 5.1 所示 图 5.1 ch5a 的项目结构
第 5 章数据验证 73 该应用中有个数据输入页面 addgoods.jsp, 效果如图 5.2 所示 ; 有个数据显示页面 goodslist.jsp, 效果如图 5.3 所示 图 5.2 数据输入页面图 5.3 数据显示页面 编写一个实现 org.springframework.validation.validator 接口的验证器类 GoodsValidator, 验证要求如下 : (1) 商品名和商品详情不能为空 (2) 商品价格在 0~100 之间 (3) 创建日期不能在系统日期之后 根据上述要求, 按照如下步骤完成应用 ch5a 1. 编写模型类 定义领域模型类 Goods, 封装输入参数 在该类中使用 @DateTimeFormat(pattern= "yyyy-mm-dd") 格式化创建日期 模型类 Goods 的具体代码如下 : package domain; import java.util.date; import org.springframework.format.annotation.datetimeformat; public class Goods { private String gname; private String gdescription; private double gprice; // 日期格式化 ( 需要在配置文件配置 FormattingConversionServiceFactoryBean) @DateTimeFormat(pattern="yyyy-MM-dd") private Date gdate; public String getgname() { return gname; public void setgname(string gname) { this.gname = gname; public String getgdescription() { return gdescription;
74 S pring MVC 开发技术指南 public void setgdescription(string gdescription) { this.gdescription = gdescription; public double getgprice() { return gprice; public void setgprice(double gprice) { this.gprice = gprice; public Date getgdate() { return gdate; public void setgdate(date gdate) { this.gdate = gdate; 2. 编写验证器类 编写实现 org.springframework.validation.validator 接口的验证器类 GoodsValidator, 使用 @Component 注解将 GoodsValidator 类声明为组件 具体代码如下 : package validator; import java.util.date; import org.springframework.stereotype.component; import org.springframework.validation.errors; import org.springframework.validation.validationutils; import org.springframework.validation.validator; import domain.goods; @Component public class GoodsValidator implements Validator{ @Override public boolean supports(class<?> klass) { // 要验证的 Model, 返回值为 False 则不验证 return Goods.class.isAssignableFrom(klass); @Override public void validate(object object, Errors errors) { Goods goods = (Goods)object;//object 要验证的对象 //goods.gname.required 是错误消息属性文件中的编码 ( 国际化后, 对应的是国际化的信息 ) ValidationUtils.rejectIfEmpty(errors, "gname", "goods.gname.required"); ValidationUtils.rejectIfEmpty(errors, "gdescription", "goods.gdescription.required"); if(goods.getgprice() > 100 goods.getgprice() < 0){
第 5 章数据验证 75 errors.rejectvalue("gprice", "gprice.invalid"); Date goodsdate = goods.getgdate(); // 在系统时间之后 if(goodsdate!= null && goodsdate.after(new Date())){ errors.rejectvalue("gdate", "gdate.invalid"); 3. 编写错误消息属性文件 在 /WEB-INF/resource 目录下, 编写属性文件 errormessages.properties 文件内容如下 : goods.gname.required= 请输入商品名称 goods.gdescription.required= 请输入商品详情 gprice.invalid= 价格在 0~100 之间 gdate.invalid= 创建日期不能在系统日期之后 Unicode 编码 (Eclipse 带有将汉字转换成 Unicode 编码的功能 ) 的属性文件内容如下 : goods.gname.required=\u8bf7\u8f93\u5165\u5546\u54c1\u540d\u79f0\u3002 goods.gdescription.required=\u8bf7\u8f93\u5165\u5546\u54c1\u8be6\u60c5 \u3002 gprice.invalid=\u4ef7\u683c\u57280-100\u4e4b\u95f4\u3002 gdate.invalid=\u521b\u5efa\u65e5\u671f\u4e0d\u80fd\u5728\u7cfb\u7edf\u 65E5\u671F\u4E4B\u540E\u3002 属性文件创建完成后, 想要告诉 Spring MVC 从该文件中获取错误消息, 则需要在配置文件中声明一个 messagesource bean, 具体代码如下 : <!-- 配置消息属性文件 --> <bean id="messagesource" class="org.springframework.context.support.reloadableresource_ BundleMessageSource"> <property name="basename" value="/web-inf/resource/error Messages"/> </bean> 4. 编写 Service 层 在 Service 层中编写一个 GoodsService 接口和 GoodsServiceImpl 实现类 具体代码如下 : package service; import java.util.arraylist; import domain.goods; public interface GoodsService {
76 S pring MVC 开发技术指南 boolean save(goods g); ArrayList<Goods> getgoods(); package service; import java.util.arraylist; import org.springframework.stereotype.service; import domain.goods; @Service public class GoodsServiceImpl implements GoodsService{ // 使用静态集合变量 users 模拟数据库 private static ArrayList<Goods> goods = new ArrayList<Goods>(); @Override public boolean save(goods g) { goods.add(g); return true; @Override public ArrayList<Goods> getgoods() { return goods; 5. 编写控制器类 编写控制器类 GoodsController, 在该类中使用 @Resource 注解注入自定义验证器 另外, 控制器类中包含两个处理请求的方法, 具体代码如下 : package controller; import javax.annotation.resource; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.controller; import org.springframework.ui.model; import org.springframework.validation.bindingresult; import org.springframework.validation.validator; import org.springframework.web.bind.annotation.modelattribute; import org.springframework.web.bind.annotation.requestmapping; import domain.goods; import service.goodsservice; @Controller @RequestMapping("/goods") public class GoodsController { // 得到一个用来记录日志的对象, 这样打印信息的时候能够标记打印的是哪个类的信息 private static final Log logger = LogFactory.getLog(GoodsController. class);
第 5 章数据验证 77 @Autowired private GoodsService goodsservice; // 注解验证器相当于 GoodsValidator validator = new GoodsValidator(); @Resource private Validator validator; @RequestMapping("/input") public String input(model model){ // 如果 model 中没有 goods 属性,addGoods.jsp 会抛出异常, // 因为表单标签无法找到 modelattribute 属性指定的 form // backing object model.addattribute("goods", new Goods()); return "addgoods"; @RequestMapping("/save") public String save(@modelattribute Goods goods, BindingResult result, Model model){ this.validator.validate(goods, result);// 添加验证 if (result.haserrors()) { return "addgoods"; goodsservice.save(goods); logger.info(" 添加成功 "); model.addattribute("goodslist", goodsservice.getgoods()); return "goodslist"; 6. 编写配置文件 配置文件 springmvc-servlet.xml 的代码如下 : <?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemalocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 使用扫描机制, 扫描包 --> <context:component-scan base-package="controller"/> <context:component-scan base-package="service"/>
78 S pring MVC 开发技术指南 <context:component-scan base-package="validator"/> <!-- 注册格式化转换器 --> <bean id="conversionservice" class="org.springframework.format. support.formattingconversionservicefactorybean"> <property name="formatters"> <set> <!-- 注册自定义格式化转换器 --> </set> </property> </bean> <mvc:annotation-driven conversion-service="conversionservice"/> <!-- 配置消息属性文件 --> <bean id="messagesource" class="org.springframework.context.support. ReloadableResourceBundleMessageSource"> <property name="basename" value="/web-inf/resource/errormessages"/> </bean> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.internalresource_ ViewResolver" id="internalresourceviewresolver"> <!-- 前缀 --> <property name="prefix" value="/web-inf/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans> 7. 编写视图 JSP 文件 应用 ch5a 中有两个视图文件 :addgoods.jsp 和 goodslist.jsp addgoods.jsp 的代码如下 : <%@ page language="java" contenttype="text/html; charset=utf-8" pageencoding="utf-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <% String path = request.getcontextpath(); String basepath = request.getscheme()+"://"+request.getservername()+":"+ request.getserverport()+path+"/"; %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html> <head> <base href="<%=basepath%>"> <meta http-equiv="content-type" content="text/html; charset=utf-8">
第 5 章数据验证 79 <title>insert title here</title> </head> <body> <form:form modelattribute="goods" action="goods/save" method="post"> <fieldset> <legend> 添加一件商品 </legend> <p> <label> 商品名 :</label> <form:input path="gname"/> </p> <p> <label> 商品详情 :</label> <form:input path="gdescription"/> </p> <p> <label> 商品价格 :</label> <form:input path="gprice"/> </p> <p> <label> 创建日期 :</label> <form:input path="gdate"/>(yyyy-mm-dd) </p> <p id="buttons"> <input id="reset" type="reset"> <input id="submit" type="submit" value=" 添加 "> </p> </fieldset> <!-- 取出所有验证错误 --> <form:errors path="*"/> </form:form> </body> </html> goodslist.jsp 的代码如下 : <%@ page language="java" contenttype="text/html; charset=utf-8" pageencoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <% String path = request.getcontextpath(); String basepath = request.getscheme()+"://"+request.getservername()+":"+ request.getserverport()+path+"/"; %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/tr/html4/loose.dtd"> <html>