新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅

转载 作者:来者不拒 更新时间:2024-01-31 09:25:36 24 4

介绍

在开发现代应用程序时,数据验证是确保用户输入的正确性和应用程序数据完整性的关键方面。Spring Boot 提供了强大的数据验证机制,使开发者能够轻松地执行验证操作。本文将深入介绍 Spring Boot 中的 Validation,以及如何在应用程序中正确使用它.

为什么使用数据验证?

  • 1.用户输入的正确性:数据验证是确保用户输入的正确性的一种重要手段。通过验证用户输入的数据,可以防止无效或错误的数据进入应用程序,提高数据的质量。例如:系统中的备注字段数据库中对应的长度是256,如果用户输入的备注超过这个长度值,那么就会导致mysql报Data too long.

    1. 数据完整性: 数据完整性是指数据在存储和传输过程中的准确性和一致性。数据验证有助于确保数据满足特定的格式、长度、范围等要求,从而提高数据的完整性。
  1. 安全性: 数据验证也是保障应用程序安全性的关键因素。通过验证用户输入,可以防范一些潜在的安全威胁,例如 SQL 注入、跨站脚本攻击等.

  2. 业务规则的执行: 在应用程序中,通常存在一些业务规则,例如某个字段不能为空、日期范围必须在某个特定范围内等。通过数据验证,可以确保这些业务规则在应用程序中得到正确执行.

手动数据校验的痛点

日常开发中,有些写项目可能没有采用Spring Validator,采用的是在代码中手动校验数据。但是手动校验数据会带来代码冗余、错误处理的一致性以及业务规则的维护的一些痛点.

  • 代码冗余的手动校验逻辑,导致代码中大量的if-else
public ResponseEntity<String> registerUser(UserRegistrationRequest request) {
    if (request == null) {
        return ResponseEntity.badRequest().body("Request cannot be null");
    }

    if (StringUtils.isBlank(request.getUsername())) {
        return ResponseEntity.badRequest().body("Username cannot be blank");
    }

    if (StringUtils.length(request.getPassword()) < 6) {
        return ResponseEntity.badRequest().body("Password must be at least 6 characters long");
    }

    // 处理用户注册逻辑
    return ResponseEntity.ok("User registered successfully");
}

  • 缺乏统一的错误处理机制 。

  • 业务规则维护的困难 随着业务规则的增加,手动编写的校验逻辑可能变得庞大且难以维护。修改和扩展校验规则可能需要修改多个地方,增加了维护成本.

  • 缺乏验证组的支持 手动校验通常不支持验证组的概念,难以根据不同场景执行不同的验证规则.

  • 不易于集成前端验证 手动校验不易与前端验证框架集成,导致前后端验证逻辑可能不一致.

通过引入 Spring Validator,我们能够有效解决这些痛点,提高代码的可读性、可维护性,并确保校验逻辑的一致性.

Spring Boot 中的 Validation 概述

因Springboot的spring-boot-starter-web默认内置了Hibernate-Validator(Spring boot 2.3以前版本),虽然Hibernate-Validator也能做到数据校验,但是考虑到spring-boot-starter-validation 是一个抽象层,使得验证框架的具体实现变得可插拔。这意味着,除了 Hibernate Validator,开发者可以选择其他符合 Bean Validation 规范的实现。所以我们可以手动引入spring-boot-starter-validation实现数据验证.

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

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

spring-boot-starter-validation 不仅支持 JSR-303(Bean Validation 1.0)规范,还提供了对 JSR-380(Bean Validation 2.0)规范的全面支持。这使得开发者可以利用 Bean Validation 2.0 的新特性,更灵活地定义验证规则,包括对集合、嵌套对象的验证等.

通过在实体类的字段上使用标准的 Bean Validation 注解(如 @NotBlank、@Size、@Email 等),我们能够直观地定义数据的验证规则。这些验证规则会在应用程序的不同层次(如控制器层)生效,确保输入数据的正确性.

基本用法

Spring Boot Validation 提供了一系列注解,用于在实体类中定义验证规则。以下是一些常用的校验相关的注解及其功能以及用法: 1.@NotNull: 校验元素值不能为 null。如果元素为null,则验证失败。通常用于字段级别的验证.

@NotNull(message = "Name cannot be null")
private String name;

2.@NotBlank: 校验字符串元素值不能为 null 或空字符串。必须包含至少一个非空格字符(即执行trim()之后不为'')。如果元素为null或者‘‘,则验证失败。通常用于String类型的字段校验.

@NotBlank(message = "Username cannot be blank")
private String username;

3.NotEmpty: 校验集合元素或数组元素或者字符串是否非空。通常作用于集合字段或数组字段,此时需要集合或者数字的元素个数大于0。也可以作用于字符串,此时校验字符串不能为null或空串(可以是一个空格)。注意与@NotBlank的使用区别.

@NotEmpty(message = "List cannot be empty")
private List<String> items;

4.@Length: 校验字符串元素的长度。作用于字符串。注:Hibernate-Validator中注解,等同于spring-boot-starter-validation中的@Size.

@Length(min = 5, max = 20, message = "Length must be between 5 and 20 characters")
private String username;

5.@Size: 校验集合元素个数或字符串的长度在指定范围内。在集合或字符串字段上添加 @Size 注解.

@Size(min = 1, max = 10, message = "Number of items must be between 1 and 10")
private List<String> items;

