第十四章 数据校验

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"%>




添加商品


    
        
添加一件商品

(yyyy-MM-dd)

③ 编写验证器类

编写实现 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 信用卡验证
@Email 验证是否为邮件地址,如果为 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";
        
    }
}

你可能感兴趣的:(第十四章 数据校验)