Skip to content

验证


基础用法

添加依赖

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

⚠️ spring-boot-starter-web 不包含 validation starter,需要手动添加。很多新手在这里卡住——配了 @Valid 注解但完全不生效。

定义验证规则

java
public class CreateUserRequest {

    @NotBlank(message = "用户名不能为空")             // 类比 'required|string'
    @Size(min = 2, max = 20, message = "用户名长度2-20")
    private String name;

    @NotBlank
    @Email(message = "邮箱格式不正确")
    private String email;

    @NotNull
    @Min(18) @Max(120)
    private Integer age;

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

    // getter / setter
}

在控制器中使用

java
@RestController
public class UserController {

    @PostMapping("/users")
    public User create(@Valid @RequestBody CreateUserRequest request) {
        // 如果验证失败,不会执行到这里
        // 会在参数解析阶段抛出 MethodArgumentNotValidException
        return userService.create(request);
    }
}

验证注解速查

注解作用类比
@NotBlank字符串不为 null 且去掉空格后不为空required|string
@NotEmpty集合/字符串不为空required|array
@NotNull字段不为 nullrequired
@Size(min, max)字符串长度/集合大小范围between:2,20
@Min / @Max数字最小值/最大值min:18|max:120
@Email邮箱格式email
@Pattern(regexp)正则匹配regex:/pattern/
@Positive / @PositiveOrZero正数 / 非负数无直接对应
@Future / @Past未来日期 / 过去日期after:tomorrow|before:today

分组验证(不同场景不同规则)

java
public class UserRequest {

    public interface Create {}     // 创建时的验证组
    public interface Update {}     // 更新时的验证组

    @NotBlank(groups = Create.class)
    @Null(groups = Update.class)   // 更新时不允许传 password
    private String password;

    @NotBlank(groups = Create.class)
    private String username;

    @NotBlank(groups = Update.class)
    private Long id;
}

// 使用
@PostMapping("/users")
public User create(@Validated(Create.class) @RequestBody UserRequest req) { ... }

@PutMapping("/users/{id}")
public User update(@Validated(Update.class) @RequestBody UserRequest req) { ... }

PHP 中实现分组验证通常需要写多个 Request 类或 if 判断。Java 的 groups 机制是编译期声明式的。


自定义验证注解

java
// 1. 定义注解
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 2. 实现验证逻辑
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 (value == null) return true;  // null 由 @NotNull 处理
        return PATTERN.matcher(value).matches();
    }
}

// 3. 使用
public class UserRequest {
    @Phone
    private String phone;
}

PHP 中自定义验证规则:

php
// Laravel
Validator::extend('phone', function ($attr, $value) {
    return preg_match('/^1[3-9]\d{9}$/', $value);
});

Java 需要写两个文件(注解定义 + 验证器实现),比 PHP 繁琐,但好处是类型安全可复用


异常处理

java
@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Map<String, String>> handleValidation(
            MethodArgumentNotValidException e) {

        Map<String, String> errors = new LinkedHashMap<>();
        e.getBindingResult().getFieldErrors().forEach(err ->
            errors.put(err.getField(), err.getDefaultMessage())
        );

        return ApiResponse.error(400, "Validation failed")
                .setData(errors);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ApiResponse<Map<String, String>> handleParamValidation(
            ConstraintViolationException e) {

        Map<String, String> errors = new LinkedHashMap<>();
        e.getConstraintViolations().forEach(v ->
            errors.put(v.getPropertyPath().toString(), v.getMessage())
        );

        return ApiResponse.error(400, "Validation failed")
                .setData(errors);
    }
}

⚠️ 常见坑

1. @Valid vs @Validated

  • @Valid:JSR 标准注解,能触发嵌套验证
  • @Validated:Spring 的变体,支持分组验证

大部分场景用 @Valid 就够了。需要分组验证时用 @Validated

2. 路径参数和查询参数的验证

java
@RestController
@Validated  // 类上需要加这个注解
public class UserController {

    @GetMapping("/users/{id}")
    public User show(@PathVariable @Min(1) Long id) { ... }
    // 类上无 @Validated 时,@Min 不会生效
}

3. 嵌套对象的验证需要 @Valid

java
public class OrderRequest {
    @Valid                    // 不加这个,address 内的验证不生效
    private Address address;
}

public class Address {
    @NotBlank
    private String city;
}

4. message 中支持 EL 表达式

java
@Size(min = 2, max = 20, message = "用户名长度必须在{min}到{max}之间")
private String name;
// 输出:用户名长度必须在2到20之间

面向 PHP 开发者的 Spring Boot 文档