spring-boot-starter-validation校验启动器简述

spring-boot-starter-validation校验启动器简述

spring-boot-starter-validation 是 Spring Boot 中的一个启动器依赖,用于在项目中引入数据校验的功能。它基于 javax.validation(现为 jakarta.validation)和 Hibernate Validator 实现,为对象属性提供了丰富的校验注解,同时支持自定义校验逻辑。

基本概念

Java API规范即JSR-303JavaEE 6中的一项子规范,又称作 Bean Validation,提供了针对 Java Bean字段的一些校验注解,如@NotNull@Min等。JSR-349是其升级版本,添加了一些新特性。

JSR定义了Bean校验的标准validation-api,但没有提供实现

Hibernate Validator是对这个规范的实现(与ORM框架无关),并在它的基础上增加了一些新的校验注解如@Email@Length等。

Spring全面支持了 JSR-303JSR-349规范,对 Hibernate Validation 进行二次封装,在 SpringMVC 模块中添加了自动校验机制,可以利用注解对 Java Bean 的字段的值进行校验,并将校验信息封装进特定的类中。

依赖作用

spring-boot-starter-validation 的主要作用是提供一套基于注解的校验框架,用于验证数据是否合法。该依赖通常用于 Spring Boot 应用中的以下场景:

  1. 前端数据校验:自动校验传入的请求数据是否符合规定的格式和要求。

  2. 服务端逻辑校验:确保服务内部的数据符合业务逻辑,以防止数据不一致或异常情况。

  3. 数据层保护:通过校验确保入库的数据是符合规范的,有助于保持数据的完整性和一致性。

注解使用方式

1. @NotNull
  • 作用 :确保字段不为 null

  • 适用类型 :所有类型(不能为基础数据类型如 int,因为它们不能为 null)。

  • 示例

    @NotNull(message = "用户名不能为空")

    private String username;

2. @Null
  • 作用 :确保字段为 null

  • 适用类型:所有类型。

  • 示例

    @Null(message = "该字段必须为空")

    private String deprecatedField;

3. @NotEmpty
  • 作用:确保集合、字符串、数组等不为空(不能为空且大小/长度不能为0)。

  • 适用类型:字符串、集合、数组等。

  • 示例

    @NotEmpty(message = "列表不能为空")

    private List items;

4. @NotBlank
  • 作用:确保字符串不为空白(即不能为空,且至少包含一个非空白字符)。

  • 适用类型:字符串。

  • 示例

    @NotBlank(message = "描述不能为空白字符")

    private String description;

5. @Size
  • 作用:限制集合、数组或字符串的大小或长度在指定范围内。

  • 属性

    • min:最小长度(默认为0)。

    • max:最大长度。

  • 适用类型:字符串、集合、数组等。

  • 示例

    @Size(min = 1, max = 10, message = "用户名长度必须在1到10之间")

    private String username;

6. @Min@Max
  • 作用:限制数值类型的字段值的最小值和最大值。

  • 属性

    • value:允许的最小/最大值。
  • 适用类型 :数字类型(如 intlongdouble 等)。

  • 示例

    @Min(value = 18, message = "年龄不能小于18")

    @Max(value = 100, message = "年龄不能超过100")

    private int age;

7. @Positive@PositiveOrZero
  • 作用

    • @Positive:确保字段值为正数。

    • @PositiveOrZero:确保字段值为非负数(即正数或零)。

  • 适用类型:数字类型。

  • 示例

    @Positive(message = "工资必须为正数")

    private BigDecimal salary;

8. @Negative@NegativeOrZero
  • 作用

    • @Negative:确保字段值为负数。

    • @NegativeOrZero:确保字段值为非正数(即负数或零)。

  • 适用类型:数字类型。

  • 示例

    @NegativeOrZero(message = "债务不能为正数")

    private BigDecimal debt;

9. @Past@PastOrPresent
  • 作用

    • @Past:确保日期在当前日期之前。

    • @PastOrPresent:确保日期在当前日期或之前。

  • 适用类型java.util.Datejava.time.LocalDate 等日期类型。

  • 示例

    @Past(message = "生日必须是过去的日期")

    private LocalDate birthDate;

