spring之validation校验
对于任何⼀个应⽤⽽⾔在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进⾏验证。SpringMVC⾃⾝对数据在服务端的校验有⼀个⽐较好的⽀持,它能将我们提交到服务端的数据按照我们事先的约定进⾏数据有效性验证,对于不合格的数据信息SpringMVC会把它保存在错误对象中,这些错误信息我们也可以通过SpringMVC提供的标签在前端JSP页⾯上进⾏展⽰。
使⽤Validator接⼝进⾏验证
在SpringMVC中提供了⼀个Validator接⼝,我们可以通过该接⼝来定义我们⾃⼰对实体对象的验证。接下来看⼀个⽰例。
假设我们现在有⼀个需要进⾏验证的实体类User,其代码如下所⽰:
1. public class User {
2. private String username;
3. private String password;
4. public String getUsername() {
5. return username;
6.    }
7. public void setUsername(String username) {
8. this.username = username;
9.    }
10. public String getPassword() {
11. return password;
12.    }
13. public void setPassword(String password) {
14. this.password = password;
15.    }
16. public String toString() {
17. return username + ", " + password;
18.    }
19. }
那么当我们需要使⽤SpringMVC提供的Validator接⼝来对该实体类进⾏校验的时候该如何做呢?这个时候我们应该提供⼀个Validator 的实现类,并实现Validator接⼝的supports⽅法和validate⽅法。Supports⽅法⽤于判断当前的Validator实现类是否⽀持校验当前需要校验的实体类,只有当supports⽅法的返回结果为true的时候,该Validator接⼝实现类的validate⽅法才会被调⽤来对当前需要校验的实体类进⾏校验。这⾥假设我们需要验证User类的username和password都不能为空,先给出其代码,稍后再进⾏解释。这⾥我们定义⼀个UserValidator,其代码如下:
1. import org.springframework.validation.Errors;
2. import org.springframework.validation.ValidationUtils;
3. import org.springframework.validation.Validator;
4. public class UserValidator implements Validator {
5. public boolean supports(Class<?> clazz) {
6. // TODO Auto-generated method stub
7. return User.class.equals(clazz);
8.    }
9. public void validate(Object obj, Errors errors) {
10. // TODO Auto-generated method stub
11.        jectIfEmpty(errors, "username", null, "Username is empty.");
12.        User user = (User) obj;
13. if (null == Password() || "".Password()))
14.            jectValue("password", null, "Password is empty.");
15.    }
16. }
在上述代码中我们在supports⽅法中定义了该UserValidator只⽀持对User对象进⾏校验。在validate⽅法中我们校验了User对象的username和password不为empty的情况,这⾥的empty包括null和空字符串两种情况。ValidationUtils类是Spring中提供的⼀个⼯具类。Errors就是Spring⽤来存放错误信息的对象。
我们已经定义了⼀个对User类进⾏校验的UserValidator了,但是这个时候UserValidator还不能对User对象进⾏校验,因为我们还没有告诉Spring应该使⽤UserValidator来校验User对象。在SpringMVC中我们可以使⽤DataBinder来设定当前Controller需要使⽤的Validator。先来看下⾯⼀段代码:
1. import javax.validation.Valid;
2. import org.springframework.stereotype.Controller;
3. import org.springframework.validation.BindingResult;
4. import org.springframework.validation.DataBinder;
5. import org.springframework.web.bind.annotation.InitBinder;
6. import org.springframework.web.bind.annotation.RequestMapping;
7. @Controller
8. public class UserController {
9. @InitBinder
10. public void initBinder(DataBinder binder) {
11.        binder.setValidator(new UserValidator());
12.    }
13. @RequestMapping("login")
14. public String login(@Valid User user, BindingResult result) {
15. if (result.hasErrors())
16. return "redirect:user/login";
17. return "redirect:/";
18.    }
19. }
在上⾯这段代码中我们可以看到我们定义了⼀个UserController,该Controller有⼀个处理login操作的处理器⽅法login,它需要接收客户端发送的⼀个User对象,我们就是要利⽤前⾯的UserValidator对该User对象进⾏校验。⾸先我们可以看到我们login⽅法接收的参数user是⽤@Valid进⾏标注的,这⾥的@Valid是定义在JSR-303标准中的,我这⾥使⽤的是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中使⽤,如果我们希望⼀个Validator对所有的Controller都起作⽤的话,我们可以通过WebBindingInitializer的initBinder⽅法来设定了。另外,在SpringMVC的配置⽂件中通过mvc:annotation-driven的validator属性也可以指定全局的Validator。代码如下所⽰:Xml代码
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans xmlns="/schema/beans"
3.    xmlns:xsi="/2001/XMLSchema-instance" xmlns:context="/schema/context"
4.    xmlns:mvc="/schema/mvc"
5.    xsi:schemaLocation="/schema/beans
6.      /schema/beans/spring-beans-3.0.xsd
7.      /schema/context
8.      /schema/context/spring-context-3.0.xsd
9.      /schema/mvc
10.      /schema/mvc/spring-mvc-3.0.xsd">
11.
12.    <mvc:annotation-driven validator="userValidator"/>
13.
14.    <bean id="userValidator" class="UserValidator"/>
15.
16.    ...
17. </beans>
springmvc常用标签
使⽤JSR-303 Validation进⾏验证
JSR-303是⼀个数据验证的规范,这⾥我不会讲这个规范是怎么回事,只会讲⼀下JSR-303在SpringMVC中的应⽤。JSR-303只是⼀个规范,⽽Spring也没有对这⼀规范进⾏实现,那么当我们在SpringMVC中需要使⽤到JSR-303的时候就需要我们提供⼀个对JSR-303规范的实现,Hibernate Validator是实现了这⼀规范的,这⾥我将以它作为JSR-303的实现来讲解SpringMVC对JSR-303的⽀持。
JSR-303的校验是基于注解的,它内部已经定义好了⼀系列的限制注解,我们只需要把这些注解标记在需要验证的实体类的属性上或是其对应的get⽅法上。来看以下⼀个需要验证的实体类User的代码:
Java代码
1.
2. import straints.Min;
3. import straints.NotNull;
4. import org.straints.NotBlank;
5.
6. public class User {
7.
8.    private String username;
9.
10.    private String password;
11.
12.    private int age;
13.
14.    @NotBlank(message="⽤户名不能为空")
15.    public String getUsername() {
16.        return username;
17.    }
18.
19.    public void setUsername(String username) {
20.        this.username = username;
21.    }
22.
23.    @NotNull(message="密码不能为null")
24.    public String getPassword() {
25.        return password;
26.    }
27.
28.    public void setPassword(String password) {
29.        this.password = password;
30.    }
31.
32.    @Min(value=10, message="年龄的最⼩值为10")
33.    public int getAge() {
34.        return age;
35.    }
36.
37.    public void setAge(int age) {
38.        this.age = age;
39.    }
40.
41. }
我们可以看到我们在username、password和age对应的get⽅法上都加上了⼀个注解,这些注解就是JSR-303⾥⾯定义的限制,其中
@NotBlank是Hibernate Validator的扩展。不难发现,使⽤JSR-303来进⾏校验⽐使⽤Spring提供的Validator接⼝要简单的多。我们知道注解只是起到⼀个标记性的作⽤,它是不会直接影响到代码的运
⾏的,它需要被某些类识别到才能起到限制作⽤。使⽤SpringMVC的时候我们只需要把JSR-303的实现者对应的jar包放到classpath中,然后在SpringMVC的配置⽂件中引⼊MVC Namespace,并加上<mvn:annotation-driven/>就可以⾮常⽅便的使⽤JSR-303来进⾏实体对象的验证。加上了<mvn:annotation-driven/>之后Spring会⾃动检测classpath下的JSR-303提供者并⾃动启⽤对JSR-303的⽀持,把对应的校验错误信息放到Spring的Errors对象中。这时候SpringMVC的配置⽂件如下所⽰:
Xml代码
1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans xmlns="/schema/beans"
3.    xmlns:xsi="/2001/XMLSchema-instance" xmlns:context="/schema/context"
4.    xmlns:mvc="/schema/mvc"
5.    xsi:schemaLocation="/schema/beans
6.      /schema/beans/spring-beans-3.0.xsd
7.      /schema/context
8.      /schema/context/spring-context-3.0.xsd
9.      /schema/mvc
10.      /schema/mvc/spring-mvc-3.0.xsd">
11.
12.    <mvc:annotation-driven/>
13. </beans>
接着我们来定义⼀个使⽤User对象作为参数接收者的Controller,其代码如下所⽰:
Java代码
1. import javax.validation.Valid;
2. import org.springframework.stereotype.Controller;
3. import org.springframework.validation.BindingResult;
4. import org.springframework.web.bind.annotation.RequestMapping;
5.
6. @Controller
7. public class UserController {
8.
9.    @RequestMapping("login")
10.    public String login(@Valid User user, BindingResult result) {
11.        if (result.hasErrors())
12.            return "user/login";
13.        return "redirect:/";
14.    }
15.
16. }
这样当我们不带任何参数请求login.do的时候就不能通过实体对象User的属性数据有效性限制,然后会把对应的错误信息放置在当前的Errors对象中。
JSR-303原⽣⽀持的限制有如下⼏种:
限制说明
@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之间
除了JSR-303原⽣⽀持的限制类型之外我们还可以定义⾃⼰的限制类型。定义⾃⼰的限制类型⾸先我们得定义⼀个该种限制类型的注解,⽽且该注解需要使⽤@Constraint标注。现在假设我们需要定义⼀个表⽰⾦额的限制类型,那么我们可以这样定义:
Java代码
1.
2. import java.lang.annotation.ElementType;
3. import java.lang.annotation.Retention;
4. import java.lang.annotation.RetentionPolicy;
5. import java.lang.annotation.Target;
6.
7. import javax.validation.Constraint;
8. import javax.validation.Payload;
9.
10. x.constraint.impl.MoneyValidator;
11.
12. @Target({ElementType.FIELD, ElementType.METHOD})
13. @Retention(RetentionPolicy.RUNTIME)
14. @Constraint(validatedBy=MoneyValidator.class)
15. public @interface Money {
16.
17.    String message() default"不是⾦额形式";
18.
19.    Class<?>[] groups() default {};
20.
21.    Class<? extends Payload>[] payload() default {};
22.
23. }
我们可以看到在上⾯代码中我们定义了⼀个Money注解,⽽且该注解上标注了@Constraint注解,使⽤@Constraint注解标注表明我们定义了⼀个⽤于限制的注解。@Constraint注解的validatedBy属性⽤于指定我们定义的当前限制类型需要被哪个ConstraintValidator进⾏校验。在上⾯代码中我们指定了Money限制类型的校验类是MoneyValidator。另外需要注意的是我们在定义⾃⼰的限制类型的注解时有三个属性是必须定义的,如上⾯代码所⽰的message、groups和payload属性。
在定义了限制类型Money之后,接下来就是定义我们的限制类型校验类MoneyValidator了。限制类型校验类必须实现接⼝
javax.validation.ConstraintValidator,并实现它的initialize和isValid⽅法。我们先来看⼀下MoneyValidator的代码⽰例:
Java代码
1.
2. import Pattern;
3.
4. import javax.validation.ConstraintValidator;
5. import javax.validation.ConstraintValidatorContext;
6.
7. x.constraint.Money;
8.
9. public class MoneyValidator implements ConstraintValidator<Money, Double> {
10.
11.    private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表⽰⾦额的正则表达式
12.    private Pattern moneyPattern = Patternpile(moneyReg);
13.
14.    public void initialize(Money money) {
15.        // TODO Auto-generated method stub
16.
17.    }
18.
19.    public boolean isValid(Double value, ConstraintValidatorContext arg1) {
20.        // TODO Auto-generated method stub
21.        if (value == null)
22.            return true;
23.        return moneyPattern.String()).matches();
24.    }
25.
26. }
从上⾯代码中我们可以看到ConstraintValidator是使⽤了泛型的。它⼀共需要指定两种类型,第⼀个类型是对应的initialize⽅法的参数类型,第⼆个类型是对应的isValid⽅法的第⼀个参数类型。从上⾯的两个⽅法我们可以看出isValid⽅法是⽤于进⾏校验的,有时候我们在校验的过程中是需要取当前的限制类型的属性来进⾏校验的,⽐如我们在对@Min限制类型进⾏校验的时候我们是需要通过其value属性获取到当前校验类型定义的最⼩值的,我们可以看到isValid⽅法⽆法获取到当前的限制类型Money。这个时候initialize⽅法的作⽤就出来了。我们知道initialize⽅法是可以获取到当前的限制类型的,所以当我们在校验某种限制类型时需要获取当前限制类型的某种属性的时候,我们可以给当前的ConstraintValidator定义对应的属性,然后在initialize⽅法中给该属性赋值,接下来我们就可以在isValid⽅法中使⽤其对应的属性了。针对于这种情况我们来看⼀个代码⽰例,现在假设我要定义⾃⼰的@Min限制类型和对应的MinValidator校验器,那么我可以如下定义:
Min限制类型
Java代码
1. @Target({ElementType.FIELD, ElementType.METHOD})
2. @Retention(RetentionPolicy.RUNTIME)
3. @Constraint(validatedBy=MinValidator.class)
4. public @interface Min {
5.
6.    int value() default 0;
7.
8.    String message();

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。