文章目录

  • 1. 基础简介
  • 2. 框架简介
  • 2.1. validation-api
  • 2.2. jakarta.validation-api
  • 2.3. hibernate-validator
  • 2.4. spring-boot-starter-validation
  • 3. 注解说明
  • 3.1. 标识注解
  • 3.2. 约束注解
  • 3.3. 提示信息
  • 4. 使用说明
  • 4.1. 一般性校验
  • 4.2. 自定义校验
  • 4.3. 分组校验
  • 4.4. 异常处理
  • 5. 使用示例





1. 基础简介

JCPJava Community Process)为Java技术制定标准技术规范的机构,任何人都可以注册 JCP 网站,并且可以参与JSRJava Specification RequestsJava规范提案)的评审,也可以提交自己的JSR

Bean ValidationJava定义的一套基于注解的数据校验规范,出自JSR303JSR349JSR380规范提案。目前最新规范是Bean Validation 2.0

JSRs: Java Specification Requests(bean validation)

java 注解校验字段只能是数字 注解校验 springboot_hibernate

2. 框架简介

JSR规范提案只是提供了技术标准,并没有提供具体的实现。具体实现框架有validation-apijakarta.validation-apihibernate-validator

2.1. validation-api

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

引用路径:

D:\Programmer\maven\repository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar

Java2009年的Java EE 6中发布了JSR303以及javax.validation包,对JSR303版本的Bean Validation作了接口实现, 并没有相关校验代码。也就是validation-api

注意:单独使用该引用在使用SpringBoot 2.3.5.RELEASE版本项目测试的时候,数据验证未生效。

2.2. jakarta.validation-api

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.2</version>
</dependency>

引用路径:

D:\Programmer\maven\repository\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar

jakarta.validation-api是由validation-api改名而来。2018-03-05年,开源组织Eclipse基金会宣布将Java EEEnterprise Edition:企业版)被更名为Jakarta EE(雅加达)。这是OracleJava EE移交给Eclipse基金会后加强对Java品牌控制的措施。随着Java EE的改名,包名和路径名也做了相应修改,从上面的引用路径可以看出来。

注意:单独使用该引用在使用SpringBoot 2.3.5.RELEASE版本项目测试的时候,数据验证未生效。

2.3. hibernate-validator

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

应用路径:

D:\Programmer\maven\repository\org\hibernate\validator\hibernate-validator\6.1.6.Final\hibernate-validator-6.1.6.Final.jar

hibernate-validator与持久层框架Hibernate没有什么关系,hibernate-validatorHibernate基金会下的一个项目,是对Bean Validation的具体实现,它提供了JSR380规范中所有内置constraint的实现,还加入了它自己的一些constraint实现。点开pom文件,会发现hibernate-validator依赖于validation-apijakarta.validation-api)。所以我们使用数据验证的时候可以直接引用这个包即可。

2.4. spring-boot-starter-validation

对于SpringBoot项目,直接引用它提供的starter,这个starter内部依赖了hibernate-validator

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

java 注解校验字段只能是数字 注解校验 springboot_spring_02


官方参考地址:

Java Community Process 官网Jakarta EE 官网

Jakarta Bean Validation 2.0 规范

Hibernate Validator 7.0.0.Final - Jakarta Bean Validation 参考实现

Hibernate Validator GitHub 地址

3. 注解说明

3.1. 标识注解

注解

完整类型

说明

@Valid

javax.validation.Valid

标记用于验证级联的属性、方法参数或方法返回类型。在验证属性、方法参数或方法返回类型时,将验证在对象及其属性上定义的约束。

@Validated

org.springframework.validation.annotation.Validated

Spring 提供的扩展注解,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验,可以很方便的用于分组校验

3.2. 约束注解

Bean Validation规范中包含的22个注解,也就是validation-apijakarta.validation-api)实现的注解:

每个注解都有messagegroupspayload这三个属性,这是Bean Validation规范的要求。下表还列出了每个注解自有的一些属性。

注解

说明

可用类型

@Null

被注释的元素必须是 null。

任何类型

@NotNull

被注释的元素不能是 null。

任何类型

@AssertTrue