10. @Future@FutureOrPresent
  • 作用

    • @Future:确保日期在当前日期之后。

    • @FutureOrPresent:确保日期在当前日期或之后。

  • 适用类型java.util.Datejava.time.LocalDate 等日期类型。

  • 示例

    @Future(message = "预约日期必须是未来的日期")

    private LocalDate appointmentDate;

11. @Pattern
  • 作用:确保字符串符合指定的正则表达式。

  • 属性

    • regexp:指定的正则表达式。

    • flags:正则表达式的匹配标志(如大小写敏感性)。

  • 适用类型:字符串。

  • 示例

    @Pattern(regexp = "^1^{6,12}$", message = "密码必须是6到12位的字母和数字组合")

    private String password;

12. @Email
  • 作用:确保字符串符合电子邮件格式。

  • 属性

    • regexp:正则表达式,默认符合标准的邮箱格式。

    • flags:正则表达式的匹配标志。

  • 适用类型:字符串。

  • 示例

    @Email(message = "请输入有效的邮箱地址")

    private String email;

13. @Digits
  • 作用:限制数值字段的整数位和小数位的最大位数。

  • 属性

    • integer:最大整数位数。

    • fraction:最大小数位数。

  • 适用类型:数字类型。

  • 示例

    @Digits(integer = 5, fraction = 2, message = "金额整数部分不能超过5位,小数部分不能超过2位")

    private BigDecimal amount;

14. @DecimalMin@DecimalMax
  • 作用:限制字段数值的最小值和最大值(包含边界)。

  • 属性

    • value:允许的最小或最大值。

    • inclusive:是否包含边界值,默认为 true

  • 适用类型:数字类型。

  • 示例

    @DecimalMin(value = "0.0", inclusive = false, message = "金额必须大于0")

    private BigDecimal price;

15. @AssertTrue@AssertFalse
  • 作用

    • @AssertTrue:确保字段值为 true

    • @AssertFalse:确保字段值为 false

  • 适用类型:布尔类型。

  • 示例

    @AssertTrue(message = "必须同意条款")

    private Boolean termsAccepted;

示例汇总

以下是将这些注解应用于一个示例实体类的代码:

import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
?
public class ExampleEntity {
?
 ? ?@NotNull(message = "姓名不能为空")
 ? ?private String name;
?
 ? ?@Min(value = 18, message = "年龄不能小于18")
 ? ?@Max(value = 65, message = "年龄不能超过65")
 ? ?private int age;
?
 ? ?@Email(message = "邮箱格式不正确")
 ? ?private String email;
?
 ? ?@Pattern(regexp = "^[A-Za-z0-9]+$", message = "用户名只能包含字母和数字")
 ? ?private String username;
?
 ? ?@Size(min = 1, max = 10, message = "爱好数量必须在1到10之间")
 ? ?private List<String> hobbies;
?
 ? ?@Positive(message = "工资必须为正数")
 ? ?private BigDecimal salary;
?
 ? ?@Past(message = "入职日期必须是过去的日期")
 ? ?private LocalDate joinDate;
?
 ? ?@FutureOrPresent(message = "合同有效期必须在当前日期或未来")
 ? ?private LocalDate contractExpiryDate;
?
 ? ?// Getters and Setters
}

常见用法

在 Spring Boot 项目中,spring-boot-starter-validation 常见的用法包括为请求参数、实体类等设置校验注解,并在控制器、服务或数据层进行自动校验。以下是几种常见的注解和用法示例。

1. 基本校验注解
  • @NotNull:字段不能为空(适用于对象类型)。

  • @NotEmpty:集合、字符串等字段不能为空,且长度不能为 0。

  • @NotBlank:字符串字段不能为空,并且必须包含非空白字符。

  • @Size(min = x, max = y):字段长度或集合大小在指定范围内。

  • @Min(value) / @Max(value):字段数值不能小于或大于指定值。

  • @Pattern(regexp = "regex"):字段必须符合正则表达式。

