使用Validator接口进行验证
在SpringMVC中提供了1个Validator接口,我们可以通过该接口来定义我们自己对实体对象的验证。接下来看1个示例。
假定我们现在有1个需要进行验证的实体类User,其代码以下所示:
Java代码
- public class User {
-
- private String username;
-
- private String password;
-
- 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 toString() {
- return username + ", " + password;
- }
-
- }
那末当我们需要使用SpringMVC提供的Validator接口来对该实体类进行校验的时候该如何做呢?这个时候我们应当提供1个Validator的实现类,并实现Validator接口的supports方法和validate方法。Supports方法用于判断当前的Validator实现类是不是支持校验当前需要校验的实体类,只有当supports方法的返回结果为true的时候,该Validator接口实现类的validate方法才会被调用来对当前需要校验的实体类进行校验。这里假定我们需要验证User类的username和password都不能为空,先给出其代码,稍后再进行解释。这里我们定义1个UserValidator,其代码以下:
Java代码
- import org.springframework.validation.Errors;
- import org.springframework.validation.ValidationUtils;
- import org.springframework.validation.Validator;
-
- public class UserValidator implements Validator {
-
- public boolean supports(Class<?> clazz) {
- // TODO Auto-generated method stub
- return User.class.equals(clazz);
- }
-
- public void validate(Object obj, Errors errors) {
- // TODO Auto-generated method stub
- ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty.");
- User user = (User) obj;
- if (null == user.getPassword() || "".equals(user.getPassword()))
- errors.rejectValue("password", null, "Password is empty.");
- }
-
- }
在上述代码中我们在supports方法中定义了该UserValidator只支持对User对象进行校验。在validate方法中我们校验了User对象的username和password不为empty的情况,这里的empty包括null和空字符串两种情况。ValidationUtils类是Spring中提供的1个工具类。Errors就是Spring用来寄存毛病信息的对象。
我们已定义了1个对User类进行校验的UserValidator了,但是这个时候UserValidator还不能对User对象进行校验,由于我们还没有告知Spring应当使用UserValidator来校验User对象。在SpringMVC中我们可使用DataBinder来设定当前Controller需要使用的Validator。先来看下面1段代码:
Java代码
- import javax.validation.Valid;
- import org.springframework.stereotype.Controller;
- import org.springframework.validation.BindingResult;
- import org.springframework.validation.DataBinder;
- import org.springframework.web.bind.annotation.InitBinder;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- @Controller
- public class UserController {
-
- @InitBinder
- public void initBinder(DataBinder binder) {
- binder.setValidator(new UserValidator());
- }
-
- @RequestMapping("login")
- public String login(@Valid User user, BindingResult result) {
- if (result.hasErrors())
- return "redirect:user/login";
- return "redirect:/";
- }
-
- }
在上面这段代码中我们可以看到我们定义了1个UserController,该Controller有1个处理login操作的处理器方法login,它需要接收客户端发送的1个User对象,我们就是要利用前面的UserValidator对该User对象进行校验。首先我们可以看到我们login方法接收的参数user是用@Valid进行标注的,这里的@Valid是定义在JSR⑶03标准中的,我这里使用的是Hibernate Validation对它的实现。这里我们必须使用@Valid标注我们需要校验的参数user,否则Spring不会对它进行校验。另外我们的处理器方法必须给定包括Errors的参数,这可以是Errors本身,也能够是它的子类BindingResult,使用了Errors参数就是告知Spring关于表单对象数据校验的毛病将由我们自己来处理,否则Spring会直接抛出异常,而且这个参数是必须紧挨着@Valid参数的,即必须紧挨着需要校验的参数,这就意味着我们有多少个@Valid参数就需要有多少个对应的Errors参数,它们是逐一对应的。前面有提到我们可以通过DataBinder来指定需要使用的Validator,我们可以看到在上面代码中我们通过@InitBinder标记的方法initBinder设置了当前Controller需要使用的Validator是UserValidator。这样当我们要求处理器方法login时就会使用DataBinder设定的UserValidator来校验当前的表单对象User,首先会通过UserValidator的supports方法判断其是不是支持User对象的校验,若支持则调用UserValidator的validate方法,并把相干的校验信息寄存到当前的Errors对象中。接着我们就能够在我们的处理器方法中根据是不是有校验异常信息来做不同的操作。在上面代码中我们定义了在有异常信息的时候就跳转到登陆页面。这样我们就能够在登陆页面上通过errors标签来展现这些毛病信息了。
我们知道在Controller类中通过@InitBinder标记的方法只有在要求当前Controller的时候才会被履行,所以其中定义的Validator也只能在当前Controller中使用,如果我们希望1个Validator对所有的Controller都起作用的话,我们可以通过WebBindingInitializer的initBinder方法来设定了。另外,在SpringMVC的配置文件中通过mvc:annotation-driven的validator属性也能够指定全局的Validator。代码以下所示:
Xml代码
- <?xml version="1.0" encoding="UTF⑻"?>
- <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⑶.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context⑶.0.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc⑶.0.xsd">
-
- <mvc:annotation-driven validator="userValidator"/>
-
- <bean id="userValidator" class="com.xxx.xxx.UserValidator"/>
-
- ...
- </beans>
使用JSR⑶03 Validation进行验证
JSR⑶03是1个数据验证的规范,这里我不会讲这个规范是怎样回事,只会讲1下JSR⑶03在SpringMVC中的利用。JSR⑶03只是1个规范,而Spring也没有对这1规范进行实现,那末当我们在SpringMVC中需要使用到JSR⑶03的时候就需要我们提供1个对JSR⑶03规范的实现,Hibernate Validator是实现了这1规范的,这里我将以它作为JSR⑶03的实现来说解SpringMVC对JSR⑶03的支持。
JSR⑶03的校验是基于注解的,它内部已定义好了1系列的限制注解,我们只需要把这些注解标记在需要验证的实体类的属性上或是其对应的get方法上。来看以下1个需要验证的实体类User的代码:
Java代码
-
- import javax.validation.constraints.Min;
- import javax.validation.constraints.NotNull;
- import org.hibernate.validator.constraints.NotBlank;
-
- public class User {
-
- private String username;
-
- private String password;
-
- private int age;
-
- @NotBlank(message="用户名不能为空")
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- @NotNull(message="密码不能为null")
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- @Min(value=10, message="年龄的最小值为10")
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- }
我们可以看到我们在username、password和age对应的get方法上都加上了1个注解,这些注解就是JSR⑶03里面定义的限制,其中@NotBlank是Hibernate Validator的扩大。不难发现,使用JSR⑶03来进行校验比使用Spring提供的Validator接口要简单的多。我们知道注解只是起到1个标记性的作用,它是不会直接影响到代码的运行的,它需要被某些类辨认到才能起到限制作用。使用SpringMVC的时候我们只需要把JSR⑶03的实现者对应的jar包放到classpath中,然后在SpringMVC的配置文件中引入MVC
Namespace,并加上<mvn:annotation-driven/>就能够非常方便的使用JSR⑶03来进行实体对象的验证。加上了<mvn:annotation-driven/>以后Spring会自动检测classpath下的JSR⑶03提供者并自动启用对JSR⑶03的支持,把对应的校验毛病信息放到Spring的Errors对象中。这时候候SpringMVC的配置文件以下所示:
Xml代码
- <?xml version="1.0" encoding="UTF⑻"?>
- <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⑶.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context⑶.0.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc⑶.0.xsd">
-
- <mvc:annotation-driven/>
- </beans>
接着我们来定义1个使用User对象作为参数接收者的Controller,其代码以下所示:
Java代码
- import javax.validation.Valid;
- import org.springframework.stereotype.Controller;
- import org.springframework.validation.BindingResult;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- @Controller
- public class UserController {
-
- @RequestMapping("login")
- public String login(@Valid User user, BindingResult result) {
- if (result.hasErrors())
- return "user/login";
- return "redirect:/";
- }
-
- }
这样当我们不带任何参数要求login.do的时候就不能通过实体对象User的属性数据有效性限制,然后会把对应的毛病信息放置在当前的Errors对象中。
JSR⑶03原生支持的限制有以下几种:
限制
|
说明
|
@Null
|
限制只能为null
|
@NotNull
|
限制必须不为null
|
@AssertFalse
|
限制必须为false
|
@AssertTrue
|
限制必须为true
|
@DecimalMax(value)
|
限制必须为1个不大于指定值的数字
|
@DecimalMin(value)
|
限制必须为1个不小于指定值的数字
|
@Digits(integer,fraction)
|
限制必须为1个小数,且整数部份的位数不能超过integer,小数部份的位数不能超过fraction
|
@Future
|
限制必须是1个将来的日期
|
@Max(value)
|
限制必须为1个不大于指定值的数字
|
@Min(value)
|
限制必须为1个不小于指定值的数字
|
@Past
|
限制必须是1个过去的日期
|
@Pattern(value)
|
限制必须符合指定的正则表达式
|
@Size(max,min)
|
限制字符长度必须在min到max之间
|
除JSR⑶03原生支持的限制类型以外我们还可以定义自己的限制类型。定义自己的限制类型首先我们得定义1个该种限制类型的注解,而且该注解需要使用@Constraint标注。现在假定我们需要定义1个表示金额的限制类型,那末我们可以这样定义:
Java代码
-
- 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 {};
-
- }
我们可以看到在上面代码中我们定义了1个Money注解,而且该注解上标注了@Constraint注解,使用@Constraint注解标注表明我们定义了1个用于限制的注解。@Constraint注解的validatedBy属性用于指定我们定义确当前限制类型需要被哪一个ConstraintValidator进行校验。在上面代码中我们指定了Money限制类型的校验类是MoneyValidator。另外需要注意的是我们在定义自己的限制类型的注解时有3个属性是必须定义的,如上面代码所示的message、groups和payload属性。
在定义了限制类型Money以后,接下来就是定义我们的限制类型校验类MoneyValidator了。限制类型校验类必须实现接口javax.validation.ConstraintValidator,并实现它的initialize和isValid方法。我们先来看1下MoneyValidator的代码示例:
Java代码
-
- 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是使用了泛型的。它1共需要指定两种类型,第1个类型是对应的initialize方法的参数类型,第2个类型是对应的isValid方法的第1个参数类型。从上面的两个方法我们可以看出isValid方法是用于进行校验的,有时候我们在校验的进程中是需要取当前的限制类型的属性来进行校验的,比如我们在对@Min限制类型进行校验的时候我们是需要通过其value属性获得到当前校验类型定义的最小值的,我们可以看到isValid方法没法获得到当前的限制类型Money。这个时候initialize方法的作用就出来了。我们知道initialize方法是可以获得到当前的限制类型的,所以当我们在校验某种限制类型时需要获得当前限制类型的某种属性的时候,我们可以给当前的ConstraintValidator定义对应的属性,然后在initialize方法中给该属性赋值,接下来我们就能够在isValid方法中使用其对应的属性了。针对这类情况我们来看1个代码示例,现在假定我要定义自己的@Min限制类型和对应的MinValidator校验器,那末我可以以下定义:
Min限制类型
Java代码
- @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校验器
Java代码
- 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;
- }
-
- }
继续来讲1下ConstraintValidator泛型的第2个类型,我们已知道它的第2个类型是对应的isValid的方法的第1个参数,从我给的参数名称value来看也能够知道isValid方法的第1个参数正是对应确当前需要校验的数据的值,而它的类型也正是对应的我们需要校验的数据的数据类型。这二者的数据类型必须保持1致,否则Spring会提示找不到对应数据类型的ConstraintValidator。建立了自己的限制类型及其对应的ConstraintValidator后,其用法跟标准的JSR⑶03限制类型是1样的。以下就是使用了上述自己定义的JSR⑶03限制类型——Money限制和Min限制的1个实体类:
Java代码
- 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")
-
------分隔线----------------------------
------分隔线----------------------------