广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java SpringBoot Validation用法案例详解
  • 449
分享到

Java SpringBoot Validation用法案例详解

2024-04-02 19:04:59 449人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录constraints分类对象集成constraints示例SpringBoot集成自动验证集成Maven依赖验证RequestBody、FORM对象参数验证简单参数验证指定分组

提到输入参数的基本验证(非空、长度、大小、格式…),在以前我们还是通过手写代码,各种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进行展开。

注: 完整示例代码可参见GitHubhttps://github.com/marqueeluo/spring-boot-validation-demo

constraints分类

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

分类 注解 适用对象 null是否验证通过 说明
非空 @NotNull 所有对象 No 不是null
非空 @NotEmpty CharSequence, Collection, Map, Array No 不是null、不是""、size>0
非空 @NotBlank CharSequence No 不是null、trim后长度大于0
非空 @Null 所有对象 Yes 是null
长度 @Size(min=0, max=Integer.MAX_VALUE) CharSequence, Collection, Map, Array Yes 字符串长度、集合size
大小 @Positive BigDecimal, BigInteger, byte, short, int, long, float, double Yes 数字>0
大小 @PositiveOrZero BigDecimal, BigInteger, byte, short, int, long, float, double Yes 数字>=0
大小 @Negative BigDecimal, BigInteger, byte, short, int, long, float, double Yes 数字<0
大小 @NegativeOrZero BigDecimal, BigInteger, byte, short, int, long, float, double Yes 数字<=0
大小 @Min(value=0L) BigDecimal, BigInteger, byte, short, int, long Yes 数字>=min.value
大小 @Max(value=0L) BigDecimal, BigInteger, byte, short, int, long Yes 数字<=max.value
大小 @Range(min=0L, max=Long.MAX_VALUE) BigDecimal, BigInteger, byte, short, int, long Yes range.min<=数字<=range.max
大小 @DecimalMin(value="") BigDecimal, BigInteger, CharSequence, byte, short, int, long Yes 数字>=decimalMin.value
大小 @DecimalMax(value="") BigDecimal, BigInteger, CharSequence, byte, short, int, long Yes 数字<=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={}) CharSequence Yes 匹配正则表达式
格式 @Email
@Email(regexp=".*", flags={})
CharSequence Yes 匹配邮箱格式
格式 @Digts(integer=0, fraction=0) BigDecimal, BigInteger, CharSequence, byte, short, int, long Yes 必须是数字类型,且满足整数位数<=digits.integer, 浮点位数<=digits.fraction
布尔 @AssertTrue boolean Yes 必须是true
布尔 @AssertFalse boolean Yes 必须是false

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

对象集成constraints示例



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<OrgDto> orgs;

	//省略getter、setter、toString方法	
}


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<OrgDto> orgs上,即会对list中的每个OrgDto进行验证

SpringBoot集成自动验证

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

集成maven依赖


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

验证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;


@ControllerAdvice
public 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 = ": ";

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

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


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

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

    
    private String convertFiledErrors(List<FieldError> 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);
    }

    
    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;


@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<? extends Payload>[] payload() default {};
}



import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.time.format.DateTimeFormatter;


