1. 概述
本文介绍在 Spring Boot 中实现对请求的数据进行校验数据校验常用到概念:
JSR303/JSR-349: JSR303 是一项标准, 只提供规范不提供实现, 规定一些校验规范即校验注解, 如 @Null,@NotNull,@Pattern, 位于 javax.validation.constraints 包下 JSR-349 是其的升级版本, 添加了一些新特性
hibernate validation:hibernate validation 是对这个规范的实现, 并增加了一些其他校验注解, 如 @Email,@Length,@Range 等等
spring validation:spring validation 对 hibernate validation 进行了二次封装, 在 springmvc 模块中添加了自动校验, 并将校验信息封装进了特定的类中
本文主要包括如下内容:
演示 spring boot validation + 异常捕获机制实现数据自动校验功能
自定义校验注解, 并演示这个用法
2. 演示 spring boot 的数据自动校验功能
功能: 向服务发送请求, 这个请求带上参数, 服务需要对参数进行校验
2.1. Result
封装返回处理结果, 如果 code=200, 则表示添加成功, code=400, 则表示输入的数据异常
- public class Result {
- private int code;
- private String message;
- // set/get 方法略
- }
- 2.2. CustomerDto:
客户端的请求封装到这个 dto 中使用校验注解注解此类的成员属性这些注解的功能看名称就可以看出来其中 @PhoneValidation 就我们自定义的注解, 这个后面会说明每个注解里有个属性 message, 如果我们不想使用系统默认提供的报错信息, 我们可以修改这个值
- public class CustomerDto {@Size(min=2, max=30)
- private String name;
- // 自定义错误信息
- @NotEmpty(message = "自定义错误信息, Email 不能为空")
- private String email;
- @NotNull
- @Min(18) @Max(100)
- private Integer age;
- @NotNull
- private Gender gender;
- @DateTimeFormat(pattern="MM/dd/yyyy")
- @NotNull @Past
- private Date birthday;
- // 自定义规则注解
- @PhoneValidation
- private String phone;
- public enum Gender {
- MALE, FEMALE
- }
- // set/get 方法略
- }
2.3. 异常捕获类: BindExceptionHanlder
如果数据校验不通过, 则 Spring boot 会抛出 BindException 异常, 我们可以捕获这个异常并使用 Result 封装返回结果通过 @RestControllerAdvice 定义异常捕获类
- @RestControllerAdvice
- public class BindExceptionHanlder {
- private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
- @ExceptionHandler(BindException.class)
- public Result handleBindException(BindException ex) {
- // ex.getFieldError(): 随机返回一个对象属性的异常信息如果要一次性返回所有对象属性异常信息, 则调用 ex.getAllErrors()
- FieldError fieldError = ex.getFieldError();
- StringBuilder sb = new StringBuilder();
- sb.append(fieldError.getField()).append("=[").append(fieldError.getRejectedValue()).append("]")
- .append(fieldError.getDefaultMessage());
- // 生成返回结果
- Result errorResult = new Result();
- errorResult.setCode(400);
- errorResult.setMessage(sb.toString());
- return errorResult;
- }
- }
- 2.4. ValidationCtrl :
提供 Control 层的对外接口
- @RestController
- public class ValidationCtrl {
- private static final Logger logger = LoggerFactory
- .getLogger(ValidationCtrl.class);
- @RequestMapping(value = "/validation/save", method = RequestMethod.GET)
- public Result saveCustomerPage(@Validated CustomerDto model) {
- logger.info("Good" + model.getBirthday());
- Result okResult = new Result();
- okResult.setCode(200);
- okResult.setMessage(JSONObject.toJSON(model).toString());
- return okResult;
- }
- }
2.5. 测试:
正确的请求:
测试 URL 请求: http://127.0.0.1:8080//validation/save?name=name&email=126@126.com&age=18&gender=MALE&birthday=2/22/1985&phone=13589567201
返回:
{ "code":200, "message":"{\"birthday\":477849600000,\"gender\":\"MALE\",\"phone\":\"13589567201\",\"name\":\"name\",\"age\":18,\"email\":\"126@126.com\"}" }
校验 @Size 是否启作用
测试 URL 请求: http://127.0.0.1:8080//validation/save?name=n&email=126@126.com&age=18&gender=MALE&birthday=2/22/1985
返回:
{ "code":400, "message":"name=[n] 个数必须在 2 和 30 之间" }
校验 Eamil 为空, 输出自定义异常
测试 URL 请求: http://127.0.0.1:8080//validation/save?name=name&email=&age=18&gender=MALE&birthday=2/22/1985&phone=13589567201
返回:
{ "code":400, "message":"email=[] 自定义错误信息, Email 不能为空" }
3. 自定义校验注解, 并演示这个用法
功能: 自定义校验注解, 对输入手机进行合法性检查并演示这个用法
3.1. @PhoneValidation:
定义自己的校验注解 头注解 @Constraint 属性 validatedBy 指定真正执行校验的类 PhoneValidationValidator
- @Documented
- // 指定真正实现校验规则的类
- @Constraint(validatedBy = PhoneValidationValidator.class)
- @Target( { ElementType.METHOD, ElementType.FIELD })
- @Retention(RetentionPolicy.RUNTIME)
- public @interface PhoneValidation {
- String message() default "不是正确的手机号码";
- Class<?>[] groups() default { };
- Class<? extends Payload>[] payload() default { };
- @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
- @Retention(RUNTIME)
- @Documented
- @interface List {
- PhoneValidation[] value();
- }
- }
- 3.2. PhoneValidationValidator
真正执行校验的类, 对输入的手机执行校验
- public class PhoneValidationValidator implements ConstraintValidator<PhoneValidation, String> {
- private static final Pattern PHONE_PATTERN = Pattern.compile(
- "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$"
- );
- @Override
- public void initialize(PhoneValidation constraintAnnotation) {
- }
- @Override
- public boolean isValid(String value, ConstraintValidatorContext context) {
- if ( value == null || value.length() == 0 ) {
- return true;
- }
- Matcher m = PHONE_PATTERN.matcher(value);
- return m.matches();
- }
- }
3.3. 在 Customer 上使用新的注解
- public class CustomerDto {
- // 自定义规则注解
- @PhoneValidation
- private String phone;
- // set/get 方法略
- }
3.4. 测试
启动服务执行测试
正确请求 URL: http://127.0.0.1:8080//validation/save?name=name&email=126@126.com&age=18&gender=MALE&birthday=2/22/1985&phone=13589567201
返回结果:
{ "code":200, "message":"{\"birthday\":477849600000,\"gender\":\"MALE\",\"phone\":\"13589567201\",\"name\":\"name\",\"age\":18,\"email\":\"126@126.com\"}" }
输入错误的手机号码
测试 URL 请求: http://127.0.0.1:8080//validation/save?name=name&email=126@126.com&age=18&gender=MALE&birthday=2/22/1985&phone=13589567
返回结果:
{ "code":400, "message":"phone=[13589567] 不是正确的手机号码" }
3.5. 代码
所有的详细代码见 github 代码, 请尽量使用 tag v0.18, 不要使用 master, 因为 master 一直在变, 不能保证文章中代码和 github 上的代码一直相同
来源: https://juejin.im/entry/5ab0edc06fb9a028c71e4889