Spring Boot - 参数校验:分组校验、自定义注解、嵌套对象全解析

发布于:2025-07-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

在这里插入图片描述

01 依赖配置

在构建高效的校验体系前,需先完善项目依赖配置。

以下是优化后的依赖示例:

<dependencies>
    <!-- Web 依赖,提供 RESTful 接口支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 校验核心依赖,整合 Hibernate Validator 实现 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

注解:

  1. spring-boot-starter-web 依赖引入了 Spring MVC 组件,为构建 Web 应用提供基础支持
  2. spring-boot-starter-validation 依赖整合了 Hibernate Validator 实现,提供 JSR 380 校验规范支持

02 校验注解体系构建

构建完善的校验注解体系是实现数据校验的基础。

以下是企业级校验注解应用示例:

import javax.validation.constraints.*;
import java.time.LocalDate;

public class UserRegistrationRequest {

    // 用户名校验:非空且长度在 4-20 位之间
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20, message = "用户名长度需在 4-20 位之间")
    private String username;

    // 邮箱校验:非空且符合邮箱格式
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    // 密码校验:非空且长度不少于 8 位
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, message = "密码长度至少为 8 位")
    private String password;

    // 年龄校验:必须大于 0 且小于 150
    @Min(value = 1, message = "年龄必须大于 0")
    @Max(value = 150, message = "年龄不能超过 150")
    private Integer age;

    // 出生日期校验:不能晚于当前日期
    @PastOrPresent(message = "出生日期不能晚于当前日期")
    private LocalDate birthDate;

    // 协议同意校验:必须为 true
    @NotNull(message = "必须同意服务条款")
    private Boolean termsAccepted;
}

注解:

  1. 使用组合注解实现多维度校验,如 @NotBlank + @Size 实现既校验非空又校验长度的双重校验
  2. 通过明确的错误提示信息,提升系统友好性,便于用户定位问题
  3. 日期类型使用 @PastOrPresent 校验,避免接收未来日期的非法输入

03 控制器校验集成

在控制器层面集成校验逻辑,确保所有入参经过严格校验:

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Validated // 启用方法参数校验
@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/register")
    public ResponseEntity registerUser(@Valid @RequestBody UserRegistrationRequest request) {
        // 业务逻辑处理
        return ResponseEntity.ok("用户注册成功");
    }

    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        // 用户查询逻辑
        return ResponseEntity.ok(new User(id, "示例用户"));
    }
}

注解:

  1. @Validated 注解启用控制器方法参数校验,配合 @Valid 对请求体进行深度校验

  2. @RequestBody 结合校验注解,实现复杂对象的自动绑定与校验

  3. 通过明确的 HTTP 状态码和响应消息,提供标准化的接口反馈

04 全局异常处理机制

构建完善的全局异常处理机制,统一管理校验异常:

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        
        return ResponseEntity.badRequest().body(errors);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity handleGeneralException(Exception ex) {
        return ResponseEntity.internalServerError().body("系统内部错误");
    }
}

注解:

  1. 通过 @RestControllerAdvice 实现全局异常处理,集中管理各类异常
  2. 针对校验异常提取字段级错误信息,返回结构化的错误响应
  3. 提供默认异常处理方法,确保所有未被捕获的异常都能得到妥善处理

05 分组校验实现

在复杂业务场景中,通过分组校验实现条件校验逻辑:

// 定义校验分组接口
public interface CreateGroup {}
public interface UpdateGroup {}

// 应用分组校验的 DTO 类
public class UserDTO {

    @NotBlank(groups = CreateGroup.class, message = "创建时用户名必填")
    @Size(groups = CreateGroup.class, min = 4, message = "用户名长度至少为 4")
    private String username;

    @Email(groups = {CreateGroup.class, UpdateGroup.class}, message = "邮箱格式不正确")
    private String email;

    // Getter 和 Setter 方法
}
// 控制器方法应用分组校验
@PostMapping("/users")
public ResponseEntity createUser(@Validated({CreateGroup.class}) UserDTO userDTO) {
    // 创建用户逻辑
    return ResponseEntity.ok("用户创建成功");
}

@PutMapping("/users/{id}")
public ResponseEntity updateUser(@Validated({UpdateGroup.class}) UserDTO userDTO) {
    // 更新用户逻辑
    return ResponseEntity.ok("用户更新成功");
}

注解:

  1. 通过定义接口实现校验分组,灵活控制不同场景下的校验规则

  2. 在 DTO 类中指定各字段所属的校验分组,实现条件校验

  3. 控制器方法通过指定校验分组,精确控制校验逻辑的触发场景

06 嵌套对象校验

处理复杂对象嵌套场景下的校验需求:

// 嵌套对象校验示例
public class Order {

    @NotBlank(message = "订单号不能为空")
    private String orderId;
    
    @DecimalMin(value = "0.01", message = "订单金额必须大于 0.01")
    private BigDecimal amount;
    
    // 嵌套 Customer 对象,启用深度校验
    @Valid
    private Customer customer;
}

public class Customer {
    @NotBlank(message = "客户名称不能为空")
    private String name;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    // 嵌套 Address 对象,继续深度校验
    @Valid
    private Address address;
}

public class Address {
    @NotBlank(message = "地址不能为空")
    private String street;
    
    @NotBlank(message = "城市不能为空")
    private String city;
}
// 控制器方法应用嵌套校验
@PostMapping("/orders")
public ResponseEntity createOrder(@Valid @RequestBody Order order) {
    // 订单创建逻辑
    return ResponseEntity.ok("订单创建成功");
}

注解:

  1. 使用 @Valid 注解启用嵌套对象的深度校验,支持多层对象嵌套
  2. 校验规则沿用对象关系,自动继承父对象的校验上下文
  3. 全局异常处理器自动捕获嵌套校验错误,返回详细的错误路径信息

07 自定义校验注解开发

针对特殊业务场景开发自定义校验注解:

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

// 自定义校验实现
public class PhoneValidator implements ConstraintValidator<ValidPhoneNumber, String> {

    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";

    @Override
    public void initialize(ValidPhoneNumber constraintAnnotation) {
        // 初始化逻辑
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.trim().isEmpty()) {
            return true; // 空值不校验
        }
        return Pattern.matches(PHONE_REGEX, value);
    }
}
// 自定义注解应用示例
public class ContactInfo {
    @ValidPhoneNumber
    private String phone;
    
    // Getter 和 Setter 方法
}

注解:

  1. 通过 @Constraint 注解指定校验器实现类,完成自定义校验注解开发
  2. 校验器实现类需继承 ConstraintValidator,重写 isValid 方法定义校验逻辑
  3. 自定义注解可应用于字段或参数,与内置注解使用方式一致

网站公告

今日签到

点亮在社区的每一天
去签到