被注释的元素必须为 true。null 值是有效的。

boolean,Boolean

@AssertFalse

被注释的元素必须为 false。null 值是有效的。

boolean,Boolean

@Min(value=)

被注释的元素必须是一个数字,其值必须大于或等于指定的最小值。null 值是有效的。

value:指定元素必须大于或等于的 long 值。

BigDecimal,BigInteger,byte,short,int,long,Byte,Short,Integer,Long

@Max(value=)

被注释的元素必须是一个数字,其值必须小于或等于指定的最大值。null 值是有效的。

value:指定元素必须小于或等于的 long 值。

BigDecimal,BigInteger,byte,short,int,long,Byte,Short,Integer,Long

@DecimalMin(value=,inclusive=)

被注释的元素必须是一个数字,其值必须大于或等于指定的最小值。null 值是有效的。

value:用 String 表示 BigDecimal 字符串的最小值。

inclusive:true 表示大于或等于指定的最小值,false 表示大于最小值。默认为 true。

BigDecimal,BigInteger,CharSequence,byte,short,int,long,Byte,Short,Integer,Long

@DecimalMax(value=,inclusive=)

被注释的元素必须是一个数字,其值必须小于或等于指定的最大值。null 值是有效的。

value:用 String 表示 BigDecimal 字符串的最大值。

inclusive:true 表示小于或等于指定的最大值,false 表示小于最大值。默认为 true。

BigDecimal,BigInteger,CharSequence,byte,short,int,long,Byte,Short,Integer,Long

@Negative

被注释的元素必须是一个严格的负数(即 0 被认为是无效的值)。null 值是有效的。

BigDecimal,BigInteger,byte,short,int,long,floatdouble,Byte,Short,Integer,Long,FloatDouble

@NegativeOrZero

被注释的元素必须是负数或 0。null 值是有效的。

BigDecimal,BigInteger,byte,short,int,long,floatdouble,Byte,Short,Integer,Long,FloatDouble

@Positive

被注释的元素必须是一个严格的正数(即 0 被认为是无效的值)。null 值是有效的。

BigDecimal,BigInteger,byte,short,int,long,floatdouble,Byte,Short,Integer,Long,FloatDouble

@PositiveOrZero

被注释的元素必须是正数或 0。null 值是有效的。

BigDecimal,BigInteger,byte,short,int,long,floatdouble,Byte,Short,Integer,Long,FloatDouble

@Size(min=, max=)

被注释的元素大小必须介于最小和最大(闭区间)之间。null 值是有效的。

min:指定元素必须大于或等于的 int 值,默认为 0。

max:指定元素必须小于或等于的 int 值,默认为 int 类型的最大值。

CharSequence:计算字符序列的长度。

Collection:计算集合大小。

Map:计算 map 的大小。

Array:计算数组长度。

@Digits(integer=, fraction=)

带注释的元素必须是可接受范围内的数字。null 值是有效的。

integer:此数字所接受的最大整数的位数。

fraction:该数字所接受的最大小数的位数。

BigDecimal,BigInteger,CharSequence,byte,short,int,long,Byte,Short,Integer,Long

@Past

被注释的元素必须是一个过去的日期时间。null 值是有效的。

java.util.Date

java.util.Calendar

java.time.Instant

java.time.LocalDate

java.time.LocalDateTime

java.time.LocalTime

java.time.MonthDay

java.time.OffsetDateTime

java.time.OffsetTime

java.time.Year

java.time.YearMonth

java.time.ZonedDateTime

java.time.chrono.HijrahDate

java.time.chrono.JapaneseDate

java.time.chrono.MinguoDate

java.time.chrono.ThaiBuddhistDate

@PastOrPresent

被注释的元素必须是一个过去或现在的日期时间。null 值是有效的。

和@Past相同

@Future

被注释的元素必须是一个未来的日期时间。null 值是有效的。

和@Past相同

@FutureOrPresent

被注释的元素必须是一个现在或未来的日期时间。null 值是有效的。

和@Past相同

@Pattern(regexp=, flags={})

被注释的元素必须匹配指定的正则表达式。正则表达式遵循 Java 正则表达式约定,见 Java.util.regex.Pattern。null 值是有效的。

