9 个 Spring Boot 参数验证高阶技巧(2025–2026 实战版)

Spring Boot 的参数验证(Bean Validation + Hibernate Validator)早已不是简单的 @NotNull@Size,下面这些高阶用法可以让你的代码更简洁、更统一、更易维护,尤其是第 8、9 个技巧,真的能把代码量砍一半甚至更多。

1. 自定义复合注解(最常用高阶技巧)

把多个校验规则打包成一个注解,复用性暴增。

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
@Documented
public @interface PhoneNumber {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
    private static final Pattern PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && PATTERN.matcher(value).matches();
    }
}

使用:

@PostMapping("/register")
public Response register(@RequestBody @Validated RegisterDTO dto) {
    // ...
}
public class RegisterDTO {
    @PhoneNumber
    private String phone;
}

2. @Valid + @Validated 的正确组合使用

  • @Valid:用于对象字段嵌套校验(JSR-303 标准)
  • @Validated:Spring 增强版,支持分组校验、类级别校验
public class OrderCreateDTO {
    @NotNull
    private Long userId;

    @Valid   // 嵌套校验
    private List<OrderItemDTO> items;
}

@PostMapping("/orders")
public void create(@RequestBody @Validated(Group.Create.class) OrderCreateDTO dto) {
    // ...
}

3. 分组校验(Group Validation)——同一个 DTO 不同场景不同规则

public interface Create {}
public interface Update {}

public class UserDTO {
    @Null(groups = Create.class)          // 创建时 id 必须为空
    @NotNull(groups = Update.class)       // 更新时 id 必须有
    private Long id;

    @NotBlank(groups = {Create.class, Update.class})
    private String username;
}
@PostMapping("/users")
public void create(@Validated(Create.class) UserDTO dto) { ... }

@PutMapping("/users/{id}")
public void update(@Validated(Update.class) UserDTO dto) { ... }

4. 类级别校验(Class-level validation)

校验整个对象,而非单个字段。

@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)", message = "结束时间不能早于开始时间")
public class ActivityDTO {
    private LocalDate startDate;
    private LocalDate endDate;
}

或者更推荐自定义 Constraint:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface DateRange {
    String message() default "结束时间不能早于开始时间";
    // ...
}

5. @JsonView + 校验分组联动

结合 @JsonView 和分组校验,实现“不同接口不同字段 + 不同校验规则”。

public interface Views { interface Create {}; interface Update {}; }

public class UserDTO {
    @Null(groups = Views.Create.class)
    @NotNull(groups = Views.Update.class)
    @JsonView({Views.Create.class, Views.Update.class})
    private Long id;
}

6. MethodValidationPostProcessor 开启方法参数/返回值校验

@Configuration
public class ValidationConfig {

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(Validation.buildDefaultValidatorFactory().getValidator());
        return processor;
    }
}

然后在 Controller/Service 直接用:

@Validated
@RestController
public class UserController {

    @PostMapping("/users")
    public User createUser(@Valid @RequestBody UserDTO dto) {
        // ...
    }
}

7. 自定义全局异常处理器 + 统一错误响应

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleConstraintViolation(ConstraintViolationException e) {
        List<String> errors = e.getConstraintViolations().stream()
                .map(v -> v.getMessage())
                .toList();
        return R.error(400, "参数校验失败", errors);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .toList();
        return R.error(400, "参数校验失败", errors);
    }
}

8. 使用 @Validated + Record(代码量直接砍一半!)

Java 14+ Record + 校验 = 极简 DTO

@Validated
public record UserCreateRequest(
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20)
    String username,

    @NotBlank
    @Email
    String email,

    @PhoneNumber
    String phone
) {}

Controller 直接用:

@PostMapping("/users")
public void create(@Valid UserCreateRequest request) {
    // request.username() 访问
}

对比传统:省去 getter/setter、equals/hashCode、toString,代码量直接减半以上。

9. 使用 hibernate-validator 内置的 @ParameterNameProvider + @ConstraintValidatorContext 动态消息

终极简化:字段名自动映射中文提示

@Configuration
public class ValidationConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory factory = Validation.byDefaultProvider()
                .configure()
                .parameterNameProvider(new CustomParameterNameProvider())
                .buildValidatorFactory();
        return factory.getValidator();
    }

    static class CustomParameterNameProvider implements ParameterNameProvider {
        @Override
        public List<String> getParameterNames(Constructor<?> constructor) {
            return Arrays.stream(constructor.getParameters())
                    .map(p -> p.isAnnotationPresent(NotBlank.class) ? "用户名" : p.getName())
                    .toList();
        }
        // ...
    }
}

更推荐:直接用 @Property 或自定义 MessageInterpolator

但最直接省代码的方式还是第 8 点:Record + @Validated

总结:最值得立刻掌握的三个

  • 第 1:自定义复合注解(复用性最高)
  • 第 3:分组校验(同一个 DTO 多种场景)
  • 第 8:Record + @Validated(代码量直接减半,强烈推荐!)

你项目里现在是用传统 DTO 还是已经开始用 Record 了?
或者在校验上遇到过哪些最头疼的场景?可以告诉我,我帮你针对性给出更优解法。

文章已创建 4516

发表回复

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

相关文章

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

返回顶部