本文目录导读:

- 手动校验(最基础,无依赖)
- Java Bean Validation(JSR 380 / jakarta.validation)
- 方法级别的参数校验(非RequestBody)
- 自定义校验注解
- 参数校验的最佳实践总结
在Java中实现参数校验有多种方式,从简单的手动校验到使用框架注解,从基础类型到复杂对象,每种方式都有其适用场景。
以下是主流的几种实现方案,由浅入深:
手动校验(最基础,无依赖)
适合简单场景或不需要引入额外框架的情况。
示例代码:
public class UserService {
public void register(String username, String password, Integer age) {
// 基本非空判断
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
// 长度校验
if (username.length() < 3 || username.length() > 20) {
throw new IllegalArgumentException("用户名长度必须在3-20之间");
}
if (password == null || password.length() < 6) {
throw new IllegalArgumentException("密码长度不能少于6位");
}
if (age == null || age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
// ... 业务逻辑
}
}
优点: 简单直接,无任何依赖。
缺点: 代码冗余,重复性强,可维护性差。
Java Bean Validation(JSR 380 / jakarta.validation)
这是目前最主流、最推荐的方式,通常配合 Hibernate Validator(实现)。
1 添加依赖(Maven示例)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<!-- Spring Boot会自动引入Hibernate Validator -->
</dependency>
2 在实体/参数对象上使用注解
import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotNull;
public class UserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
private String password;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
// getters and setters...
}
3 在Controller中启用校验
@Valid + 自动抛出异常(推荐)
@RestController
@Validated // 可选,用于类级别的方法参数校验
public class UserController {
@PostMapping("/register")
public Result register(@Valid @RequestBody UserRequest request) {
// 如果校验失败,Spring会自动抛出MethodArgumentNotValidException
// 返回400 Bad Request,并包含具体错误信息
return Result.success(userService.register(request));
}
}
手动捕获校验结果
@PostMapping("/register")
public Result register(@Valid @RequestBody UserRequest request, BindingResult result) {
if (result.hasErrors()) {
// 收集所有错误信息
List<String> errors = result.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return Result.error(400, "参数校验失败: " + String.join(", ", errors));
}
return Result.success(userService.register(request));
}
4 统一异常处理(优雅返回)
通常配合 @RestControllerAdvice 统一处理校验异常,避免在每个Controller里重复处理。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationExceptions(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, "参数校验失败: " + errorMessage);
}
@ExceptionHandler(ConstraintViolationException.class)
public Result handleConstraintViolation(ConstraintViolationException ex) {
String errorMessage = ex.getConstraintViolations().stream()
.map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
.collect(Collectors.joining(", "));
return Result.error(400, "参数校验失败: " + errorMessage);
}
}
方法级别的参数校验(非RequestBody)
适用于 @RequestParam、@PathVariable 等非对象参数。
@RestController
@Validated // 类上必须加 @Validated
public class UserController {
@GetMapping("/users/{id}")
public Result getUser(@PathVariable @Min(1) Long id) {
// ...
}
@GetMapping("/search")
public Result search(
@RequestParam @NotBlank String keyword,
@RequestParam @Min(1) @Max(100) int pageSize) {
// ...
}
}
注意: 这里如果校验失败,抛出的异常是 ConstraintViolationException,需要在全局异常处理器中捕获(见上述代码)。
自定义校验注解
当内置注解(如 @NotNull, @Size)不满足复杂业务需求时,可以自定义。
1 创建自定义注解
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PhoneValidator.class) // 指定校验器
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPhone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2 实现校验器
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isEmpty()) {
return true; // 非空校验由 @NotNull 负责
}
return value.matches(PHONE_PATTERN);
}
}
3 使用自定义注解
public class UserRequest {
@ValidPhone // 使用自定义注解
private String phone;
}
参数校验的最佳实践总结
| 场景 | 推荐方案 |
|---|---|
| 简单对象(如接收JSON的DTO) | @Valid + 注解 |
| 单个参数(如URL参数、路径变量) | @Validated(类级) + 注解 |
| 复杂业务逻辑校验 | 自定义注解 + 校验器 |
| 跨字段校验(如密码和确认密码一致) | 自定义类级别注解(@Target(TYPE))或手动校验 |
| 性能要求极高、避免反射 | 手动校验(牺牲开发效率) |
| 需要国际化错误信息 | 在 ValidationMessages.properties 中配置 |
最终建议:
- 首选 Jakarta Validation(
@Valid+ 注解) - 配合 全局异常处理 统一返回格式
- 对复杂业务逻辑使用 自定义注解 或 Service层手动校验
- 避免在Controller中写大量
if-else校验代码
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播