查看: 159|回复: 0

[Java学习] SpringMVC学习记录(六)--Validator验证

发表于 4 天前
本帖最后由 半夏花开 于 2018-4-17 17:05 编辑

一.基于Validator接口的验证.

首先创建User实例,并加入几个属性

public class User {    private String username;    private String password;    private String nickname;    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public String getNickname() {        return nickname;    }    public void setNickname(String nickname) {        this.nickname = nickname;    }    @Override    public String toString() {        return "username--"+username+"password--"+password+"nickname--"+nickname;    }}

接着创建用于校检的类UserValidator,让其实现Validator,覆盖其中的两个方法

import main.java.model.User;import org.springframework.validation.Errors;import org.springframework.validation.Validator;public class UserValidator implements Validator {    @Override    public boolean supports(Class<?> aClass) {        //判断是否是要校验的类,这里是User        return User.class.equals(aClass);    }    @Override    public void validate(Object o, Errors errors) {        User u = (User) o;        if (null == u.getPassword() || "".equals(u.getPassword())){            //此方法可以加四个参数,第一个表单域field,            //区分是哪个表单出错,第二个errorCode错误码,            //第三个制定了资源文件中占位符,第四个具体错误返回信息            //简写版可以把2,3参数去掉            errors.rejectValue("password",null,null,"password is null");        }    }}

上面的类只实现了对密码判断是否为空,为空则注册这一错误信息,也就是”password is null”,接下来要实现控制器,控制器要做的事情,第一是注册这个校验器,第二是实现校验.

import main.java.model.User;....../** * 加上@Controller决定这个类是一个控制器 */@Controller@RequestMapping("/user")public class HelloController {    //我们知道在Controller类中通过@InitBinder标记的方法只有在请求当前Controller的时候才会被执行    //所以在这里注册校验器    @InitBinder    public void initBainder(DataBinder binder){        binder.replaceValidators(new UserValidator());    }    //这个方法主要是跳转到登录页面    @RequestMapping(value = "/login",method = RequestMethod.GET)    public String login(Model model){        model.addAttribute(new User());        return "user/login";    }    //处理登录表单    @RequestMapping(value = "/login",method = RequestMethod.POST)    public String login(@Validated User user, BindingResult br){        if (br.hasErrors()){            return "user/login";        }        return "--";    }    }

上面代码可以看到@Validated User user, BindingResult br这两个参数,@Validated表明参数user是要校验的类,BindingResult是存储错误信息的类,两者必须一一对应,并且位置挨着,不能中间有其他参数,
最后随便写一个jsp页面实现校检

<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@taglib prefix="sf" uri="http://www.springframework.org/tags/form" %><%    request.setCharacterEncoding("utf-8");%><html><head>    <meta charset="utf-8">    <title>用户登录</title></head><body>    <sf:form modelAttribute="user" method="post">        用户名:<sf:input path="username"/><sf:errors path="username"/>        <br>        密码:<sf:input path="password"/><sf:errors path="password"/>        <br>        昵称:<sf:input path="nickname"/><sf:errors path="nickname"/>        <br>        <input type="submit" value="提交">    </sf:form></body></html>

前面实现的是局部校验,只对当前控制器有效,如果要实现全局校验的话需要配置springMVC.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: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-3.0.xsd     http://www.springframework.org/schema/context     http://www.springframework.org/schema/context/spring-context-3.0.xsd     http://www.springframework.org/schema/mvc     http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">    <mvc:annotation-driven validator="userValidator"/>    <bean id="userValidator" class="com.xxx.xxx.UserValidator"/>    ...</beans>
二.使用Annotaion JSR-303标准的验证

使用这个需要导入支持JSR-303标准的包,建议使用Hibernate Validator这个包,先看这个标准的原生标注

[td]
限制
说明
@Null
限制只能为null
@NotNull
限制必须不为null
@AssertFalse
限制必须为false
@AssertTrue
限制必须为true
@DecimalMax(value)
限制必须为一个不大于指定值的数字
@DecimalMin(value)
限制必须为一个不小于指定值的数字
@Digits(integer,fraction)
限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future
限制必须是一个将来的日期
@Max(value)
限制必须为一个不大于指定值的数字
@Min(value)
限制必须为一个不小于指定值的数字
@Past
限制必须是一个过去的日期
@Pattern(value)
限制必须符合指定的正则表达式
@Size(max,min)
限制字符长度必须在min到max之间
@Past
验证注解的元素值(日期类型)比当前时间早
@NotEmpty
验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

要使用很简单,在需要验证的变量前面加上该Annotation即可,看下面使用后的User

public class User {    @NotEmpty(message = "用户名不能为空")    private String username;    @Size(min=6 ,max= 20 ,message = "密码长度不符合标准")    private String password;    private String nickname;   ......}

然后再控制器里面加入验证就可以了

@Controller@RequestMapping("/user")public class HelloController {    @RequestMapping(value = "/login",method = RequestMethod.GET)    public String login(Model model){        model.addAttribute(new User());        return "user/login";    }    @RequestMapping(value = "/login",method = RequestMethod.POST)    public String login(@Validated User user, BindingResult br){        if (br.hasErrors()){            return "user/login";        }        return "user/login";    }    }

然后jsp页面还是之前的页面,验证效果如下,这种方法明显简单多了


3.定义自己的Annotation Validator

这部分直接从[大牛][1]那拷贝过来的.

除了JSR-303原生支持的限制类型之外我们还可以定义自己的限制类型。定义自己的限制类型首先我们得定义一个该种限制类型的注解,而且该注解需要使用@Constraint标注。现在假设我们需要定义一个表示金额的限制类型,那么我们可以这样定义:

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import javax.validation.Constraint;import javax.validation.Payload;import com.xxx.xxx.constraint.impl.MoneyValidator;@Target({ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy=MoneyValidator.class)public @interface Money {    String message() default"不是金额形式";    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}

我们可以看到在上面代码中我们定义了一个Money注解,而且该注解上标注了@Constraint注解,使用@Constraint注解标注表明我们定义了一个用于限制的注解。@Constraint注解的validatedBy属性用于指定我们定义的当前限制类型需要被哪个ConstraintValidator进行校验。在上面代码中我们指定了Money限制类型的校验类是MoneyValidator。另外需要注意的是我们在定义自己的限制类型的注解时有三个属性是必须定义的,如上面代码所示的message、groups和payload属性。
在定义了限制类型Money之后,接下来就是定义我们的限制类型校验类MoneyValidator了。限制类型校验类必须实现接口javax.validation.ConstraintValidator,并实现它的initialize和isValid方法。我们先来看一下MoneyValidator的代码示例:

import java.util.regex.Pattern;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import com.xxx.xxx.constraint.Money;public class MoneyValidator implements ConstraintValidator<Money, Double> {    private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金额的正则表达式    private Pattern moneyPattern = Pattern.compile(moneyReg);    public void initialize(Money money) {       // TODO Auto-generated method stub    }    public boolean isValid(Double value, ConstraintValidatorContext arg1) {       // TODO Auto-generated method stub       if (value == null)           return true;       return moneyPattern.matcher(value.toString()).matches();    }}

从上面代码中我们可以看到ConstraintValidator是使用了泛型的。它一共需要指定两种类型,第一个类型是对应的initialize方法的参数类型,第二个类型是对应的isValid方法的第一个参数类型。从上面的两个方法我们可以看出isValid方法是用于进行校验的,有时候我们在校验的过程中是需要取当前的限制类型的属性来进行校验的,比如我们在对@Min限制类型进行校验的时候我们是需要通过其value属性获取到当前校验类型定义的最小值的,我们可以看到isValid方法无法获取到当前的限制类型Money。这个时候initialize方法的作用就出来了。我们知道initialize方法是可以获取到当前的限制类型的,所以当我们在校验某种限制类型时需要获取当前限制类型的某种属性的时候,我们可以给当前的ConstraintValidator定义对应的属性,然后在initialize方法中给该属性赋值,接下来我们就可以在isValid方法中使用其对应的属性了。针对于这种情况我们来看一个代码示例,现在假设我要定义自己的@Min限制类型和对应的MinValidator校验器,那么我可以如下定义:

@Target({ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy=MinValidator.class)public @interface Min {    int value() default 0;    String message();    Class<?>[] groups() default {};    Class<? extends Payload>[] payload() default {};}

MinValidator校验器

public class MinValidator implements ConstraintValidator<Min, Integer> {    private int minValue;    public void initialize(Min min) {       // TODO Auto-generated method stub       //把Min限制类型的属性value赋值给当前ConstraintValidator的成员变量minValue       minValue = min.value();    }    public boolean isValid(Integer value, ConstraintValidatorContext arg1) {       // TODO Auto-generated method stub       //在这里我们就可以通过当前ConstraintValidator的成员变量minValue访问到当前限制类型Min的value属性了       return value >= minValue;    }}

继续来说一下ConstraintValidator泛型的第二个类型,我们已经知道它的第二个类型是对应的isValid的方法的第一个参数,从我给的参数名称value来看也可以知道isValid方法的第一个参数正是对应的当前需要校验的数据的值,而它的类型也正是对应的我们需要校验的数据的数据类型。这两者的数据类型必须保持一致,否则Spring会提示找不到对应数据类型的ConstraintValidator。建立了自己的限制类型及其对应的ConstraintValidator后,其用法跟标准的JSR-303限制类型是一样的。以下就是使用了上述自己定义的JSR-303限制类型——Money限制和Min限制的一个实体类:

public class User {    private int age;    private Double salary;    @Min(value=8, message="年龄不能小于8岁")    public int getAge() {       return age;    }    public void setAge(int age) {       this.age = age;    }    @Money(message="标准的金额形式为xxx.xx")    public Double getSalary() {       return salary;    }    public void setSalary(Double salary) {       this.salary = salary;    }}
4.配合ajax验证

最近写的项目,感觉直接使用validator不太好用,主要是返回时会刷新整个页面才会出来错误信息,体验相当不好,验证还是用ajax体验比较好,所以配合ajax

思路:验证还是使用springMVC来验证,只是这次发现错误的话,把错误取出,存放到一个map中,然后ajax返回,页面根据ajax返回值来判断,从而显示不同的信息

主要代码:

if (br.hasErrors()){//判断是否有错误             //对错误集合进行遍历,有的话,直接放入map集合中            br.getFieldErrors().forEach(p->{                maps.put(p.getField(),p.getDefaultMessage());            });            return maps;        }

这样的话 maps里面存放的就是 错误变量名,错误信息,例如 username -‘用户名不能为空’





回复

使用道具 举报