Java案例如何实现参数校验?

wen python案例 4

本文目录导读:

Java案例如何实现参数校验?

  1. 手动校验(最基础,无依赖)
  2. Java Bean Validation(JSR 380 / jakarta.validation)
  3. 方法级别的参数校验(非RequestBody)
  4. 自定义校验注解
  5. 参数校验的最佳实践总结

在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 校验代码

抱歉,评论功能暂时关闭!