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" %>
登录页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
登录成功页面
登录成功!
下面选取了一张测试结果图:
注:在UserControllerTest中,login首先会接收到客户端发送的一个User对象(这是因为包装类型参数绑定),利用前面定义的UserValidator对接收到的User对象进行校验。而在login方法的User形参前,使用@Valid注解对其进行标注,这是因为只有当使用@Valid标注需要校验的参数时,Spring才会对其进行校验。而在校验的参数后面,必须给定一个包含Errors的参数,可以是Errors本身,也可以是其子类BindingResult。如果不设置包含Errors的参数,Spring会直接抛出异常,而设置后Spring会将异常的处理权交给开发人员,有开发人员来处理形参中包含Error参数的对象。注意,这个参数必须紧挨着@Valid注解标注的参数。