SpringMVC[5]--Validation校验

Web系统中,校验是必不可少的环节,校验一般分为前端校验和后端校验,前端校验一般使用脚本语言,对即将要提交的数据进行校验,不符合业务要求的将给予提示。后端校验一般是逻辑性校验,例如校验用户的某种凭证是否过期,某种参数是否在合法请求范围内等在前端不方便的校验。
设涉知识点:

  • Bean Validation数据校验
  • 分组校验
  • Spring Validation接口校验

相关jar包:

  • hibernate-validator-4.3.0.Final.jar
  • jobs-logging-3.1.0.CR2.jar
  • validation-api-1.0.0.GA.jar

1. Bean Validation数据校验

特性: 使用简洁的注释语法来对Bean中的某个属性进行校验。
如:看一下前台传来的水果商品参数中的水果名称是否长度超限,产地信息是否为空。
内容接着:SpringMVC[2]--框架搭建

1.1 搭建validation校验框架
  • 在annotation-driven的注解驱动配置上添加一个validator属性,为其指定一个“validator”值,该值为“校验器”的名称,配置如下:
 
  • 在核心配置文件springmvc.xml中添加名为“validator”的校验器配置,其具体配置如下:


    
    

 定义了一个为“validator”的校验器,指定其中的校验器提供类是“HibernateValidator”,即添加的Hibernate校验器。
 而下面的validationMessageSource指的是校验使用的资源文件,在该文件中配置校验的错误信息。若不配置默认使用classpath下的ValidationMessages.properties。
在springmvc.xml中添加id为messageSource的资源属性文件配置:



    
        
            classpath:ProductValidationMessages.properties
        
    
    
    
    
    

然后需要在config或者resources文件夹中新建ProductValidationMessages.properties配置文件,用来配置校验错误信息。

  • 由于该校验机制是给处理器Controlelr使用的,而加载和调用处理器的是处理器适配器HandlerAdapter,所以要为处理器适配器的配置添加校验器:

【下面的内容会自动默认注册,所以不用写。】

  • validator还需要检测前台传来的日期、数字类型数据是否正确,所以在其conversion-service属性中配置一个可以将字符串转换为Data类型或数字类型的Java类,配置如下:


1.2 添加校验注解信息
package cn.com.mvc.model;

import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.Size;

public class Fruits {
    @Size(min=1, max=20, message="{fruits.name.length.error}")
    private String name;
    private double price;
    @NotEmpty(message="{fruits.producing_area.isEmpty}")
    private String producing_area;//产地

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public String getProducing_area() {
        return producing_area;
    }
    public void setProducing_area(String producing_area) {
        this.producing_area = producing_area;
    }
}

 在name属性上添加@Size注解,并且指定了其最小(min)和最大(max)字符限制,其中message用来提示校验出错误时显示的错误信息。
 校验非空使用的注解为@NotEmpty,其中也指定了message错误信息。

  • 编写ProductValidationMessages.properties配置文件
#添加校验错误提示信息
fruits.name.length.error=请输入1到20个字符的商品名称
fruits.producing_area.isEmpty=请输入商品的生产地
  • 在Controller方法中捕获校验信息
@Controller
@RequestMapping("query")
public class FindControllerTest4 {
    private FruitsService fruitsService = new FruitsServiceImpl();

    @RequestMapping("queryFruitsByCondition")
    public String queryFruitsByCondition(Model model, @Validated Fruits fruits, BindingResult bindingResult){
//        获取校验错误信息
        List allErrors = null;
        if (bindingResult.hasErrors()){
            allErrors = bindingResult.getAllErrors();
            for(ObjectError objectError:allErrors){
//                输出错误信息
                System.out.println(objectError.getDefaultMessage());
            }
        }
        List findList = null;
        if(fruits==null || (fruits.getName()==null && fruits.getProducing_area()==null)){
            //如果fruits或查询条件为空,默认查询所有数据
            findList = fruitsService.queryFruitsList();
        } else {
//           如果fruits查询条件不为空,按条件查询
            findList = fruitsService.queryFruitsByCondition(fruits);
        }
        model.addAttribute("fruitsList", findList);
        return "findFruits";
    }
}

