基于springboot使用hibernate validator校验数据

在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等

hibernate validator官方文档)提供了一套比较完善、便捷的验证实现方式。

spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

如果是Spring Mvc,那可以直接添加hibernate-validator依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.17.Final</version>
</dependency>

1. 常见的注解

注解 说明
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value=) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value=) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value=, inclusive=) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value=, inclusive=) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Future 检查被注释的日期是否是将来的日期
@FutureOrPresent 检查被注释的日期是现在还是将来
@NotEmpty 检查被注释的元素是否不为null或为空
@Negative 检查被注释的元素是否严格为负。零值被视为无效。
@NegativeOrZero 检查被注释的元素是负数还是零。
@Past 检查被注释的日期是否是过去的日期
@PastOrPresent 检查被注释的日期是过去还是现在
@Pattern(regex=, flags=) 检查被注释的字符串是否与正则表达式匹配match
@Positive 检查被注释元素是否严格为正。零值被视为无效。
@PositiveOrZero 检查被注释元素是正数还是零。
@Size(min=, max=) 检查被注释的元素的大小是否介于min和之间max(包括)
@Email 检查被注释的元素是否为有效的电子邮件地址。
@Digits(integer=, fraction=) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Currency(value=) 检查带注释的货币单位javax.money.MonetaryAmount是否为指定货币单位的一部分。
@ISBN 检查带注释的字符序列是有效的ISBNtype确定ISBN的类型。默认值为ISBN-13。
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@Range(min=, max=) 被注释的元素必须在合适的范围内
@SafeHtml(whitelistType= , additionalTags=, additionalTagsWithAttributes=, baseURI=) 检查带注释的值是否包含潜在的恶意片段,例如<script/>。为了使用此约束,jsoup库必须是类路径的一部分。通过该whitelistType属性,可以选择预定义的白名单类型,可以通过additionalTags或进行细化additionalTagsWithAttributes。前者允许添加没有任何属性的标签,而后者则允许使用注释指定标签和可选的允许属性以及该属性的可接受协议@SafeHtml.Tag。另外,baseURI允许指定用于解析相对URI的基本URI。
@UniqueElements 检查被注释的集合仅包含唯一元素
@URL(protocol=, host=, port=, regexp=, flags=) 根据RFC2396检查带注释的字符序列是否为有效URL。如果任何可选参数protocolhostport指定时,相应的URL片段必须在指定的值相匹配。可选参数,regexpflags允许指定URL必须匹配的其他正则表达式(包括正则表达式标志)。默认情况下,此约束使用java.net.URL构造函数来验证给定的字符串是否表示有效的URL。也提供基于正则表达式的版本RegexpURLValidator-可以通过XML(请参见第8.2节“通过映射约束constraint-mappings)或编程API(请参见第12.14.2节“以编程方式添加约束定义”)进行配置。

2. 动手实践(快速上手)

使用idea新建一个springboot项目并引入web

新建一个User的bean

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.*;

/**
 * @author john
 * @date 2020/4/9 - 10:46
 */
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String id;

    @NotBlank(message = "用户名不能为空")
    @Length(min = 4, max = 8, message = "用户名长度不在4-8之间")
    private String name;

    @NotNull(message = "密码不能为空")
    @Pattern(regexp = "[0-9]\\d+", message = "密码不符合规范")
    private String password;

    @NotNull(message = "年龄不能为空")
    @Range(min = 1, max = 140, message = "年龄值不太正常")
    private Integer age;

    @Max(value = 10, message = "级别超过最大值了")
    @Min(value = 1, message = "级别低于最小值")
    private Integer level;

    private String mobile;
}

在创建一个响应数据的bean

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * @author john
 * @date 2020/4/9 - 11:16
 */
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
    private boolean status;
    private T data;
    private List<String> messages;
}

在创建一个控制器

package com.example.validatordemo.controller;

import com.example.validatordemo.bean.User;
import com.example.validatordemo.response.ApiResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;

/**
 * @author john
 * @date 2020/4/9 - 11:04
 */
@RestController
@RequestMapping("/user")
public class UserController {
    //创建用户
    @PostMapping
    public ApiResponse<User> register(@Valid @RequestBody User user, BindingResult errors) {
        //判断传入用户数据是否合法
        List<String> errs = new ArrayList<>();
        if (errors.hasErrors()) {
            errors.getAllErrors().stream().forEach(error -> {
                FieldError fieldError = (FieldError) error;
                errs.add(fieldError.getDefaultMessage());
            });
            return new ApiResponse<User>(false, null, errs);
        }
        //输入入库
        System.out.println("数据插入成功");
        return new ApiResponse<User>(false, user, errs);
    }

