在开发业务时,不可避免的需要处理一些校验, 如果是写 if-else 这种代码去校验, 那会有一大段这样的代码。不过还好有个校验插件: javax.validation.validation-api ,不过一般会引用hibernate的校验组件: org.hibernate.hibernate-validator , 它已经引用了validation-api组件。
1.基础校验类型
JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。注解如下:
创建需要被校验的实体类,使用一些比较常用的校验注解,还是比较浅显易懂的,字段上的注解名称即可推断出校验内容,每一个注解都包含了message字段,用于校验失败时作为提示信息,
特殊的校验注解,如Pattern(正则校验),还可以自己添加正则表达式,在实体类上加上@notnull @size等验证,例如:
1 @Entity 2 @Table(name = "userinfo", uniqueConstraints = @UniqueConstraint(columnNames = "username")) 3 public class User { 4 private Integer id; 5 @Size(min = 4, max = 15, message = "用户名长度为4-15位") 6 private String username; 7 @Size(min = 6, max = 18, message = "密码长度为6-18位") 8 private String password; 9 @Pattern(regexp = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$", message = "邮箱格式不正确") 10 private String email; 11 @Pattern(regexp = "^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message = "手机号填写错误") 12 private String phone; 13 @NotEmpty(message = "性别不能为空") 14 private String sex; 15 private Date creat_time; 16 private Date update_time; 17 @Id 18 @GeneratedValue 19 public Integer getId() { 20 return id; 21 }
//省略后面set和get方法
说明:
@null 被注释的元素必须为空
@notnull 被注释的元素必须不为空
@AssertTrue 被注释的元素必须为true
@AssertFalse 被注释的元素必须为false
@Min 被注释的元素必须是一个数字,其值必须大于等于指定的最小数
@Max 被注释的元素必须是一个数字,其值必须小于等于指定的最大数
@DecimalMin 被注释的元素必须是一个数字,其值必须大于等于指定的最小数
@DecimalMax 被注释的元素必须是一个数字,其值必须小于等于指定的最大数
@Size(min,max) 被注释的元素的大小必须在指定的范围内
@Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是电子邮箱
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串必须非空
@Range 被注释的元素必须在合适的范围内
@NotNull 和 @NotEmpty 和 @NotBlank 区别:
@NotEmpty 用在集合类上面,@NotBlank 用在String上面, @NotNull 用在基本类型上。
我们可以看到我们在username、password和age对应的get方法上都加上了一个注解,这些注解就是JSR-303里面定义的限制,把对应的校验错误信息放到Spring的Errors对象中。
2.在@Controller中校验数据
接着我们来定义一个使用User对象作为参数接收者的Controller,其代码如下所示:
1 @Controller 2 public class FooController { 3 @RequestMapping("/foo") 4 public String foo(@Validated Foo foo <1>, BindingResult bindingResult <2>) { 5 if(bindingResult.hasErrors()){ 6 for (FieldError fieldError : bindingResult.getFieldErrors()) { 7 //... 8 } 9 return "fail"; 10 } 11 return "success"; 12 } 13 }
<1> 参数Foo前需要加上 @Validated 注解,表明需要spring对其进行校验,而校验的信息会存放到其后的 BindingResult 中。注意,必须相邻,如果有多个参数需要校验,形式可以如下。 foo(@Validated Foo foo, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult){ //*** } ;即一个校验类对应一个校验结果。
<2> 校验结果会被自动填充,在controller中可以根据业务逻辑来决定具体的操作,如跳转到错误页面。
3.页面获取message里的错误信息
现、在Form里 th:Object 里添加实体的对象,通过 #fields.hasErrors('字段名') 判断该字段是否有错误,如果有错误,通过 th:errors=“*{字段名}” 显示messages里的错误信息提示。例如:
<p th:if="${#fields.hasErrors('username')} " th:errors="*{username}" class="text-danger"></p>
4.分组校验
例如:
1 Class Foo{ 2 @Min(value = 18,groups = {Adult.class}) 3 private Integer age; 4 public interface Adult{} 5 public interface Minor{} 6 }
这个里的age只有在Adult的分组下才会背校验,例如:
1 @RequestMapping("/drink") 2 public String drink(@Validated({Foo.Adult.class}) Foo foo, BindingResult bindingResult) { 3 if(bindingResult.hasErrors()){ 4 for (FieldError fieldError : bindingResult.getFieldErrors()) {}......
5.自定义校验
(1)我们尝试添加一个“字符串不能包含空格”的限制。
1 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) 2 @Retention(RUNTIME) 3 @Documented 4 @Constraint(validatedBy = {CannotHaveBlankValidator.class})<1> 5 public @interface CannotHaveBlank { 6 //默认错误消息 7 String message() default "不能包含空格"; 8 //分组 9 Class<?>[] groups() default {}; 10 //负载 11 Class<? extends Payload>[] payload() default {}; 12 //指定多个时使用 13 @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) 14 @Retention(RUNTIME) 15 @Documented 16 @interface List { 17 CannotHaveBlank[] value(); 18 } 19 }
我们不需要关注太多东西,使用spring validation的原则便是便捷我们的开发,例如payload,List ,groups,都可以忽略。
<1>自定义注解中指定了这个注解真正的验证者类。
(2)编写真正的校验者类
1 public class CannotHaveBlankValidator implements <1> ConstraintValidator<CannotHaveBlank, String> { 2 @Override 3 public void initialize(CannotHaveBlank constraintAnnotation) { 4 } 5 @Override 6 public boolean isValid(String value, ConstraintValidatorContext context <2>) { 7 //null时不进行校验 8 if (value != null && value.contains(" ")) { 9 <3> 10 //获取默认提示信息 11 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); 12 System.out.println("default message :" + defaultConstraintMessageTemplate); 13 //禁用默认提示信息 14 context.disableDefaultConstraintViolation(); 15 //设置提示语 16 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); 17 return false; 18 } 19 return true; 20 } 21 }
<1> 所有的验证者都需要实现ConstraintValidator接口,它的接口也很形象,包含一个初始化事件方法,和一个判断是否合法的方法。
1 public interface ConstraintValidator<A extends Annotation, T> { 2 3 void initialize(A constraintAnnotation); 4 5 boolean isValid(T value, ConstraintValidatorContext context); 6 }
<2> ConstraintValidatorContext 这个上下文包含了认证中所有的信息,我们可以利用这个上下文实现获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作。
<3> 一些典型校验操作,或许可以对你产生启示作用。
值得注意的一点是,自定义注解可以用在METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER之上, ConstraintValidator 的第二个泛型参数T,是需要被校验的类型。