2. 在 DTO 中使用校验注解

在控制器层接收请求参数的 DTO 类上使用这些注解,一遍进行对应校验。例如:

import javax.validation.constraints.*;
?
public class UserDto {
 ? ?@NotBlank(message = "用户名不能为空")
 ? ?private String username;
?
 ? ?@Email(message = "邮箱格式不正确")
 ? ?private String email;
?
 ? ?@Size(min = 8, max = 20, message = "密码长度必须在8到20个字符之间")
 ? ?private String password;
?
 ? ?@Min(value = 18, message = "年龄不能小于18")
 ? ?private int age;
?
 ? ?// Getters and Setters
}
3. 在控制器中应用校验

在控制器中使用 @Validated@Valid 注解,使 Spring 自动触发校验规则,如果校验失败会抛出异常。如下:

import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
?
@RestController
@RequestMapping("/api/users")
public class UserController {
 ? ?
 ? ?@PostMapping
 ? ?public ResponseEntity<String> createObject(@Validated @RequestBody ObjectDto obj) {
 ? ? ? ?// 处理业务逻辑
 ? ? ? ?return ResponseEntity.ok("实体创建成功");
 ?  }
?
 ? ?@PostMapping
 ? ?public ResponseEntity<String> createUser(@Valid @RequestBody UserDto user) {
 ? ? ? ?// 处理业务逻辑
 ? ? ? ?return ResponseEntity.ok("用户创建成功");
 ?  }
}

参数较少时,也可直接在参数前使用校验注解。如下:

// 路径变量
@GetMapping("{userId}")
public String detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
 ? ?// 校验通过,才会执行业务逻辑处理
 ? ?UserDTO userDTO = new UserDTO();
 ? ?userDTO.setUserId(userId);
 ? ?userDTO.setAccount("11111111111111111");
 ? ?userDTO.setUserName("xixi");
 ? ?userDTO.setAccount("11111111111111111");
 ? ?return "ok";
}
?
// 查询参数
@GetMapping("detail")
public String getDetail(@Length(min = 6, max = 20) @NotNull String ?account) {
 ? ?// 校验通过,才会执行业务逻辑处理
 ? ?return "ok";
}

自定义校验

可以通过自定义校验注解和校验器来实现更复杂的校验逻辑。例如,创建一个校验密码复杂度的注解:

注解定义:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
?
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
 ? ?//默认错误消息
 ? ?String message() default "密码不符合复杂度要求";
 ? ?//分组
 ? ?Class<?>[] groups() default {};
 ? ?//负载
 ? ?Class<? extends Payload>[] payload() default {};
}
校验器实现:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
?
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
?
 ? ?@Override
 ? ?public void initialize(ValidPassword constraintAnnotation) {
 ?  }
?
 ? ?@Override
 ? ?public boolean isValid(String password, ConstraintValidatorContext context) {
 ? ? ? ?return password != null && password.matches("^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$");
 ?  }
}
使用自定义注解:
public class UserDto {
 ? ?@ValidPassword
 ? ?private String password;
?
 ? ?// Getters and Setters
}

全局异常处理

当校验失败时,Spring 会抛出 MethodArgumentNotValidExceptionConstraintViolationException 异常。可以通过全局异常处理来捕获这些异常并返回自定义的错误响应:

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
?
import javax.validation.ConstraintViolationException;
?
@ControllerAdvice
public class GlobalExceptionHandler {
?
 ? ?@ExceptionHandler(MethodArgumentNotValidException.class)
 ? ?@ResponseStatus(HttpStatus.BAD_REQUEST)
 ? ?@ResponseBody
 ? ?public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
 ? ? ? ?Map<String, String> errors = new HashMap<>();
 ? ? ? ?ex.getBindingResult().getFieldErrors().forEach(error -> 
 ? ? ? ? ? ?errors.put(error.getField(), error.getDefaultMessage()));
 ? ? ? ?return errors;
 ?  }