@Size(min = 5, max = 20, message = "Length must be between 5 and 20 characters")
private String username;

6.@Min: 校验数字元素的最小值.

@Min(value = 18, message = "Age must be at least 18")
private int age;

7.@Max: 校验数字元素的最大值.

@Max(value = 100, message = "Age must not exceed 100")
private int age;

9.@DecimalMax: 作用于BigDecimal类型字段, 校验字段的最大值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最大值,inclusive 属性表示是否包含最大值.

@DecimalMax(value = "100.00", inclusive = true, message = "Value must be less than or equal to 100.00")
private BigDecimal amount;

10.@DecimalMin: 作用于BigDecimal类型字段, 校验字段的最小值,支持比较的值为字符串表示的十进制数。通常搭配它的inclusive()使用,区别边界问题。value 属性表示最小值,inclusive 属性表示是否包含最小值.

@DecimalMin(value = "0.00", inclusive = false, message = "Value must be greater than 0.00")
private BigDecimal amount;

11.@Email: 校验字符串元素是否为有效的电子邮件地址。可以通过regexp自定义邮箱匹配正则.

@Email(message = "Invalid email address")
private String email;

12.@Pattern: 根据正则表达式校验字符串元素的格式.

@Pattern(regexp = "[a-zA-Z0-9]+", message = "Only alphanumeric characters are allowed")
private String username;

13.@Digits: 校验数字元素的整数部分和小数部分的位数。作用于BigDecimal,BigInteger,字符串,以及byte, short,int, long以及它们的包装类型.

@Digits(integer = 5, fraction = 2, message = "Number must have up to 5 integer digits and 2 fraction digits")
private BigDecimal amount;

14.@Past: 校验日期或时间元素是否在当前时间之前。即是否是过去时间。作用于Date相关类型的字段.

@Past(message = "Date must be in the past")
private LocalDate startDate;

15.@Future: 校验日期或时间元素是否在当前时间之后。即是否是未来时间。作用于Date相关类型的字段.

@Future(message = "Date must be in the future")
private LocalDate endDate;

注:以上只罗列部分注解以及它们的功能,其余他们的字段属性并没有详细说明,其他注解以及详细的说明需要去看源码.

用法示例

1.定义接口入参请求参数
/**  
* @version 1.0  
* @description: <p></p >  
* @author: 码农Academy  
* @create: 2024/1/8 16:46  
*/  
@Data  
public class UserCreateRequestVO {  
  
	@NotBlank(message = "请输入用户名")  
	@Size(max = 128, message = "用户名长度最大为128个字符")  
	private String userName;  
	  
	@Email(message = "请填写正确的邮箱地址")  
	private String email;  
	  
	@Min(value = 18, message = "用户年龄必须大于18岁")  
	@Max(value = 60, message = "用户年龄必须小于60岁")  
	private Integer age;  
	  
	@NotEmpty(message = "请输入你的兴趣爱好")  
	@Size(max = 5, message = "兴趣爱好最多可以输入5个")  
	private List<String> hobbies;  
	  
	@DecimalMin(value = "50", inclusive = false, message = "体重必须大于50KG")  
	private BigDecimal weight;  

	@Validated
	@NotNull(message = "请输入地址信息")  
	private UserAddressRequestVO address;  
}
2.定义请求接口
@RestController  
@RequestMapping("user")  
@Validated  
@Slf4j  
public class UserController {  
  
	/**  
	* 创建用户  
	* @param requestVO  
	* @return  
	*/  
	@PostMapping("create")  
	public ResultResponse<Void> createUser(@Validated @RequestBody UserCreateRequestVO requestVO){  
	return ResultResponse.success(null);  
	}  

	/**  
	* 校验用户邮箱是否合法  
	* @param email  
	* @return  
	*/  
	@GetMapping("email")  
	public ResultResponse<Void> validUserEmail(@Email(message = "邮箱格式不正确") String email){  
	return ResultResponse.success(null);  
	}
}
3.测试
  • 创建用户校验,Json请求体校验
    image.png

我们需要捕获一下MethodArgumentNotValidException。该部分内容请参考文章:SpringBoot统一异常处理 。

  • 校验邮箱,单参数校验
    image.png

注:单参数校验时我们需要,在方法的类上加上@Validated注解,否则校验不生效.

嵌套对象的校验

在UserCreateRequestVO中增加一个address的校验,即需要对嵌套对象进行校验 。

/**  
* @version 1.0  
* @description: <p></p >  
* @author: 码农Academy  
* @create: 2024/1/8 19:45  
*/  
@Data  
public class UserAddressRequestVO {  
	@Size(max = 16, message = "地址信息中国家长度不能超过16个字符")  
	@NotBlank(message = "地址信息国家不能为空")  
	private String country;  
	  
	private String city;  
	  
	@Size(max = 128, message = "详细地址长度不能超过128个字符")  
	private String address1;  
}

在UserAddressRequestVO中增加address属性 。

@Data  
public class UserCreateRequestVO {
	@NotNull(message = "请输入地址信息")  
	private UserAddressRequestVO address;
}

解决办法,要在嵌套对象上使用 @Valid 注解 。

@Data  
public class UserCreateRequestVO {

	@NotNull(message = "请输入地址信息")  
	@Valid  
	private UserAddressRequestVO address;
}