public class DateFormatValidator implements ConstraintValidator<DateFormat, String> {

    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;



@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<? extends Payload>[] payload() default {};

    
    @OverridesAttribute(constraint = Pattern.class, name = "regexp") String regexp() default "^\\d{8,11}$";

    
    @OverridesAttribute(constraint = Pattern.class, name = "flags") Pattern.Flag[] flags() default {};

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

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

使用自定义constraint注解

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



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用法案例详解的文章就介绍到这了,更多相关Java SpringBoot Validation用法内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java SpringBoot Validation用法案例详解

本文链接: https://www.lsjlt.com/news/134787.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java SpringBoot Validation用法案例详解
    目录constraints分类对象集成constraints示例SpringBoot集成自动验证集成maven依赖验证RequestBody、Form对象参数验证简单参数验证指定分组...
    99+
    2022-11-12
  • Java Springboot websocket使用案例详解
    什么是WebSocket WebSocket是一种在单个TCP连接上进行全双工通信的协议 … 为什么要实现握手监控管理 如果说,连接随意创建,不管的话,会存在错误,broken pi...
    99+
    2022-11-12
  • Java Scanner用法案例详解
    一、Scanner类简介       Java 5添加了java.util.Scanner类,这是一个用于扫描输入文本的新的实用程序。它是以前...
    99+
    2022-11-12
  • Java SPI用法案例详解
     1.什么是SPI      SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接...
    99+
    2022-11-12
  • Java ConcurrentHashMap用法案例详解
    一、概念 哈希算法(hash algorithm):是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值。 哈希表(hash table):根据设定的哈希函数H(k...
    99+
    2022-11-12
  • Java ArrayAdapter用法案例详解
          拖延症最可怕的地方就是:就算自己这边没有拖延,但对方也会拖延,进而导致自己这边也开始拖延起来!现在这个项目我这边已经是完...
    99+
    2022-11-12
  • Java DatabaseMetaData用法案例详解
    目录一 . 得到这个对象的实例二. 方法getTables的用法三. 方法getColumns的用法四、方法getPrimaryKeys的用法五、方法.getTypeInfo()的用...
    99+
    2022-11-12
  • SpringBoot 中使用 Validation 校验参数的方法详解
    目录1. Validation 介绍1.1 Validation 注解1.2 @valid 和 @validated的区别2. SpringBoot 中使用 Validator 校验...
    99+
    2022-11-13
  • Java之Class.forName()用法案例详解
    Class.forName()主要功能 Class.forName(xxx.xx.xx)返回的是一个类, Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载...
    99+
    2022-11-12
  • Java getParameter方法案例详解
    html核心代码 <body> <font size = "5" color ="blue">圆面积计算</font><br&...
    99+
    2022-11-12
  • Java DFA算法案例详解
    1.背景 项目中需要对敏感词做一个过滤,首先有几个方案可以选择: 直接将敏感词组织成String后,利用indexOf方法来查询。 传统的敏感词入库后SQL查询。 ...
    99+
    2022-11-12
  • Java Collections.shuffle()方法案例详解
    Java.util.Collections类下有一个静态的shuffle()方法,如下: 1)static void shuffle(List<?> list) ...
    99+
    2022-11-12
  • springBoot整合redis使用案例详解
    一、创建springboot项目(采用骨架方式) 创建完成; 我们分析下pom文件中内容: 所使用到的关键依赖: <!--springBoot集成redis-...
    99+
    2022-11-12
  • SpringBoot项目使用aop案例详解
    目录前言一、SpringBoot项目引入AOP依赖二、普通方式三、注解方式前言 IOC和AOP是Spring中的两个核心的概念,简单介绍一下我的理解: IOC:控制反转,就是将以前由...
    99+
    2023-05-14
    SpringBoot使用aop SpringBoot项目使用aop
  • springBoot详细讲解使用mybaties案例
    首先创建springBoot项目,jdk选择1.8 然后倒入mybaties的相关依赖 我们用的springBoot,当然spring全家桶里面含有mybaties,所以我们直接使用...
    99+
    2022-11-13
  • CRITICAL_SECTION用法案例详解
          很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTI...
    99+
    2022-11-12
  • Java WeakHashMap案例详解
    WeakHashMap 继承于AbstractMap,实现了Map接口。 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映...
    99+
    2022-11-12
  • Java Assert.assertEquals案例详解
    junit.framework包下的Assert提供了多个断言方法. 主用于比较测试传递进去的两个参数. Assert.assertEquals();及其重载方法: 1. 如果两者一...
    99+
    2022-11-12
  • Java ResourceBundle案例详解
    JAVA中ResourceBundle使用详解 这个类主要用来解决国际化和本地化问题。国际化和本地化可不是两个概念,两者都是一起出现的。可以说,国际化的目的就是为...
    99+
    2022-11-12
  • Java JNDI案例详解
    JNDI的理解 JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一个,不少专家觉得...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作