?
 ? ?@ExceptionHandler(ConstraintViolationException.class)
 ? ?@ResponseStatus(HttpStatus.BAD_REQUEST)
 ? ?@ResponseBody
 ? ?public String handleConstraintViolationException(ConstraintViolationException ex) {
 ? ? ? ?return ex.getMessage();
 ?  }
}

分组校验

可以使用分组校验来对同一类对象的不同场景应用不同的校验规则。分组校验的基本思路是通过定义校验分组,并在具体校验时指定校验组来灵活控制哪些规则生效。

1. 定义校验分组接口

首先,为不同的校验场景定义分组接口,例如在创建、更新、删除等不同操作中使用不同的校验规则。

// 创建时的校验组
public interface CreateGroup {

} 



// 更新时的校验组
public interface UpdateGroup {

}
2. 在字段上应用分组

在需要校验的字段上,使用 @NotNull@Size 等注解的 groups 属性,指定校验组。这使得某些字段在特定的分组下才会触发校验。

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
?
public class UserDto {
?
 ? ?@NotNull(groups = CreateGroup.class, message = "创建时用户名不能为空")
 ? ?private String username;
 ? ?
 ? ?@NotNull(groups = {CreateGroup.class, UpdateGroup.class, TransferGroup.class})
 ? ?@Length(min = 6, max = 20, groups = {CreateGroup.class, Update.class})
 ? ?private String account;
?
 ? ?@Size(min = 8, groups = UpdateGroup.class, message = "更新时密码长度不能少于8位")
 ? ?private String password;
?
 ? ?// getters and setters
 ? ?
 ? ?//类内部的分组接口
 ? ?public interface TransferGroup {
 ? ? ? ?
 ?  }
}
3. 在控制器方法中指定校验组

在控制器方法中,使用 @Validated 注解指定分组以触发分组校验。需要注意的是,@Valid 注解不会支持分组校验,因此需要使用 @Validated

import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
?
@RestController
@RequestMapping("/users")
public class UserController {
?
 ? ?@PostMapping
 ? ?public String createUser(@Validated(CreateGroup.class) @RequestBody UserDto userDto) {
 ? ? ? ?// 只有 CreateGroup 组内的校验生效
 ? ? ? ?return "User created successfully";
 ?  }
?
 ? ?@PostMapping
 ? ?public String updateUser(@Validated(UpdateGroup.class) @RequestBody UserDto userDto) {
 ? ? ? ?// 只有 UpdateGroup 组内的校验生效
 ? ? ? ?return "User updated successfully";
 ?  }
 ? ?
 ? ?@PostMapping
 ? ?public String updateUser(@Validated(UserDto.TransferGroup.class) @RequestBody UserDto userDto) {
 ? ? ? ?// 只有 TransferGroup 组内的校验生效
 ? ? ? ?return "User transfer successfully";
 ?  }
}

在上面的例子中:

  • 创建用户 时,仅 CreateGroup 组内的校验规则会生效(例如 username@NotNull 校验)。

  • 更新用户 时,仅 UpdateGroup 组内的校验规则会生效(例如 password@Size 校验)。

  • 转移用户 时,仅 TransferGroup 组内的校验规则会生效(例如 account@NotNull 校验)。

4. 结合多个分组

在复杂场景中,可以组合多个分组校验规则。例如在创建和更新操作中,都需要校验某些字段的规则,可以通过传入多个分组来实现组合校验。

@PutMapping("/updateBoth")
public String updateBoth(@Validated({CreateGroup.class, UpdateGroup.class}) @RequestBody UserDto userDto) {
 ? ?// 同时触发 CreateGroup 和 UpdateGroup 的校验规则
 ? ?return "User updated with both checks";
}
5. 自定义分组校验逻辑

如果需要自定义复杂的分组校验逻辑,可以在校验注解中指定 groups,然后在自定义校验器中判断校验组。这样可以对不同的校验组应用不同的逻辑。

6.在分组校验情况下校验未分组的参数

有参数如下

