target
了解数据校验的意义
了解客户端校验(使用 JavaScript 进行客户端验证)
了解ValidationUtils工具类和Validator接口
掌握日期格式的转化
理解数据校验的开发步骤
理解 Hibernate-Validator数据验证
1. 概述
用户的输入一般是随意的,为了保证数据的合法性,数据验证是所有 Web 应用必须处理的问题。
在 Spring MVC框架中有以下两种方法可以验证输入数据:
- 利用 Spring自带的验证框架。
- 利用 JSR 303 实现。
数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,通过 JavaScript 代码完成。服务器端验证是整个应用阻止非法数据的最后防线,通过在应用中编程实现。
1.1 客户端验证
在大多数情况下,使用 JavaScript 进行客户端验证的步骤如下:
- 编写验证函数。
- 在提交表单的事件中调用验证函数。
- 根据验证函数来判断是否进行表单提交。
客户端验证可以过滤用户的误操作,是第一道防线,一般使用 JavaScript 代码实现。但仅有客户端验证是不够的,攻击者还可以绕过客户端验证直接进行非法输入,这样可能会引起系统异常,为了确保数据的合法性,防止用户通过非正常手段提交错误信息,必须加上服务器端验证。
1.2 服务端验证
Spring MVC 的 Converter 和 Formatter 在进行类型转换时是将输入数据转换成领域对象的属性值(一种 Java 类型),一旦成功,服务器端验证器就会介入。也就是说,在 Spring MVC 框架中先进行数据类型转换,再进行服务器端验证。
服务器端验证对于系统的安全性、完整性、健壮性起到了至关重要的作用。在 Spring MVC 框架中可以利用 Spring 自带的验证框架验证数据,也可以利用 JSR 303 实现数据验证。
2. SpringMVC校验
创建自定义 Spring验证器时需要实现的 Validator 接口和工具类 ValidationUtils。
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 filed,String errorCode)
void rejectValue(String filed,String errorCode,String defaultMessage)
在一般情况下只需要给 reject 或 rejectValue 方法一个错误代码,Spring MVC框架就会在消息属性文件中查找错误代码,获取相应错误消息。具体示例如下:
if(goods.getGprice() > 100 || goods.getGprice() < 0){
errors.rejectValue("gprice","gprice.invalid"); // gprice.invalid为错误代码
}
2.2 ValidationUtils 类
org.springframework.validation.ValidationUtils 是一个工具类,该类中有几个方法可以帮助用户判定值是否为空。
例如:
if(goods.getGname()==null || goods.getGname().isEmpty()) {
errors.rejectValue("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");
2.3 实例
做一个如下的页面进行校验:
编写一个实现 org.springframework.validation.Validator 接口的验证器类 GoodsValidator,验证要求如下:
- 商品名和商品详情不能为空。
- 商品价格在 0 到 100。
- 创建日期不能在系统日期之后。
① 创建实体类
在该类中使用 @DateTimeFormat(pattern="yyyy-MM-dd")格式化创建日期。
package com.lee.springmvc.bean;
public class Goods {
private String gname;
private String gdescription;
private double gprice;
// 日期格式化(还需要在配置文件中添加"mvc:annotation-driven"这个标签, 否则不生效.)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date gdate;
// getter、setter略
}
② 创建数据输入页面
在 WEB-INF 目录下创建文件夹 views,并在该文件夹中创建数据输入页面 addGoods.jsp。代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
添加商品
③ 编写验证器类
编写实现 org.springframework.validation.Validator 接口的验证器类 GoodsValidator,使用 @Component 注解将 GoodsValidator 类声明为验证组件。具体代码如下:
package com.lee.springmvc.validator;
@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) {
errors.rejectValue("gprice", "gprice.invalid");
}
Date goodsDate = goods.getGdate();
// 在系统时间之后
if (goodsDate != null && goodsDate.after(new Date())) {
errors.rejectValue("gdate", "gdate.invalid");
}
}
}
④ 编写错误消息属性文件
在 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
goods.gdescription.required=\u8BF7\u8F93\u5165\u5546\u54C1\u8BE6\u60C5
gprice.invalid=\u4EF7\u683C\u4E3A0~100
gdate.invalid=\u521B\u5EFA\u65E5\u671F\u4E0D\u80FD\u5728\u7CFB\u7EDF\u65E5\u671F\u4E4B\u540E
在属性文件创建完成后需要告诉 Spring MVC 从该文件中获取错误消息,则需要在springmvc.xml配置文件中声明一个 messageSource bean,具体代码如下:
⑤ 编写 Service 层
编写一个 GoodsService 接口。具体代码如下:
package com.lee.springmvc.service;
public interface GoodsService {
public boolean save(Goods g);
public ArrayList getGoods();
}
GoodsServiceImpl 实现类具体代码如下:
package com.lee.springmvc.service.impl;
@Service
public class GoodsServiceImpl implements GoodsService {
// 使用静态集合变量goods模拟数据库
private static ArrayList goods = new ArrayList();
@Override
public boolean save(Goods g) {
goods.add(g);
return true;
}
@Override
public ArrayList getGoods() {
return goods;
}
}
⑥ 编写控制器类
编写控制器类 GoodsController,在该类中使用 @Resource 注解注入自定义验证器。另外,在控制器类中包含两个处理请求的方法,具体代码如下:
package com.lee.springmvc.controller;
@Controller
@RequestMapping("/goods")
public class GoodsController {
// 得到一个用来记录日志的对象,这样在打印信息的时候能够标记打印的是哪个类的信息
private static final Log logger = LogFactory.getLog(GoodsController.class);
@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(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";
}
}
⑦ 编写配置文件
在 src 目录下编写配置文件 springmvc.xml,具体代码如下:
⑧ 创建数据显示页面
在 WEB-INF/views 目录下创建数据显示页面 goodsList.jsp。核心代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
Insert title here
商品名
商品详情
商品价格
创建日期
${goods.gname }
${goods.gdescription }
${goods.gprice }
${goods.gdate }
⑨ 创建 web.xml 文件
在 WEB-INF 目录下创建 web.xml 文件,在该文件中配置 Spring MVC 的核心控制器 DispatcherServlet 和字符编码过滤器,具体代码如下:
Validator
springmvc
org.springframework.web.servlet.DispatcherServlet
1
springmvc
/
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
/*
⑩ 测试应用
发布 应用并启动 Tomcat 服务器,然后通过地址"http://localhost:8080/SpringMVC-14/goods/input"测试应用。
当表单填写不正确时:
当填写正确时:
2.4 开发经验
① 日期转化
如果有如下需求:
- 前台输入一个字符串形式的日期
- 实体类是Date类型
- 控制器将前台接受到的字符串类型转化为Date类型
方法一:
//1.首先在实体类上加注解
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date gdate;
// 2. springmvc配置文件中开启annotation-driven
方法二:
//在控制器里使用@InitBinder格式化
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); // true:允许输入空值,false:不能为空值
}
② 数据校验步骤
//1. 首先编写校验类(需实现Validator接口),并将该类纳入到IOC容器
@Component
public class GoodsValidator implements Validator
//2. 错误信息的配置文件
goods.gdescription.required=请输入商品详情
gprice.invalid=价格为0~100
//3. 在springmvc配置文件中,将错误信息的配置文件加载
//4. 在控制器中开启校验
@Resource
private Validator validator;
this.validator.validate(goods, result); // 添加验证
3. Hibernate-Validator数据验证
对于 JSR 303 验证,目前有两个实现,一个是 Hibernate Validator,一个是 Apache BVal。本教程采用的是 Hibernate Validator,注意它和 Hibernate 无关,只是使用它进行数据验证。
3.1 相关jar
hibernate-validator-4.3.2.Final.jar
jboss-logging-3.1.0.Final.jar
validation-api-1.0.0. GA.jar
classmate-0.8.0.jar
3.2 标注类型
JSR 303 不需要编写验证器,但需要利用它的标注类型在领域模型的属性上嵌入约束。
① 空检查
- @Null:验证对象是否为 null。
- @NotNull:验证对象是否不为 null,无法检查长度为 0 的字符串。
- @NotBlank:检查约束字符串是不是 null,以及被 trim 后的长度是否大于 0,只针对字符串,且会去掉前后空格。
- @NotEmpty:检查约束元素是否为 null 或者是 empty。
示例如下:
@NotBlank(message="{goods.gname.required}") //goods.gname.required为属性文件的错误代码
private String gname;
② boolean 检查
- @AssertTrue:验证 boolean 属性是否为 true。
- @AssertFalse:验证 boolean 属性是否为 false。
示例如下:
@AssertTrue
private boolean isLogin;
③ 长度检查
- @Size(min=,max=):验证对象(Array、Collection、Map、String)长度是否在给定的范围之内。
- @Length(min=,max=):验证字符串长度是否在给定的范围之内。
示例如下:
@Length(min=1,max=100)
private String gdescription;
④ 日期检查
- @Past:验证 Date 和 Callendar 对象是否在当前时间之前。
- @Future:验证 Date 和 Calendar 对象是否在当前时间之后。
- @Pattern:验证 String 对象是否符合正则表达式的规则。
示例如下:
@Past(message="{gdate.invalid}")
private Date gdate;
⑤ 数值检查
名称 | 说明 |
---|---|
@Min | 验证 Number 和 String 对象是否大于指定的值 |
@Max | 验证 Number 和 String 对象是否小于指定的值 |
@DecimalMax | 被标注的值必须不大于约束中指定的最大值,这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示,小数存在精度 |
@DecimalMin | 被标注的值必须不小于约束中指定的最小值,这个约束的参数是一个通过 BigDecimal 定义的最小值的字符串表示,小数存在精度 |
@Digits | 验证 Number 和 String 的构成是否合法 |
@Digits(integer=,fraction=) | 验证字符串是否符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度 |
@Range(min=,max=) | 检查数字是否介于 min 和 max 之间 |
@Valid | 对关联对象进行校验,如果关联对象是个集合或者数组,那么对其中的元素进行校验,如果是一个 map,则对其中的值部分进行校验 |
@CreditCardNumber | 信用卡验证 |
验证是否为邮件地址,如果为 null,不进行验证,通过验证 |
示例如下:
@Range(min=10,max=100,message="{gprice.invalid}")
private double gprice;
⑥ 正则表达式
名称 | 说明 | 实例 |
---|---|---|
@Pattern | 值必须匹配正则表达式 | @Pattern(regext = "\d{3}") |
3.3 简单版本实例
- 直接在实体类上加注解,并且写好message《注意:message内容用双引号引起来》
- 在控制器中对需要校验的参数前加注解@Valid即可
注意:springmvc配置文件需要有
① 实体类
public class User {
@NotBlank(message = "用户名不能为空!")
private String username;
@Range(min = 1,max = 120,message = "年龄必须是1-120之间!")
private int age;
@NotEmpty(message = "电话号码不能为空!")
@Pattern(regexp = "/^(13[0-9]|15[0|1|3|6|7|8|9]|18[8|9])\\d{8}$/")
private String tel;
@Email(message = "不合法的电子邮箱!")
private String email;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past(message = "必须是当前日期之前的一个日期!")
private Date birthday;
}
② 前台表单:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
注册
③ 控制器
一定要加@Valid注解,否则不会校验。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("input")
public String register(Model model) {
model.addAttribute("user",new User());
return "register";
}
@RequestMapping("register")
public String save(@Valid User user,BindingResult result) {
System.out.println(user);
System.out.println(result);
if (result.hasErrors()) {
return "register";
}
return "success";
}
}
3.4 配置文件版本实例
- 在实体类属性上加相应注解,message需要写配置文件的key《message内容需要用大括号引起来》
- 错误消息配置文件
- 在springmvc配置文件中加载错误消息配置文件
- 在控制器中使用@Valid对模型对象就行校验
① 实体类
public class User {
@NotBlank(message = "{user.username.notblank}")
private String username;
@Range(min = 1,max = 120,message = "{user.age.range}")
private int age;
@NotEmpty(message = "{user.tel.notblank}")
@Pattern(regexp = "^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$",message = "{user.tel.notMatch}")
private String tel;
@Email(message = "{user.email.notMatch}")
private String email;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past(message = "{user.birthday.before}")
private Date birthday;
}
② 前台表单
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
注册
③ 错误消息配置文件
在WEB-INF/resource目录下,新建errorMessages.properties文件:
user.username.notblank=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
user.age.range=\u7528\u6237\u5E74\u9F84\u5E94\u8BE5\u57280~120\u4E4B\u95F4
user.tel.notblank=\u7528\u6237\u7535\u8BDD\u4E0D\u80FD\u4E3A\u7A7A
user.tel.notMatch=\u7535\u8BDD\u53F7\u7801\u4E0D\u7B26\u5408\u89C4\u8303
user.email.notMatch=\u975E\u6CD5\u7684\u90AE\u7BB1
user.birthday.before=\u8F93\u5165\u4E00\u4E2A\u6BD4\u5F53\u524D\u65E5\u671F\u5C0F\u7684\u65E5\u671F
④ springmvc加载错误消息配置文件
必须在springmvc的配置文件中将上面配置的文件加载,程序才能读到相关配置:
需要在mvc注解驱动里开启validator支持。
/WEB-INF/resource/errorMessages
⑤ 控制器
控制器里直接对模型对象用@Valid注解校验即可,还需要把校验结果用BindingResult进行绑定:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("input")
public String register(Model model) {
model.addAttribute("user",new User());
return "register";
}
@RequestMapping("register")
public String save(@Valid User user,BindingResult result) {
System.out.println(user);
System.out.println(result);
if (result.hasErrors()) {
return "register";
}
return "success";
}
}