首先创建User实例,并加入几个属性:
public class User {
private String username;
private String password;
private String nickname;
get/set // 省略
}
接着创建用于校检的类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;
// 此处只拿校验password属性举例
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是存储错误信息的类,两者必须一一对应,并且位置挨着,不能中间有其他参数。
如果要实现全局校验的话需要配置springMVC.xml文件(不建议使用这种方式配置)
...
使用这个需要导入支持JSR-303标准的包,建议使用Hibernate Validator这个包,先看这个标准的原生标注
限制 |
说明 |
@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,也可以通过正则表达式和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";
}
}
2.自定义Annotation Validator
除了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 {
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 {
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;
}
}
获取校验信息:
@RequestMapping("/login")
public String testValid(@Valid User user, BindingResult result){
if (result.hasErrors()){
List errorList = result.getAllErrors();
for(ObjectError error : errorList){
System.out.println(error.getDefaultMessage());
}
}
return "test";
}