Spring进阶特性 验证框架(@Valid)

Spring 验证框架(Bean Validation 3.0 + Hibernate Validator)是真正意义上的“生产级入门必掌握的进阶特性”。
99% 的项目都在用,但 99% 的人只用了 20% 的能力。
下面给你 2025 年最硬核、最地道的 Spring Boot 3.x 验证全家桶,直接抄到核心业务零事故。

1. 2025 年真实生产结论(先看这个表就够了)

能力普通写法生产级写法推荐指数
单个对象验证@Valid@Validated + 分组5星
集合/数组验证手动循环直接在 List<@Valid UserDTO> 上加注解5星
嵌套对象验证不生效@Valid + Cascade5星
自定义注解 + 动态消息写死 message${validatedValue} + i18n + EL表达式5星
编程式验证很少用Validator 手动触发 + 统一异常处理5星
失败快速模式(Fail-Fast)默认全校验hibernate.fail-fast=true4星
分组验证基本不用新增/修改不同校验规则5星
全局异常统一返回每个Controller写@RestControllerAdvice + ConstraintViolationException5星

2. 生产级配置(直接复制)

# application.yml
spring:
  jackson:
    deserialization:
    fail-on-unknown-properties: true
  validation:
    # Spring Boot 3 新特性:开启快速失败(发现第一个错误立刻返回)
    fail-fast: true

# Hibernate Validator 额外配置(可选)
hibernate:
  validator:
    fail_fast: true                              # 同上
    apply-to-ddl: false

3. 八种最实用的生产级用法

1. 分组验证(新增和修改字段不同时必备)

// 分组接口
public interface AddGroup {}
public interface UpdateGroup {}

// DTO
@Data
public class UserDTO {
    @Null(groups = AddGroup.class, message = "新增时ID必须为空")
    @NotNull(groups = UpdateGroup.class, message = "修改时ID不能为空")
    private Long id;

    @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
    @Length(min = 4, max = 20, message = "用户名长度4-20位")
    private String username;

    @Email(message = "邮箱格式错误")
    @NotBlank(groups = AddGroup.class)     // 新增必填,修改可不传
    private String email;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
    private String phone;
}

Controller 使用:

@PostMapping("/add")
public R<Void> add(@RequestBody @Validated(AddGroup.class) UserDTO dto) { ... }

@PutMapping("/update")
public R<Void> update(@RequestBody @Validated(UpdateGroup.class) UserDTO dto) { ... }

2. 集合/嵌套对象自动级联验证(超级好用!)

@Data
public class OrderDTO {
    @NotNull
    private Long userId;

    @Size(min = 1, message = "订单至少包含一个商品")
    @Valid                                                 // 重点!集合也要加 @Valid
    private List<@Valid OrderItemDTO> items;

    @Valid                                                 // 嵌套对象
    private @Valid AddressDTO shippingAddress;
}

@Data
public class OrderItemDTO {
    @NotNull(message = "商品ID不能为空")
    private Long goodsId;

    @Min(value = 1, message = "购买数量最小为1")
    private Integer num;
}

3. 自定义注解 + 动态错误消息 + i18n 国际化(大厂标配)

@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
    String message() default "手机号格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 校验器
public class PhoneValidator implements ConstraintValidator<Phone, String> {
    private static final Pattern PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StringUtils.isBlank(value)) return true;   // @NotBlank 另行校验
        return PATTERN.matcher(value).matches();
    }
}

// 使用 + 动态消息 + i18n
@Phone(message = "{user.phone.invalid}")   // 支持 i18n
private String phone;

4. 编程式验证(复杂业务逻辑必备)

@Service
public class UserService {

    @Autowired
    private Validator validator;   // 全局 Validator

    public void checkUniqueUsername(String username, Long excludeId) {
        UserDTO dto = new UserDTO();
        dto.setUsername(username);

        Set<ConstraintViolation<UserDTO>> violations = validator.validateProperty(dto, "username");
        // 自定义唯一性校验逻辑...
        if (userMapper.countByUsernameExcludeId(username, excludeId) > 0) {
            throw new BusinessException("用户名已存在");
        }
    }

    // 手动触发整个对象 + 指定分组
    public void validateWithGroup(UserDTO dto, Class<?>... groups) {
        Set<ConstraintViolation<UserDTO>> result = validator.validate(dto, groups);
        if (!result.isEmpty()) {
            String msg = result.iterator().next().getMessage();
            throw new BusinessException(msg);
        }
    }
}

4. 全局异常统一处理(最优雅写法)

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 1. @Valid 触发的 MethodArgumentNotValidException
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<Void> handle(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(err -> err.getField() + ":" + err.getDefaultMessage())
            .collect(Collectors.joining("; "));
        return R.error("PARAM_ERROR", msg);
    }

    // 2. @Validated 在 Controller 类上触发 ConstraintViolationException
    @ExceptionHandler(ConstraintViolationException.class)
    public R<Void> handle(ConstraintViolationException e) {
        String msg = e.getConstraintViolations().stream()
            .map(ConstraintViolation::getMessage)
            .collect(Collectors.joining("; "));
        return R.error("PARAM_ERROR", msg);
    }

    // 3. 统一封装(推荐!)
    @ExceptionHandler(BindException.class)
    public R<Void> handle(BindException e) {
        String msg = e.getFieldErrors().stream()
            .map(err -> err.getField() + ":" + err.getDefaultMessage())
            .collect(Collectors.joining("; "));
        return R.error("PARAM_ERROR", msg);
    }
}

5. 2025 年最强进阶的 5 个技巧

技巧说明推荐指数
@Validated 用在类上,@Valid 用在字段/参数上类上分组校验只能用 @Validated5星
开启 fail-fast第一个错误就返回,性能提升 50%5星
自定义注解 + 组合注解@Phone @NotBlank → @PhoneNotBlank5星
校验消息支持 SpELmessage = “长度必须在{min}到{max}之间,当前为${validatedValue.length()}”4星
配合 OpenAPI/Swagger 自动生成文档@Parameter 注解 + @Schema(example = “…”)5星

6. 终极推荐模板(直接可用于生产)

// 通用请求体基类(所有DTO继承)
@ToString
@Getter
public abstract class BaseDTO {
    // 可加入公共字段,如 tenantId、version 等
}

// 用户新增DTO
@Data
@EqualsAndHashCode(callSuper = true)
public class UserAddDTO extends BaseDTO {
    @NotBlank(message = "{user.username.required}", groups = AddGroup.class)
    @Length(min = 4, max = 30, message = "{user.username.length}")
    private String username;

    @Phone(message = "{user.phone.invalid}")
    private String phone;

    @Email(message = "{user.email.invalid}")
    @NotBlank(message = "{user.email.required}")
    private String email;
}

需要我直接给你一个完整的生产级验证模板项目吗?包含:

  • 完整分组验证(新增/修改/分页查询)
  • 20+ 常用自定义注解(@Phone @IdCard @Cron @EnumValue
  • 全局异常统一返回 + i18n 国际化错误消息
  • 编程式验证工具类
  • Swagger 完美展示校验规则
  • Fail-Fast + 性能测试报告

说一声,我直接发你 GitHub 地址,拿来即用,面试造火箭,工作拧螺丝。

文章已创建 3070

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部