在Controller方法的形参fruits前面添加了@Validated注解,在后面添加了BindingResult类。一般会在需要校验的Bean形参前面加@Validated注解,标注该参数需要执行Validated校验,而在需要校验的Bean形参后面添加BindingResult参数接收校验的出错信息。

@Validated和BindingResult注解时成对出现的,并且在形参中出现的顺序是固定的(一前一后)。

  • 补充Service层代码
package cn.com.mvc.service;

import cn.com.mvc.model.Fruits;

import java.util.ArrayList;
import java.util.List;

public class FruitsServiceImpl implements FruitsService {
    public List fruitsList = null;
    public List init(){
        if (fruitsList == null){
            fruitsList = new ArrayList();

            Fruits apple = new Fruits();
            apple.setId(1);
            apple.setName("红富士苹果");
            apple.setPrice(2.3);
            apple.setProducing_area("山东");

            Fruits banana = new Fruits();
            banana.setId(2);
            banana.setName("香蕉");
            banana.setPrice(1.5);
            banana.setProducing_area("上海");

            fruitsList.add(apple);
            fruitsList.add(banana);

            return fruitsList;
        }else {
            return fruitsList;
        }

    }
    @Override
    public List queryFruitsList() {
        return init();
    }

    @Override
    public Fruits queryFruitById(Integer id) {
        init();
        Fruits f;
        for(int i = 0; i < fruitsList.size(); i++){
            f = fruitsList.get(i);
            if (f.getId() == id)
                return f;
        }
        return null;
    }

    @Override
    public List queryFruitsByCondition(Fruits fruits) {
        init();
        String name = fruits.getName();
        String area = fruits.getProducing_area();
        List queryList = new ArrayList();
        Fruits f;
        for (int i = 0; i < fruitsList.size(); i++){
            f = fruitsList.get(i);
            if ((!name.equals("")&&f.getName().contains(name)) ||
                    (!area.equals("")&&f.getProducing_area().contains(area))){
                queryList.add(f);
            }
        }
        return queryList.size()>0?queryList:null;
    }
}
1.3 测试校验结果
  • controller层添加
//将错误传到页面
model.addAttribute("allErrors", allErrors);
  • 前端页面定义一个div,专门用来显示错误。
<%--显示错误信息--%>

    
        ${error.defaultMessage}

2. 分组校验

当使用Bean Validation校验框架的时候,一般都会将校验信息在对应的实体JavaBean中,上面代码中有。
问题:所有使用该实体类的Controller类对应的方法都要进习惯一次校验,但有些Controller仅仅将Fruits实体类作为查询条件(如里面只有一个id),这样的Fruits实体类再进行Bean Validation校验时就会出现问题,导致该方法抛出不该抛的异常。

  SpringMVC提供“分组校验”,将不同校验规则分给不同的组,当在Controller方法中校验相关的实体类Bean时,可以指定不同的组使用不同的校验规则。
首先创建两个组接口,称为FruitGroup1、FruitGroup2

package cn.com.mvc.validator.group;
//校验分组1
public interface FruitsGroup1 {
}
package cn.com.mvc.validator.group;
//校验分组2
public interface FruitsGroup2 {
}

在Fruits实体类中的两个校验分配给不同的组:

public class Fruits {
    private int id;
    @Size(min=0, max=10, message="{fruits.name.length.error}", groups = {FruitsGroup1.class})
    private String name;
    private double price;
    @NotEmpty(message="{fruits.producing_area.isEmpty}", groups = {FruitsGroup2.class})
    private String producing_area;//产地
    //其余代码省略
}

之后就在Controller中的@Validation注解中添加一个value值即可,如:

@RequestMapping("queryFruitsByCondition")
    public String queryFruitsByCondition(Model model, @Validated(value=FruitsGroup1.class) Fruits fruits, BindingResult bindingResult){
    //代码省略
}

3. Spring Validator接口校验

Validator接口校验是SpringMVC自己的校验机制。SpringMVC为其提供了接口,可以用它来验证自己定义的实体对象。
原理:使用了一个Errors对象工作,当验证器验证失败的时候,会向Errors对象填充验证失败的信息。
区别:Bean Validation是在需要校验的JavaBean中进行约束指定,而Spring的Validator接口校验是实现Validator接口,并编写指定类型的校验规则。