@Data
public class PageDto {
?
 ? ?/** 当前页码,默认1 */
 ? ?@NotNull(groups = {PageQueryGroup.class}, message = "页码不能为空")
 ? ?@Min(value = 1, message = "页码不能小于1")
 ? ?private Long currentPage = 1L;
?
 ? ?/** 每页条数,默认10 */
 ? ?@NotNull(groups = {PageQueryGroup.class}, message = "每页条数不能为空")
 ? ?@Min(value = 1, message = "每页条数不能小于1")
 ? ?private Long pageSize = 10L;
}

当控制器进行分组校验时,@Min未生效

@GetMapping("/page")
public Result page(@Validated(PageQueryGroup.class) PageDto pageDto){
 ? ?//后续代码校验通过后执行
}

出现这种情况的原因在于 @Validated(PageQueryGroup.class) 指定了校验组 PageQueryGroup.class,因此只会校验与该组匹配的注解(即 @NotNull(groups = {PageQueryGroup.class})),而 @Min 因未指定分组,因此未被包含在 PageQueryGroup.class 组的校验中。

为了让 @Min 也生效,需要使用自带默认组javax.validation.groups.Default

javax.validation.groups.Default这个默认分组本质就是在无任何分组的校验注解下(如@NotNull@Min)也是无任何分组下@Validated默认校验的分组,源码如下:

package javax.validation.groups;
?
public interface Default {
 ? ?
}

要让@Min在无分组下及PageQueryGroup下都生效,可选方法如下

方法1:增加@Min的生效分组
@NotNull(groups = {PageQueryGroup.class}, message = "页码不能为空")
@Min(value = 1, groups = {Default.class, PageQueryGroup.class}, message = "页码不能小于1")
private Long currentPage = 1L;

这样,该注解既能在不指定分组的校验下生效,本次分组下也会生效

方法2:控制器中增加指定默认分组
@GetMapping("/page")
public Result page(@Validated(PageQueryGroup.class, Default.class) PageDto pageDto){
 ? ?//后续代码校验通过后执行
}

这样,控制器下自动校验既会校验PageQueryGroup分组,也会校验默认分组

两种方法使用其中一种,或两种同时使用,都能达成效果

嵌套校验

嵌套校验用于处理对象内部的属性也是一个对象的情况,通常应用于复杂对象结构的校验。要实现嵌套校验,需要在嵌套对象的属性上使用 @Valid 注解,确保内部对象的字段也会被递归校验。以下是嵌套校验的具体步骤和注意事项:

1. 定义嵌套结构的对象

例如,我们有一个 Order 对象,包含一个 Customer 对象,并且需要校验 OrderCustomer 的字段。

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
?
public class Customer {
 ? ?@NotNull(message = "Customer name cannot be null")
 ? ?private String name;
?
 ? ?@Size(min = 10, max = 15, message = "Phone number must be between 10 and 15 characters")
 ? ?private String phoneNumber;
?
 ? ?// Getters and Setters
}

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
?
public class Order {
 ? ?@NotNull(message = "Order ID cannot be null")
 ? ?private String orderId;
?
 ? ?@Valid // 确保嵌套校验生效
 ? ?@NotNull(message = "Customer details are required")
 ? ?private Customer customer;
?
 ? ?@NotEmpty(message = "Order items cannot be empty")
 ? ?private List<String> items;
?
 ? ?// Getters and Setters
}

Order 类中,我们将 Customer 对象标注为 @Valid,表示在校验 Order 时,也会对其 customer 属性中的字段进行递归校验。

2. 使用嵌套校验

在控制器方法中使用 @Valid@ValidatedOrder 对象进行校验:

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
?
@RestController
@RequestMapping("/orders")
public class OrderController {
?
 ? ?@PostMapping
 ? ?public String createOrder(@Valid @RequestBody Order order) {
 ? ? ? ?// 如果校验失败,将会抛出异常
 ? ? ? ?return "Order created successfully";
 ?  }
}