regexp:要匹配的正则表达式。

flags:解析正则表达式时考虑的 Flag 枚举数组。

CharSequence

@NotEmpty

被注释的元素不能是 null 或空。

CharSequence:计算字符序列的长度。

Collection:计算集合大小。

Map:计算 map 的大小。

Array:计算数组长度。

@NotBlank

被注释的元素不能是 null,并且必须包含至少一个非空白字符。

CharSequence

@Email(regexp=, flags={})

检查指定的字符序列是否为有效的电子邮件地址。允许通过可选参数regexpflags指定电子邮件必须匹配的附加正则表达式(包括正则表达式标志)。null 值是有效的。

CharSequence

hibernate-validator额外实现的注解:

注解

说明

可用类型

@Length(min=, max=)

验证字符串是否包含在 min 和 max 之间。null 值是有效的。

min:指定元素的长度必须大于或等于的 int 值,默认为 0。

max:指定元素的长度必须小于或等于的 int 值,默认为 int 类型的最大值。

验证字符串长度

@Range(min=, max=)

被注释的元素必须在适当的范围内。

min:指定元素的数值必须大于或等于的 int 值,默认为 0。

max:指定元素的数值必须小于或等于的 int 值,默认为 long 类型的最大值。

数值或数值字符串的数值大小

注解的可以到org.hibernate.validator.constraints包下面自行查看,这里仅列举几个常用的注解。

校验实现可以到org.hibernate.validator.internal.constraintvalidators包下面自行查看。其中bv包下是对javax.validation下注解的实现,hv包下是对org.hibernate.validator下注解的实现。

3.3. 提示信息

注解的默认提示信息,如果没有写message属性的话则默认显示文件中配置的提示信息,我们用的是简体中文的ValidationMessages_zh_CN.properties文件,文件路径:

hibernate-validator-6.1.6.Final.jar!\org\hibernate\validator\ValidationMessages_zh_CN.properties

默认提示息如下所示:

javax.validation.constraints.AssertFalse.message     = 必须为 false
javax.validation.constraints.AssertTrue.message      = 必须为 true
javax.validation.constraints.DecimalMax.message      = 必须小于 ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message      = 必须大于 ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message          = 数字值超出了边界(期望 <{integer} digits>.<{fraction} digits>)
javax.validation.constraints.Email.message           = 必须为格式规范的电子邮件地址
javax.validation.constraints.Future.message          = 必须是未来的日期
javax.validation.constraints.FutureOrPresent.message = 必须是现在或将来的日期
javax.validation.constraints.Max.message             = 必须小于或等于 {value}
javax.validation.constraints.Min.message             = 必须大于或等于 {value}
javax.validation.constraints.Negative.message        = 必须小于 0
javax.validation.constraints.NegativeOrZero.message  = 必须小于或等于 0
javax.validation.constraints.NotBlank.message        = 不得为空白
javax.validation.constraints.NotEmpty.message        = 不得为空
javax.validation.constraints.NotNull.message         = 不得为 null
javax.validation.constraints.Null.message            = 必须为 null
javax.validation.constraints.Past.message            = 必须是过去的日期
javax.validation.constraints.PastOrPresent.message   = 必须是过去或现在的日期
javax.validation.constraints.Pattern.message         = 必须与 "{regexp}" 匹配
javax.validation.constraints.Positive.message        = 必须大于 0
javax.validation.constraints.PositiveOrZero.message  = 必须大于或等于 0
javax.validation.constraints.Size.message            = 大小必须在 {min} 和 {max} 之间