接口使用:

3.1 定义一个User实体类
package cn.com.mvc.model;

public class User {
    private String username;
    private String password;
    //get和set方法省略
}
3.2 编写一个Validator接口的实现类,并实现其supports方法和validate方法。

supports方法的作用:判断当前的Validator实现类是否支持校验当前需要校验的实体类,如果支持,该方法返回true,此时才可以调用valida方法来对需要校验的实体类进行校验。
validate方法的作用:编写具体的校验逻辑,并根据不同的校验结果,将错误放入错误对象Errors中。其中Errors是存储和暴露数据绑定错误和验证错误相关信息的接口,其提供了存储和获取错误消息的方法。
编写一个Validator接口的实现类:

package cn.com.mvc.validator;

import cn.com.mvc.model.User;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class aClass) {
        return User.class.equals(aClass);
    }
    @Override
    public void validate(Object o, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "username", "Username.is.empty", "用户名不能为空");
        User user = (User)o;
        if (null == user.getPassword() || "".equals(user.getPassword())){
            //指定验证失败的字段名、错误名、默认错误信息
            errors.rejectValue("password", "Password.is.empty", "密码不能为空。");
        } else if (user.getPassword().length() < 6){
            //指定验证失败的字段名、错误吗、默认错误信息
            errors.rejectValue("password","length.too.short","密码长度不小于6位。");
        }
    }
}

关于Errors的两个方法,第一个rejectValue方法设置了错误字段名为“password”,注册全局错误码“Password.is.empty”。第二个rejectValue方法除了设置错误字段名和全局错误码外,还设置默认消息“密码长度不得小于6位”,当校验器从messageSource没有找到错误码“Password.is.empty.”对应的错误消息时,则显示默认消息“密码长度不得小于6位”。

3.3 在Controller中的initBinder方法中位DataBinder设置一个Validator(即UserValidator),然后在相关方法中添加BindingResult对象。
package cn.com.mvc.controller;

import cn.com.mvc.model.User;
import cn.com.mvc.validator.UserValidator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.Valid;
import java.util.List;

@Controller
@RequestMapping("user")
public class UserControllerTest {
    @InitBinder
    public void initBinder(DataBinder binder){
        binder.setValidator(new UserValidator());
    }
    @RequestMapping("toLogin")
    public String toLoginPage(){
        //跳转至登录页面
        return "/user/login";
    }
    @RequestMapping("login")
    public String login(Model model, @Valid User user, BindingResult result){
        //登录检测
        List allErrors = null;
        if(result.hasErrors()){
            allErrors = result.getAllErrors();
            for (ObjectError objectError : allErrors){
                //输出错误信息
                System.out.println("code="+objectError.getCode()+" DefaultMessage="+objectError.getDefaultMessage());
                //将错误信息传到页面
                model.addAttribute("allErrors",allErrors);
            }
            return "/user/login";
        } else {
            //其他的业务逻辑
        }
        return "/user/loginSuccess";
    }
}
3.4 校验测试

jsp页面代码,分别为login.jsp和loginSuccess.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>


    登录页面


    
账号:
密码:
<%--错误信息展示--%>
${error.defaultMessage}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    登录成功页面


    登录成功!


下面选取了一张测试结果图:


SpringMVC[5]--Validation校验_第1张图片
测试结果.png

注:在UserControllerTest中,login首先会接收到客户端发送的一个User对象(这是因为包装类型参数绑定),利用前面定义的UserValidator对接收到的User对象进行校验。而在login方法的User形参前,使用@Valid注解对其进行标注,这是因为只有当使用@Valid标注需要校验的参数时,Spring才会对其进行校验。而在校验的参数后面,必须给定一个包含Errors的参数,可以是Errors本身,也可以是其子类BindingResult。如果不设置包含Errors的参数,Spring会直接抛出异常,而设置后Spring会将异常的处理权交给开发人员,有开发人员来处理形参中包含Error参数的对象。注意,这个参数必须紧挨着@Valid注解标注的参数。

你可能感兴趣的:(SpringMVC[5]--Validation校验)