    //修改用户
    @PutMapping
    public ApiResponse<User> update(@Valid @RequestBody User user, BindingResult errors) {
        //判断传入用户数据是否合法
        List<String> errs = new ArrayList<>();
        if (errors.hasErrors()) {
            errors.getAllErrors().stream().forEach(error -> {
                FieldError fieldError = (FieldError) error;
                errs.add(fieldError.getDefaultMessage());
            });
            return new ApiResponse<>(false, null, errs);
        }
        //输入入库
        System.out.println("数据修改成功");
        return new ApiResponse<>(false, user, errs);
    }
}

上面分别定义了创建用户和修改用户的操作

测试创建用户结果

由此可见当传入的数据不符合规范时会被识别并处理


3 . hibernate的校验模式

上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了.

Hibernate Validator有以下两种验证模式:

1 、普通模式(默认是这个模式)

  普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

2、快速失败返回模式

  快速失败返回模式(只要有一个验证失败,则返回)
  

  > 默认是普通模式


配置校验模式

基于上面的案例,继续操作,配置hibernate Validator为快速失败返回模式:

增加如下代码

package com.example.validatordemo.conf;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

/**
 * @author john
 * @date 2020/4/9 - 11:47
 */
@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

再次重启项目,使用postman进行测试

可见此时在遇到第一个不匹配的数据后就结束校验,并快速返回


多个参数的,可以加多个@Valid和BindingResult,如:

public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)

public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)

4. 分组的使用

在上面的例子中,我们定义了user的两个方法一个添加用户,一个是修改用户,正常来说当添加用户操作时,此时的用户id属性应该为空,而当用户处理修改状态时,用户的id应该已经存在,因此我们对id在两种不同的情况下应该区别对待,如何校验用户的id呢,这时我们可以使用分组实现.

具体操作如下

先新建组

package com.example.validatordemo.validator;

import javax.validation.groups.Default;

/**
 * @author john
 * @date 2020/4/9 - 12:00
 */
public interface AddUserGroup extends Default {
}
package com.example.validatordemo.validator;

import javax.validation.groups.Default;

/**
 * @author john
 * @date 2020/4/9 - 12:00
 */
public interface UpdateUserGroup extends Default {
}

修改User的bean,修改部分如下

public class User {
	...
        
    @NotNull(message = "id不能为空", groups = {UpdateUserGroup.class})
    private String id;
    
    ...
}    

修改控制器代码

public class UserController {
 	...

    //修改用户
    @PutMapping
    public ApiResponse<User> update(@Validated(UpdateUserGroup.class) @RequestBody User user, BindingResult errors) {
       ...
    }
}

执行测试,当执行插入用户时

当执行修改操作时

可见此时的不同操作时,对id有不同的判断


5. 自定义校验的使用

在刚才的操作中,我们对一个字段一直没有校验,那就是mobile,使用内置的校验应该无法满足需求,这时候我们可以自定义校验来实现对mobile字段的校验

定义自定义约束,有三个步骤

  • 创建约束注解
  • 实现一个验证器
  • 定义默认的错误信息

创建约束注解

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author john
 * @date 2020/4/9 - 12:39
 */
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {

    /**
     * 错误提示信息,可以写死,也可以填写国际化的key
     */
    String message() default "手机号码不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Mobile[] value();
    }
}

实现一个验证器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

/**
 * @author john
 * @date 2020/4/9 - 12:41
 */
public class MobileValidator implements ConstraintValidator<Mobile, String> {

    /**
     * 手机验证规则
     */
    private Pattern pattern;

    @Override
    public void initialize(Mobile mobile) {
        pattern = Pattern.compile(mobile.regexp());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }

        return pattern.matcher(value).matches();
    }
}

定义默认的错误信息

public class User {

   ....

    @Mobile(message = "手机号码格式异常")
    private String mobile;
}

重启项目执行测试

此时手机格式校验失败

6. 代码

微云下载

7. 参考

springboot使用hibernate validator校验

如何优雅的做数据校验-Hibernate Validator详细使用说明

Hibernate Validator 6.1.2.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

posted @ 2020-04-09 13:05  if年少有为  阅读(1880)  评论(0编辑  收藏  举报