org.hibernate.validator.constraints.CreditCardNumber.message        = 无效信用卡卡号
org.hibernate.validator.constraints.Currency.message                = 无效货币(必须为 {value} 之一)
org.hibernate.validator.constraints.EAN.message                     = 无效 {type} 条形码
org.hibernate.validator.constraints.Email.message                   = 电子邮件地址格式不规范
org.hibernate.validator.constraints.ISBN.message                    = 无效 ISBN
org.hibernate.validator.constraints.Length.message                  = 长度必须介于 {min} 与 {max} 之间
org.hibernate.validator.constraints.CodePointLength.message         = 长度必须介于 {min} 与 {max} 之间
org.hibernate.validator.constraints.LuhnCheck.message               = ${validatedValue} 的校验位无效,Luhn Modulo 10 校验和失败
org.hibernate.validator.constraints.Mod10Check.message              = ${validatedValue} 的校验位无效,Modulo 10 校验和失败
org.hibernate.validator.constraints.Mod11Check.message              = ${validatedValue} 的校验位无效,Modulo 11 校验和失败
org.hibernate.validator.constraints.ModCheck.message                = ${validatedValue} 的校验位无效,{modType} 校验和失败
org.hibernate.validator.constraints.NotBlank.message                = 可能不为空
org.hibernate.validator.constraints.NotEmpty.message                = 可能不为空
org.hibernate.validator.constraints.ParametersScriptAssert.message  = 脚本表达式 "{script}" 未求值为 true
org.hibernate.validator.constraints.Range.message                   = 必须介于 {min} 与 {max} 之间
org.hibernate.validator.constraints.SafeHtml.message                = 可能具有不安全的 HTML 内容
org.hibernate.validator.constraints.ScriptAssert.message            = 脚本表达式 "{script}" 未求值为 true
org.hibernate.validator.constraints.UniqueElements.message          = 必须仅包含唯一元素
org.hibernate.validator.constraints.URL.message                     = 必须为有效 URL

org.hibernate.validator.constraints.br.CNPJ.message                 = 无效巴西企业纳税人登记号 (CNPJ)
org.hibernate.validator.constraints.br.CPF.message                  = 无效巴西个人纳税人登记号 (CPF)
org.hibernate.validator.constraints.br.TituloEleitoral.message      = 无效巴西投票人身份证号

org.hibernate.validator.constraints.pl.REGON.message                = 无效波兰纳税人识别号 (REGON)
org.hibernate.validator.constraints.pl.NIP.message                  = 无效 VAT 识别号 (NIP)
org.hibernate.validator.constraints.pl.PESEL.message                = 无效波兰身份证号 (PESEL)

org.hibernate.validator.constraints.time.DurationMax.message        = 必须短于 ${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
org.hibernate.validator.constraints.time.DurationMin.message        = 必须长于 ${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}

4. 使用说明

  • pom文件中添加spring-boot-starter-validation的引用;
  • 在接口方法中对需要进行参数校验的对象添加@Validated注解或者@Valid注解;
  • 然后在代码中使用BindingResult或者Errors来获取错误信息。可以只返回第一个错误字段信息,也可以返回所有的错误字段信息。
  • 使用了统一异常处理的话,可以省略BindingResult或者Errors参数。
package com.example.demo.controller;

import com.example.demo.entity.UserInfo;
import com.example.demo.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

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

/**
 * @auther wangbo
 * @date 2021-01-15 14:57
 */
@Slf4j
@RestController
@RequestMapping("/user_info")
public class UserInfoController {

    @PostMapping("/add")
    public Result<Void> add(@RequestBody @Validated UserInfo userInfo, BindingResult bindingResult){
        //判断是否有字段校验错误
        if (bindingResult.hasFieldErrors()){
            //获取与字段相关的所有错误
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            List<String> msgList = new ArrayList<>();
            for (FieldError fieldError : fieldErrors){
                msgList.add(fieldError.getField() + " : " + fieldError.getDefaultMessage());
            }
            return Result.failure(msgList.toString());
        }
        return Result.success();
    }

    @PostMapping("/add1")
    public Result<Void> add1(@RequestBody @Valid UserInfo userInfo, Errors errors){
        //判断是否有字段校验错误
        if (errors.hasFieldErrors()){
            //获取与字段相关的所有错误中的第一个错误,没有则返回null
            FieldError fieldError = errors.getFieldError();
            assert fieldError != null;
            return Result.failure(fieldError.getField() + " : " + fieldError.getDefaultMessage());
        }
        return Result.success();
    }

    /**
     * 走异常处理中心
     */
    @PostMapping("/add2")
    public Result<Void> add2(@RequestBody @Valid UserInfo userInfo){
        return Result.success();
    }

}

4.1. 一般性校验

  • 普通对象校验,直接使用相关约束注解即可;
  • 对于有嵌套对象的字段校验,需要在该字段上面加上@Valid注解;
  • 如果被校验的对象有继承关系,并且父类字段使用了约束注解,那么这些父类的约束条件也会被校验。
  • 最好在约束注解都加上message属性,并且体现出字段信息,这样在异常处理的时候可以只返回message信息,不用返回对应的字段信息了。不写message的话,则使用的默认message,体现不出具体错误字段信息,异常处理的时候需要带上字段信息。
package com.example.demo.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;

/**
 * @auther wangbo
 * @date 2021-01-15 14:55
 */
@Data
public class UserInfo {
    
    @NotBlank
    @Length(min=3, max=10)
    private String username;

    @NotNull
    @Range(min = 15, max = 100)
    private Integer age;

    @NotBlank
    @Email
    private String email;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message = "手机号格式不正确")
    private String phone;
    
    @NotNull
    @AssertTrue
    private Boolean status;

    //嵌套对象校验
    @NotNull
    @Valid
    private Interest interest;

    @NotEmpty
    @Valid
    private List<Blog> blogList;
    
}
package com.example.demo.entity;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
 * @auther wangbo
 * @date 2021-01-16 22:46
 */