在此例中,@Valid 将对 Order 对象执行校验,同时对嵌套的 Customer 对象中的字段进行递归校验。如果 Customer 中的 namephoneNumber 不符合约束条件,将会返回相应的错误信息。

3. 处理校验失败的响应

Spring 默认会返回一个 400 Bad Request 错误响应,并包含详细的校验错误信息。若需要自定义校验错误响应,可以使用 @ControllerAdvice@ExceptionHandler 进行处理:

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
?
import java.util.HashMap;
import java.util.Map;
?
@ControllerAdvice
public class ValidationExceptionHandler {
?
 ? ?@ResponseStatus(HttpStatus.BAD_REQUEST)
 ? ?@ExceptionHandler(MethodArgumentNotValidException.class)
 ? ?public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
 ? ? ? ?Map<String, String> errors = new HashMap<>();
 ? ? ? ?ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
 ? ? ? ?return errors;
 ?  }
}

此异常处理程序会捕获校验异常 MethodArgumentNotValidException,并将错误信息以字段名和错误信息的形式返回给客户端。

4. 嵌套分组校验

如果有不同的校验场景(例如创建和更新),还可以结合分组校验。在嵌套对象中指定校验组时,同样需要在外层对象的字段上标记 @Validated(Group.class) 来激活分组校验。

5. 嵌套校验的限制
  • 嵌套校验通常只适用于一层嵌套,如果存在更深层嵌套结构,@Valid 仍会递归校验,但需要确保各层对象的校验约束清晰、合理,避免深度过大的对象结构。

  • 若嵌套校验对象中存在集合(如 ListSet 等),@Valid 会自动对集合中的每个元素进行递归校验。

集合校验

如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:

包装List类型,并声明@Valid注解

public class ValidationList<E> implements List<E> {
?
 ? ?@Delegate // @Delegate是lombok注解
 ? ?@Valid // 一定要加@Valid注解
 ? ?public List<E> list = new ArrayList<>();
 ? ?
 ? ?// 一定要记得重写toString方法
 ? ?@Override
 ? ?public String toString() {
 ? ? ? ?return list.toString();
 ?  }
?
}

@Delegate注解受lombok版本限制,1.18.6以上版本可支持。如果校验不通过,会抛出NotReadablePropertyException,同样可以使用统一异常进行处理。

比如,我们需要一次性保存多个User对象,Controller层的方法可以这么写:

@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList<UserDTO> userList) {
 ? ?// 校验通过,才会执行业务逻辑处理
 ? ?return Result.ok();
}

手动校验(编程式校验)

上述都是基于注解来实现自动校验的,在某些情况下,如在业务实现的过程中,我们可能需要手动调用校验逻辑。这个时候可以注入javax.validation.Validator对象,然后再调用其api。

@Autowired
private javax.validation.Validator globalValidator;
?
// 手动校验
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
    //这里的validate方法第一个参数是需要校验的对象,后续是校验分组,分组可以为0个(此时为默认分组),也可以为多个
 ? ?Set<ConstraintViolation<UserDTO>> validate = globalValidator.validate(userDTO, Save.class);
 ? ?// 如果校验通过,validate为空;否则,validate包含未校验通过项
 ? ?if (!validate.isEmpty()) {
        // 校验失败
        // 抛出异常方法一
        for (ConstraintViolation<UserDTO> violation: validate) {
 ? ? ? ? ? ?System.out.println(violation.getMessage());
            throw new RuntimException(violation.getMessage());
 ? ? ?  }
        // 抛出异常方法二
        throw new RuntimException(validate.iterator().next().getMessage());
 ?  }

    // 校验通过才会执行到这里

 ? ?return Result.ok();
}

快速失败(Fail Fast)

Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启Fali Fast模式,一旦校验失败就立即返回

1.在 application.properties 中配置:
spring.validation.fail-fast=true

yaml文件参照配置即可。无需编写额外的代码即可启用快速失败,简单快捷,适合一般场景。

