SpringBoot2 | 第二十六篇(二):自定义验证

​ 上一篇中介绍了数据有效性校验的重要性,也简单介绍了如何用轻松的方式搞定数据有效性校验,但是当系统自带的注解无法满足我们的要求时候应该咋办呢?这就是本章将给各位介绍的自定义 Validator 注解

[TOC]

为何要自定义

javax.validation 包与 hibernate-validator 包中存在的注解几乎可以满足大部分的要求,又拥有基于正则表达式的@Pattern,为什么还需要自己去定义呢?

原因如下

  • 正则效率不高
  • 正则可读性不好
  • 正则门槛较高,很多开发者并不会编写正则表达式

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.1.0.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9

1、pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2、application.yml

1
2
server:
port: 8088

3、annotation

这里定义了一个 @DateTime 注解,在该注解上标注了 @Constraint 注解,它的作用就是指定一个具体的校验器类

关键字段(强制性)

  • message: 验证失败提示的消息内容
  • groups: 为约束指定验证组(非常不错的一个功能,下一章介绍)
  • payload: 不太清楚
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.fatal.annotation;

import com.fatal.validator.DateTimeValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义注解:校验 Date 类型的格式
* 最后两个属性时必须要的。否则会报错
* @author: Fatal
* @date: 2018/11/28 0028 11:40
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD, ElementType.PARAMETER}) // 属性和参数上可以使用
@Constraint(validatedBy = DateTimeValidator.class) // 指定该注解的校验器
public @interface DateTime {

/**
* 错误消息 - 关键字段
*
* @return 默认错误消息
*/
String message() default "格式错误";

/**
* 格式
*
* @return 验证的日期格式
*/
String format() default "yyyy-MM-dd";

/**
* 允许我们为约束指定验证组 - 关键字段(TODO 下一章中会介绍)
*
* @return 分组
*/
Class<?>[] groups() default {};

/**
* payload - 关键字段
*
* @return 暂时不清楚, 知道的欢迎留言交流
*/
Class<? extends Payload>[] payload() default {};

}

4、entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.fatal.entity;

import com.fatal.annotation.DateTime;
import lombok.Data;

/**
* Student 实体
* @author: Fatal
* @date: 2018/11/28 0028 11:48
*/
@Data
public class Student {

private String name;

private Integer age;

@DateTime(format = "yyyy-MM-dd HH:mm", message = "您输入的格式错误,正确的格式为:{format}")
private String birthday;

}

5、validator

定义校验器类 DateTimeValidator 实现 ConstraintValidator 接口,实现接口后需要实现它里面的 initialize:isValid: 方法。

方法介绍

  • initialize: 主要用于初始化,它可以获得当前注解的所有属性
  • isValid: 进行约束验证的主体方法,其中 value 就是验证参数的具体实例,context 代表约束执行的上下文环境。

​ 这里的验证方式虽然简单,但职责明确;为空验证可以使用 @NotBlank、@NotNull、@NotEmpty 等注解来进行控制,而不是在一个注解中做各种各样的规则判断,应该职责分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.fatal.validator;

import com.fatal.annotation.DateTime;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
* 自定义 @DateTime 验证器
* ConstraintValidator<A, T>
* A: 要绑定校验器的注解
* T: 校验的类型
* @author: Fatal
* @date: 2018/11/28 0028 11:43
*/
public class DateTimeValidator implements ConstraintValidator<DateTime, String> {

private DateTime dateTime;

@Override
public void initialize(DateTime dateTime) {
this.dateTime = dateTime;
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 如果 value 为空则不进行格式验证,为空验证可以使用 @NotBlank @NotNull @NotEmpty 等注解来进行控制,职责分离
if (value == null) {
return true;
}
String format = dateTime.format();
// 拦截长度不一致的
if (value.length() != format.length()) {
return false;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
simpleDateFormat.parse(value);
} catch (ParseException e) {
return false;
}
return true;
}
}

6、controlller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.fatal.controller;

import com.fatal.entity.Student;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author: Fatal
* @date: 2018/11/28 0028 11:38
*/
@RestController
public class ValidatorController {

@GetMapping("/")
public String validate(@Validated Student student, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}

return "success";
}

}

显示

启动项目

可以使用 IDEA 自带的 RestClient 测试,也可以使用 Postman 测试

访问 http://localhost:8088/

  • IDEA 自带的 RestClient

    • 错误格式

    1543385251989

    1543385261012

    • 正常格式

    1543385785159

    1543385803024

  • Postman

    • 错误格式

    1543385656746

    • 正确格式

    1543385862302

笔记

ConstraintValidator 接口

ConstraintValidator

  • A 要绑定校验器的注解
  • T 校验的类型

参考资料

一起来学SpringBoot | 第二十篇:轻松搞定数据验证(二)

总结

这只是对自定义验证功能的展示,实际上校验时间并非这么操作。如果真的是 Date 类型接收参数,那么在转换器里边校验即可,它抛出异常我们在全局异常处理器中定制 Handler 返回就行。

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter26_2

学习 唐亚峰 前辈的经验