@Data
public class Interest {
    @NotBlank
    private String name;
}
package com.example.demo.entity;

import lombok.Data;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 * @auther wangbo
 * @date 2021-01-16 22:50
 */
@Data
public class Blog {
    @NotBlank
    private String title;
    @NotNull
    @Min(value = 1)
    @Max(value = 5)
    private Integer type;
}

4.2. 自定义校验

有时候框架提供的约束注解并不能满足我们的需求,所以需要自定义合适的校验规则来满足自己的校验需求。

自定义注解用到的注解:

  • @Target:指定此注解支持的元素类型,比如:FIELD(属性)、METHOD(方法)等;
  • @Rentention(RUNTIME):指定此类型的注解将在运行时通过反射方式使用;
  • @Constraint(validatedBy = { }):标记注解的类型为约束,指定注解所使用的验证器(写验证逻辑的类),如果约束可以用在多种数据类型中,则每种数据类型对应一个验证器。
  • @Documented:表示带有某种类型的注释将由 javadoc 和类似工具记录;
  • @Repeatable(List.class):指示注解可以在相同的位置重复多次,通常具有不同的配置。List 包含注解类型。

Bean Validation API 规范要求任何约束注解都需要定义以下属性:

  • message:定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制。
  • groups:用于将约束注解进行分组,不同组会执行不同的校验操作。
  • payload:主要是针对Bean的,使用不多,未做详细了解。

下面定义一个注解,用在Field字段上,运行时生效,触发的是DateStrFormatValidator这个验证类。

自定义校验注解:

package com.example.demo.annotation;

import com.example.demo.validator.DateStrFormatValidator;

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

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 字符串日期格式校验注解
 * @auther wangbo
 * @date 2021-01-17 10:10
 */
@Documented
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {DateStrFormatValidator.class})
public @interface DateStrFormat {

    String message() default "日期格式和{format}不匹配";

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

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

    /**
     * 自定义属性
     */
    String format() default "yyyy-MM-dd HH:mm:ss";

}

提供一个校验程序:

package com.example.demo.validator;

import com.example.demo.annotation.DateStrFormat;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

/**
 * 字符串日期格式校验程序
 * @auther wangbo
 * @date 2021-01-17 11:24
 */
public class DateStrFormatValidator implements ConstraintValidator<DateStrFormat, String> {

    private String format;

    /**
     * 初始化验证器
     */
    @Override
    public void initialize(DateStrFormat constraintAnnotation) {
        this.format = constraintAnnotation.format();
    }

