千家信息网

Java SpringBoot Validation怎么用

发表于:2025-11-08 作者:千家信息网编辑
千家信息网最后更新 2025年11月08日,这篇文章主要为大家展示了"Java SpringBoot Validation怎么用",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Java SpringB
千家信息网最后更新 2025年11月08日Java SpringBoot Validation怎么用

这篇文章主要为大家展示了"Java SpringBoot Validation怎么用",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Java SpringBoot Validation怎么用"这篇文章吧。

    提到输入参数的基本验证(非空、长度、大小、格式…),在以前我们还是通过手写代码,各种if、else、StringUtils.isEmpty、CollectionUtils.isEmpty…,真感觉快要疯了,太繁琐,Low爆了…,其实在Java生态提供了一套标准JSR-380(aka. Bean Validation 2.0,part of Jakarta EE and JavaSE),它已成为对象验证事实上的标准,这套标准可以通过注解的形式(如@NotNull, @Size…)来对bean的属性进行验证。而Hibernate Validator对这套标准进行了实现,SpringBoot Validation无缝集成了Hibernate Validator、自定义验证器、自动验证的功能。下文将对SpringBoot集成Validation进行展开。

    constraints分类

    JSR-380的支持的constrants注解汇总如下表:

    分类注解适用对象null是否验证通过说明
    非空@NotNull所有对象No不是null
    非空@NotEmptyCharSequence, Collection, Map, ArrayNo不是null、不是""、size>0
    非空@NotBlankCharSequenceNo不是null、trim后长度大于0
    非空@Null所有对象Yes是null
    长度@Size(min=0, max=Integer.MAX_VALUE)CharSequence, Collection, Map, ArrayYes字符串长度、集合size
    大小@PositiveBigDecimal, BigInteger, byte, short, int, long, float, doubleYes数字>0
    大小@PositiveOrZeroBigDecimal, BigInteger, byte, short, int, long, float, doubleYes数字>=0
    大小@NegativeBigDecimal, BigInteger, byte, short, int, long, float, doubleYes数字<0
    大小@NegativeOrZeroBigDecimal, BigInteger, byte, short, int, long, float, doubleYes数字<=0
    大小@Min(value=0L)BigDecimal, BigInteger, byte, short, int, longYes数字>=min.value
    大小@Max(value=0L)BigDecimal, BigInteger, byte, short, int, longYes数字<=max.value
    大小@Range(min=0L, max=Long.MAX_VALUE)BigDecimal, BigInteger, byte, short, int, longYesrange.min<=数字<=range.max
    大小@DecimalMin(value="")BigDecimal, BigInteger, CharSequence, byte, short, int, longYes数字>=decimalMin.value
    大小@DecimalMax(value="")BigDecimal, BigInteger, CharSequence, byte, short, int, longYes数字<=decimalMax.value
    日期@Past
    • 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

    Yes时间在当前时间之前
    日期@PastOrPresent同上Yes时间在当前时间之前 或者等于此时
    日期@Future同上Yes时间在当前时间之后
    日期@FutureOrPresent同上Yes时间在当前时间之后 或者等于此时
    格式@Pattern(regexp="", flags={})CharSequenceYes匹配正则表达式
    格式@Email
    @Email(regexp=".*", flags={})
    CharSequenceYes匹配邮箱格式
    格式@Digts(integer=0, fraction=0)BigDecimal, BigInteger, CharSequence, byte, short, int, longYes必须是数字类型,且满足整数位数<=digits.integer, 浮点位数<=digits.fraction
    布尔@AssertTruebooleanYes必须是true
    布尔@AssertFalsebooleanYes必须是false

    注: 后续还需补充Hibernate Validator中实现的constraints注解,如表中@Range。

    对象集成constraints示例

    /** * 用户 - DTO * * @author luohq * @date 2021-09-04 13:45 */public class UserDto {    @NotNull(groups = Update.class)    @Positive    private Long id;    @NotBlank    @Size(max = 32)    private String name;    @NotNull    @Range(min = 1, max = 2)    private Integer sex;    @NotBlank    @Pattern(regexp = "^\\d{8,11}$")    private String phone;    @NotNull    @Email    private String mail;    @NotNull    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$")    private String birthDateStr;    @NotNull    @PastOrPresent    private LocalDate birthLocalDate;    @NotNull    @Past    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime registerLocalDatetime;    @Valid    @NotEmpty    private List orgs;        //省略getter、setter、toString方法    }/** * 组织 - DTO * * @author luohq * @date 2021-09-04 14:10 */public class OrgDto {    @NotNull    @Positive    private Long orgId;    @NotBlank    @Size(min = 1, max = 32)    private String orgName;        //省略getter、setter、toString方法    }

    注:

    • 可通过constraints注解的groups指定分组
      即指定constraints仅在指定group生效,默认均为Default分组,
      后续可通过@Validated({MyGroupInterface.class})形式进行分组的指定

    • 可通过@Valid注解进行级联验证(Cascaded Validation,即嵌套对象验证)
      如上示例中@Valid添加在 List orgs上,即会对list中的每个OrgDto进行验证

    SpringBoot集成自动验证

    参考:
    https://www.baeldung.com/javax-validation-method-constraints#validation

    集成maven依赖

        org.springframework.boot    spring-boot-starter-validation

    验证RequestBody、Form对象参数

    在参数前加@Validated

    验证简单参数

    在controller类上加@Validated

    验证指定分组

    全局controller验证异常处理

    通过@ControllerAdvice、@ExceptionHandler来对SpringBoot Validation验证框架抛出的异常进行统一处理,
    并将错误信息拼接后统一返回,具体处理代码如下:

    import com.luo.demo.validation.domain.result.CommonResult;import com.luo.demo.validation.enums.RespCodeEnum;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.HttpStatus;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.ResponseStatus;import javax.servlet.http.HttpServletRequest;import javax.validation.ConstraintViolationException;import java.util.List;import java.util.Optional;import java.util.stream.Collectors;import java.util.stream.Stream;/** * controller增强 - 通用异常处理 * * @author luohq * @date 2021-09-04 13:43 */@ControllerAdvicepublic class ControllerAdviceHandler {    private static final Logger log = LoggerFactory.getLogger(ControllerAdviceHandler.class);    /**     * 是否在响应结果中展示验证错误提示信息     */    @Value("${spring.validation.msg.enable:true}")    private Boolean enableValidationMsg;    /**     * 符号常量     */    private final String DOT = ".";    private final String SEPARATOR_COMMA = ", ";    private final String SEPARATOR_COLON = ": ";    /**     * 验证异常处理 - 在@RequestBody上添加@Validated处触发     *     * @param request     * @param ex     * @return     */    @ExceptionHandler({MethodArgumentNotValidException.class})    @ResponseStatus(HttpStatus.OK)    @ResponseBody    public CommonResult handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {        log.warn("{} - MethodArgumentNotValidException!", request.getServletPath());        CommonResult commonResult = CommonResult.respWith(RespCodeEnum.PARAM_INVALID.getCode(), this.convertFiledErrors(ex.getBindingResult().getFieldErrors()));        log.warn("{} - resp with param invalid: {}", request.getServletPath(), commonResult);        return commonResult;    }    /**     * 验证异常处理 - form参数(对象参数,没有加@RequestBody)触发     *     * @param request     * @param ex     * @return     */    @ExceptionHandler({BindException.class})    @ResponseStatus(HttpStatus.OK)    @ResponseBody    public CommonResult handleBindException(HttpServletRequest request, BindException ex) {        log.warn("{} - BindException!", request.getServletPath());        CommonResult commonResult = CommonResult.respWith(RespCodeEnum.PARAM_INVALID.getCode(), this.convertFiledErrors(ex.getFieldErrors()));        log.warn("{} - resp with param invalid: {}", request.getServletPath(), commonResult);        return commonResult;    }    /**     * 验证异常处理 - @Validated加在controller类上,     * 且在参数列表中直接指定constraints时触发     *     * @param request     * @param ex     * @return     */    @ExceptionHandler({ConstraintViolationException.class})    @ResponseStatus(HttpStatus.OK)    @ResponseBody    public CommonResult handleConstraintViolationException(HttpServletRequest request, ConstraintViolationException ex) {        log.warn("{} - ConstraintViolationException - {}", request.getServletPath(), ex.getMessage());        CommonResult commonResult = CommonResult.respWith(RespCodeEnum.PARAM_INVALID.getCode(), this.convertConstraintViolations(ex));        log.warn("{} - resp with param invalid: {}", request.getServletPath(), commonResult);        return commonResult;    }    /**     * 全局默认异常处理     *     * @param request     * @param ex     * @return     */    @ExceptionHandler({Throwable.class})    @ResponseStatus(HttpStatus.OK)    @ResponseBody    public CommonResult handleException(HttpServletRequest request, Throwable ex) {        log.warn("{} - Exception!", request.getServletPath(), ex);        CommonResult commonResult = CommonResult.failed();        log.warn("{} - resp failed: {}", request.getServletPath(), commonResult);        return commonResult;    }    /**     * 转换FieldError列表为错误提示信息     *     * @param fieldErrors     * @return     */    private String convertFiledErrors(List fieldErrors) {        return Optional.ofNullable(fieldErrors)                .filter(fieldErrorsInner -> this.enableValidationMsg)                .map(fieldErrorsInner -> fieldErrorsInner.stream()                        .flatMap(fieldError -> Stream.of(fieldError.getField(), SEPARATOR_COLON, fieldError.getDefaultMessage(), SEPARATOR_COMMA))                        .collect(Collectors.joining()))                .map(msg -> msg.substring(0, msg.length() - SEPARATOR_COMMA.length()))                .orElse(null);    }    /**     * 转换ConstraintViolationException异常为错误提示信息     *     * @param constraintViolationException     * @return     */    private String convertConstraintViolations(ConstraintViolationException constraintViolationException) {        return Optional.ofNullable(constraintViolationException.getConstraintViolations())                .filter(constraintViolations -> this.enableValidationMsg)                .map(constraintViolations -> constraintViolations.stream()                        .flatMap(constraintViolation -> {                            String path = constraintViolation.getPropertyPath().toString();                            path = path.substring(path.lastIndexOf(DOT) + 1);                            String errMsg = constraintViolation.getMessage();                            return Stream.of(path, SEPARATOR_COLON, errMsg, SEPARATOR_COMMA);                        }).collect(Collectors.joining())                ).map(msg -> msg.substring(0, msg.length() - SEPARATOR_COMMA.length()))                .orElse(null);    }}

    参数验证未通过返回结果示例:

    注: 其中CommonResult为统一返回结果,可根据自己业务进行调整

    自定义constraints

    自定义field constraint注解主要分为以下几步:
    (1)定义constraint annotation注解及其属性
    (2)通过注解的元注解@Constraint(validatedBy = {})关联的具体的验证器实现
    (3)实现验证器逻辑

    @DateFormat

    具体字符串日期格式constraint @DateFormat定义示例如下:

    import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.*;import static java.lang.annotation.ElementType.ANNOTATION_TYPE;/** * The annotated {@code CharSequence} must match date format. * The default date format is "yyyy-MM-dd". * Can override with property "format". * see {@link java.time.format.DateTimeFormatter}. * 

    * Accepts {@code CharSequence}. {@code null} elements are considered valid. * * @author luo * @date 2021-09-05 */@Documented@Constraint(validatedBy = DateFormatValidator.class)@Target({ElementType.METHOD, ElementType.FIELD, ANNOTATION_TYPE,})@Retention(RetentionPolicy.RUNTIME)public @interface DateFormat { String message() default "日期格式不正确"; String format() default "yyyy-MM-dd"; Class[] groups() default {}; Class[] payload() default {};}import org.springframework.util.StringUtils;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.time.format.DateTimeFormatter;/** * Date Format validator * * @author luohq * @date 2021-09-05 */public class DateFormatValidator implements ConstraintValidator { private String format; @Override public void initialize(DateFormat dateFormat) { this.format = dateFormat.format(); } @Override public boolean isValid(String dateStr, ConstraintValidatorContext cxt) { if (!StringUtils.hasText(dateStr)) { return true; } try { DateTimeFormatter.ofPattern(this.format).parse(dateStr); return true; } catch (Throwable ex) { return false; } }}

    @PhoneNo

    在查看hbernate-validator中URL、Email约束实现时,发现可以通过元注解的形式去复用constraint实现(如@Pattern),故参考如上方式实现@PhoneNo约束

    import javax.validation.Constraint;import javax.validation.OverridesAttribute;import javax.validation.Payload;import javax.validation.ReportAsSingleViolation;import javax.validation.constraints.Pattern;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;/** * The annotated {@code CharSequence} must match phone no format. * The regular expression follows the Java regular expression conventions * see {@link java.util.regex.Pattern}. * 

    * Accepts {@code CharSequence}. {@code null} elements are considered valid. * * @author luo * @date 2021-09-05 */@Documented@Constraint(validatedBy = {})@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})@Retention(RUNTIME)@Repeatable(PhoneNo.List.class)@ReportAsSingleViolation@Pattern(regexp = "")public @interface PhoneNo { String message() default "电话号码格式不正确"; Class[] groups() default {}; Class[] payload() default {}; /** * @return an additional regular expression the annotated PhoneNo must match. The default is "^\\d{8,11}$" */ @OverridesAttribute(constraint = Pattern.class, name = "regexp") String regexp() default "^\\d{8,11}$"; /** * @return used in combination with {@link #regexp()} in order to specify a regular expression option */ @OverridesAttribute(constraint = Pattern.class, name = "flags") Pattern.Flag[] flags() default {}; /** * Defines several {@code @URL} annotations on the same element. */ @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @interface List { PhoneNo[] value(); }}

    注: 同理可以实现@IdNo约束

    使用自定义constraint注解

    可将之前的对象集成示例中代码调整为使用自定义验证注解如下:

    /** * 用户 - DTO * * @author luohq * @date 2021-09-04 13:45 */public class UserDto {    ...    @NotBlank    //@Pattern(regexp = "^\\d{8,11}$")    @PhoneNo    private String phone;        @NotBlank    @IdNo    private String idNo;    @NotNull    //@Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$")    @DateFormat    //@DateTimeFormat    private String birthDateStr;    ...}

    同时自定义constraints还支持跨多参数、验证对象里的多个field、验证返回对象等用法,待后续再详细探索。

    问题

    通过在对象属性、方法参数上标注注解的形式,需要侵入代码,之前有的架构师不喜欢这种风格。
    在一方开发时,我们有全部源码且在公司内部,这种方式还是可以的,且集成比较方便,
    但是依赖三方Api jar包(参数对象定义在jar包中),我们无法直接去修改参数对象,依旧使用这种侵入代码的注解方式就不适用了,
    针对三方包、或者替代注解这种形式,之前公司内部有实现过基于xml配置的形式进行验证,
    这种方式不侵入参数对象,且集成也还算方便,
    但是用起来还是没有直接在代码里写注解来的顺手(代码有补全、有提示、程序员友好),
    所以一方开发时,首选推荐SpringBoot Validation这套体系,无法直接编辑参数对象时再考虑其他方式。

    参考:

    【自定义validator - field、class level】https://www.baeldung.com/spring-mvc-custom-validator

    【Spring boot集成validation、全局异常处理】https://www.baeldung.com/spring-boot-bean-validation

    【JSR380、非Spring框架集成validation】https://www.baeldung.com/javax-validation

    【方法约束 - Single param、Cross param、Return value自定义constraints、编程调用验证】https://www.baeldung.com/javax-validation-method-constraints

    Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    https://reflectoring.io/bean-validation-with-spring-boot/

    以上是"Java SpringBoot Validation怎么用"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

    验证 注解 对象 参数 大小 数字 处理 时间 格式 代码 形式 日期 方式 示例 信息 方法 标准 错误 长度 分组 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 长兴软件开发项目管理 外国数据库的中国进军之路 防城港纠仿网络技术有 怎样解决网络安全上的问题英语 共建网络安全心得体会 软件开发部门的主要职责 服务器内存超频多少 饥荒服务器质量怎么样 网络安全文明上网文字素材 软件开发工具包干神马用的 任天堂游戏如何查询服务器 我的世界服务器背包 视频接入软件开发 记录网络安全行为为主题的宣传画 使用最先进的软件开发 服务器是怎么管理员密码 网络安全 gpu dhcp服务器的安装配置实验 网络技术有限公司核心价值观 网络安全管控班长职责 计算机网络技术一级证好考吗 游戏服务器cpu哪个好 dm数据库实例迁移教程 小米盒子网络安全性 南阳网络安全竞赛 软件开发的部门有哪些问题 牙科三维建模软件开发 如何把自己的网络安全进行到底 为什么网络安全是重大的战略问题 国外发表论文数据库
    0