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](#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 ↩︎
相关推荐
张张张3125 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~8 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
xyliiiiiL23 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing25 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode
27669582922 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