    /**
     * 实现验证逻辑
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StringUtils.isEmpty(value)) {
            return true;
        }
        try {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
            dateTimeFormatter.parse(value);
            return true;
        } catch (DateTimeParseException e) {
            return false;
        }
    }

}

Java Bean中字段上像普通约束注解一样进行使用:

@DateStrFormat(format = "yyyy-MM-dd")
private String birthday;

4.3. 分组校验

同一个对象要复用,比如上面的UserInfo在更新时候要校验userId,在保存的时候不需要校验userId,其他属性在两种情况下都要校验,这时候就可以在userId上的约束注解中使用groups属性。

先定义分组接口CreateUpdate

package com.example.demo.group;

import javax.validation.groups.Default;

/**
 * @auther wangbo
 * @date 2021-01-17 21:23
 */
public interface Create extends Default {
}
package com.example.demo.group;

import javax.validation.groups.Default;

/**
 * @auther wangbo
 * @date 2021-01-17 21:23
 */
public interface Update extends Default {
}

注意:在声明分组的时候尽量加上extend Default。因为如果约束注解上没有声明groups属性的话,默认为groups = {Default.class}。所以在声明@Validated(Update.class)的时候,就会出现没添加groups的约束注解不参与校验的问题。

Java Bean的字段上的约束注解上添加groups属性:

@NotNull(groups = {Update.class})
private Long userId;

接下来需要在声明校验的地方使用,如下所示,就实现了在更新接口校验userId,在创建接口不校验userId

import com.example.demo.group.Create;
import com.example.demo.group.Update;

@PostMapping("/create")
public Result<Void> create(@RequestBody @Validated(Create.class) UserInfo userInfo){
	...
}

@PostMapping("/update")
public Result<Void> update(@RequestBody @Validated(Update.class) UserInfo userInfo){
	...
}

JSR规范支持手动校验,不直接支持使用注解校验,不过Spring提供了分组校验注解扩展支持,即:@Validated,参数为group类集合。

4.4. 异常处理

前面都是在代码中使用对象获取错误,现在可以使用异常处理中心来处理这些参数校验错误。针对本文主要处理的是MethodArgumentNotValidExceptionValidationException异常。

  • MethodArgumentNotValidException异常是处理字段校验结果的。这里选择只处理第一个错误字段信息进行返回。当然也可以拿到所有错误字段信息。
  • ValidationException异常是处理在字段校验过程中出现的异常。比如自定义校验的实现代码有bug,会在该异常中进行处理。
package com.example.demo.common.exception;

import com.example.demo.common.result.Result;
import com.example.demo.common.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ValidationException;

/**
 * 全局异常统一处理
 *
 * @author wangbo
 * @date 2021/05/12
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {

    /**
     * 通用异常处理
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> exceptionHandler(Exception e) {
        log.error("通用异常处理", e);
        return Result.failure(ResultCode.PROGRAM_INSIDE_EXCEPTION);
    }

    /**
     * 自定义异常处理
     */
    @ExceptionHandler(CustomException.class)
    public Result<Void> customExceptionHandler(CustomException e) {
        log.info("自定义异常处理");
        return Result.failure(e.getResultCode());
    }

    /**
     * 参数校验结果异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        log.info("参数校验结果异常处理");
        BindingResult bindingResult = e.getBindingResult();
        FieldError fieldError = bindingResult.getFieldError();
        assert fieldError != null;
        return Result.failure(fieldError.getField() + " : " + fieldError.getDefaultMessage());
    }

    /**
     * 参数校验过程异常处理
     */
    @ExceptionHandler(ValidationException.class)
    public Result<Void> validationExceptionHandler(ValidationException e) {
        log.error("参数校验过程异常处理", e);
        return Result.failure(ResultCode.PROGRAM_INSIDE_EXCEPTION);
    }

}

5. 使用示例

/**
 * 登录账号
 */
@NotBlank
@Pattern(regexp = "^[a-zA-Z0-9][a-zA-Z0-9_]{5,15}$", message = "账号长度6-16位,允许数字字母下划线,不能以下划线开头")
private String username;
/**
 * 登录密码
 */
@NotBlank(groups = Create.class)
@Pattern(regexp = "^(?=.*[a-zA-Z0-9].*)(?=.*[a-zA-Z.!@#$%^&*].*)(?=.*[0-9.!@#$%^&*].*).{6,32}$", message = "密码长度6-32位,至少包含数字,字母和特殊符号中的两种")
private String password;