SpringBoot校验(validation)

title: SpringBoot校验(validation)
date: 2019-07-15
author: maxzhao
tags:
  - SpringBoot
  - validation
  - hibernate-validation
categories:
  - SpringBoot

元编程

一个健壮的系统都要对外部提交的数据进行完整性、合法性的校验。

校验是我们程序开发中必不可少的过程。

即使开发一个不面对最终用户的工具包,也需要对传入的数据进行缜密的校验来防止引发底层难以追踪的问题。

后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

不使用Bean Validation校验数据的代码基本都是靠大量的 if-else.所以我这里学习使用了 注解方式实现数据校验.

使用

SpringBoot 中的 bean validation 是集成了hibernate-validatortomcat-embed-el

引入依赖

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

简单的校验

@Valid:常见用在方法,类中字段上进行校验

@Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验

BindingResult:是验证是否错误

Model中

@Range(max = 150, min = 1, message = "年龄范围应该在1-150内。")
private Integer age;

Controller中

@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result){
      if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
}

绑定多个校验对象

@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result,@RequestBody @Valid AppUser appUser2,BindingResult result2){
      if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
}

部分标签

Bean Validation 中内置的 constraint

注解 作用
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解 作用
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url

官方详细

中文详细

容易记错的

@NotNull 任何对象的value不能为null

@NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null

@NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0

resource 下新建错误信息配置文件

在resource 目录下新建提示信息配置文件 ValidationMessages.properties

文件中的格式为message=ASCII.

中文的ascii码

hibernate的校验模式

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

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

2、快速失败返回模式

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

两种验证模式配置方式

参考官方文档

failFast:true 快速失败返回模式 false 普通模式

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation
        .byProvider( HibernateValidator.class )        
            .configure()        
            .failFast( true )        
            .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
    }
}

和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)

@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;
       }
   }

手动验证Model

AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser);
   for (ConstraintViolation<AppUser> model : violationSet) {
        System.out.println(model.getMessage());
   }

分组校验(区分新增和修改的规则)

简单使用

场景

新增用户信息和修改用户信息所需要验证的字段是不同的.

public interface AppUserVaildC {
}

public interface AppUserVaildU {
}

Model中

Default 是默认分组.

@Range(min = 0,max = 100,message = "年龄必须在[0,100]",groups={Default.class})
    /**年龄*/
private Integer age;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
    /**性别 0:未知;1:男;2:女*/
private Integer sex;

Controller中使用

@PostMapping("save")
public void v1(@RequestBody @Validated({AppUserVaildC.class, AppUserVaildU.class}) AppUser appUser,BindingResult result){
      if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
}

普通使用

AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,AppUserVaildC,AppUserVaildU);
   for (ConstraintViolation<AppUser> model : violationSet) {
        System.out.println(model.getMessage());
   }

组序列

除了按组指定是否验证之外,还可以指定组的验证顺序,前面组验证不通过的,后面组不进行验证

@GroupSequence({AppUserVaildC.class, AppUserVaildU.class, Default.class})
public interface GroupOrder {
}

Controller中使用

@PostMapping("save")
public void v1(@RequestBody @Validated({GroupOrder.class}) AppUser appUser,BindingResult result){
      if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
}

普通使用

AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,GroupOrder);
   for (ConstraintViolation<AppUser> model : violationSet) {
        System.out.println(model.getMessage());
   }

自定义验证

下面是一个自定义大小写的验证

public enum CaseMode {
    UPPER,
    LOWER;
}


@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
    String message() default "";

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

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

    CaseMode value();
}


public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
    private CaseMode caseMode;
    public void initialize(CheckCase checkCase) {
        this.caseMode = checkCase.value();
    }

    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (s == null) {
            return true;
        }

        if (caseMode == CaseMode.UPPER) {
            return s.equals(s.toUpperCase());
        } else {
            return s.equals(s.toLowerCase());
        }
    }
}

Model中

@Range(value = CaseMode.LOWER ,message = "年必须是小写",groups={Default.class})
    /**年龄*/
private String loginName;
@Range(min = 0,max = 2,message = "性别必须在[0,2]",groups = {AppUserVaildC.class})
    /**性别 0:未知;1:男;2:女*/
private Integer sex;

validator 配置

    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }

本文地址:

SpringBoot校验(validation)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342