2.自定义 Validator 配置------通过 LocalValidatorFactoryBean 实现 Validator 的 Bean 配置:
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
?
@Configuration
public class ValidationConfig {
?
 ? ?@Bean
 ? ?public LocalValidatorFactoryBean validator() {
 ? ? ? ?LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
 ? ? ? ?validator.getValidationPropertyMap().put("hibernate.validator.fail_fast", "true");
 ? ? ? ?return validator;
 ?  }
}

上述方法使用 Spring 提供的 LocalValidatorFactoryBean 类,并通过 getValidationPropertyMap() 方法设置属性 hibernate.validator.fail_fasttrue,以启用快速失败模式。这种方式更加贴近 Spring 的配置方式,通常用于需要与 Spring 的其他功能(如国际化等)集成的场景。这是因为能够更好地与 Spring 的依赖注入和国际化功能配合。例如,它会自动使用 messages.properties 等国际化资源文件,并能与 Spring 中的 @Autowired 配合。

3.自定义 Validator 配置------直接调用 Validation.byProvider(HibernateValidator.class).configure().failFast(true) 来创建 Validator
@Bean
public Validator validator() {
 ? ?ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
 ? ? ? ? ?  .configure()
 ? ? ? ? ? ?// 快速失败模式
 ? ? ? ? ?  .failFast(true)
 ? ? ? ? ?  .buildValidatorFactory();
 ? ?return validatorFactory.getValidator();

这是标准 JSR-303 的配置方式,适合不依赖于 Spring 上下文的场景,也可以直接用于非 Spring 框架的项目中。这种方式更灵活,可通过标准配置自定义 ConstraintValidatorFactory 等属性,但在 Spring 上下文中可能不支持某些 Spring 特有的功能,如国际化。

使用场景和适用性

方式

配置方式

特点

使用场景

spring.validation.fail-fast=true

配置文件

简单快捷,适合一般需求

轻量级应用、常规项目中的快速配置

LocalValidatorFactoryBean

自定义 Validator Bean

深度整合 Spring 上下文,支持国际化

需要国际化、多模块项目或复杂配置场景

ValidatorFactory

标准 ValidatorFactory

灵活,适合非 Spring 场景,不支持 Spring 国际化

标准 Validator 或非 Spring 场景

优缺点总结

方式

优点

缺点

spring.validation.fail-fast=true

简单易用,零代码改动

仅能启用快速失败,配置不灵活

LocalValidatorFactoryBean

深度整合 Spring,支持国际化和资源绑定

需要编写自定义配置类

ValidatorFactory

配置灵活,适用标准 JSR 303 项目

配置较繁琐,不支持 Spring 特性

实现原理

见参考文献中第二篇 wh柒八九 所著 Spring Validation校验_springvalidation-CSDN博客

总结

spring-boot-starter-validation 提供了一个便捷的校验框架,通过注解的方式即可对请求数据进行强制约束。常见的用法包括在 DTO 中设置注解、在控制器中启用校验,以及通过全局异常处理提供更友好的反馈。

参考文献

validation检查框架_validation框架-CSDN博客

Spring Validation校验_springvalidation-CSDN博客


  1. a-zA-Z0-9 ↩︎
相关推荐
O(1)的boot22 分钟前
微服务的问题
java·数据库·微服务
一个略懂代码的程序员25 分钟前
Redis01
java·redis
IT界的奇葩29 分钟前
基于springboot使用Caffeine
java·spring boot·后端·caffeine
西埃斯迪恩-霖40 分钟前
Idea导入SpringBoot3.2.x源码
java·ide·intellij-idea
m0_748251721 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
潘多编程1 小时前
Spring Boot性能提升:实战案例分析
java·spring boot·后端
m0_748256141 小时前
Spring Boot 整合 Keycloak
java·spring boot·后端
#HakunaMatata1 小时前
Java 中 List 接口的学习笔记
java·学习·list
Ase5gqe1 小时前
Spring Boot中实现JPA多数据源配置指南
java
AI人H哥会Java2 小时前
【JAVA】Java高级:多数据源管理与Sharding:在Spring Boot应用中实现多数据源的管理
java·开发语言