验证
基础用法
添加依赖
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 | 字段不为 null | required |
@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之间