java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Boot 参数校验全局

Spring Boot 参数校验全局处理(JSR380 + 自定义校验)

作者:码客日记

本文详细讲解了SpringBoot中前端传参的优雅校验方法,结合JSR385默认注解、全局异常捕获、自定义校验注解等实现参数校验逻辑的复用与解耦,下面就来详细的介绍一下

在Spring Boot接口开发中,前端传参的合法性校验是必不可少的环节。此前我们已经实现了接口开发与全局异常处理的基础框架,而在实际开发中,若直接在接口方法中通过大量if-else判断参数是否合法,不仅会导致代码冗余、可读性差,还会增加后期维护成本。本文将专门讲解如何借助JSR380规范注解 + 全局异常捕获,配合自定义校验注解,实现前端传参的优雅校验,彻底摒弃繁琐的if-else判断,让参数校验逻辑更简洁、更规范、更可复用。

JSR380是Java EE的参数校验规范,定义了一系列用于参数校验的注解,Spring Boot已默认集成相关实现(hibernate-validator),无需额外引入过多依赖,即可直接使用注解完成基础的参数校验;对于一些特殊场景(如手机号格式、身份证号校验等),JSR380默认注解无法满足需求,此时可通过自定义校验注解实现个性化校验,再结合全局异常捕获,统一处理所有参数校验失败的异常,实现校验逻辑与业务逻辑的解耦。

一、基础准备:引入依赖(极简配置)

Spring Boot 2.3及以上版本,已默认集成hibernate-validator(JSR380的实现),若项目为低版本,需手动引入以下依赖,确保注解能够正常生效。依赖配置简洁,无需额外配置其他参数,引入后即可直接使用JSR380相关校验注解。

<!-- JSR380 参数校验核心依赖(低版本Spring Boot需手动引入) -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.5.Final</version>
</dependency>
<!-- 可选:validation-api 规范依赖(确保注解识别) -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

注意:高版本Spring Boot(2.3+)无需手动引入上述依赖,启动项目后即可直接使用@NotNull、@NotBlank等校验注解;若引入后出现依赖冲突,可排除项目中重复的validation相关依赖,确保依赖版本统一。

二、JSR380 核心注解用法(基础校验,摒弃if-else)

JSR380提供了一系列常用的校验注解,可直接作用于实体类的字段上,也可直接作用于接口方法的参数上,用于校验参数的合法性(如非空、长度、格式等)。使用时只需在需要校验的字段/参数上添加对应注解,并指定校验失败的提示信息,即可完成基础的参数校验,无需编写任何if-else判断。

以下是开发中最常用的JSR380注解,结合实体类案例详细说明用法,所有注解均支持自定义校验失败提示信息,提升接口返回的友好性。

2.1 常用核心注解说明

2.2 实体类中使用案例(最常用场景)

在接口开发中,前端传参通常会封装为实体类(如用户注册、登录参数),将校验注解作用于实体类字段,配合@Valid注解触发校验,即可完成参数合法性校验,彻底摒弃if-else。

// 示例:用户注册参数实体类(所有注解均无导包,直接复制可用)
@Data
public class UserRegisterDTO {
    // 用户名:非空、长度2-20位
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间")
    private String username;
    // 密码:非空、长度6-16位
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 16, message = "密码长度必须在6-16位之间")
    private String password;
    // 邮箱:非空、符合邮箱格式
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    // 年龄:非空、18-60岁之间
    @NotNull(message = "年龄不能为空")
    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 60, message = "年龄不能大于60岁")
    private Integer age;
    // 手机号:非空、符合手机号正则(此处用@Pattern临时实现,后续用自定义注解优化)
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
}

2.3 接口中触发校验

在接口方法的实体类参数前添加**@Valid**注解(或@Validated),即可触发该实体类的参数校验;若参数校验失败,会抛出MethodArgumentNotValidException异常(请求体参数校验失败)或ConstraintViolationException异常(路径参数、请求参数校验失败),后续通过全局异常捕获统一处理这些异常,返回友好的提示信息。

// 示例:用户注册接口(触发参数校验)
@RestController
@RequestMapping("/user")
public class UserController {
    // 注册接口:@Valid 触发UserRegisterDTO的参数校验
    @PostMapping("/register")
    public Result<Void> register(@Valid @RequestBody UserRegisterDTO registerDTO) {
        // 校验通过后,执行注册业务逻辑(无需任何if-else判断参数)
        // 业务逻辑...
        return Result.success("注册成功");
    }
    // 示例:路径参数校验(直接作用于参数上)
    @GetMapping("/{id}")
    public Result<User> getUserById(@PathVariable @NotNull(message = "用户ID不能为空") Long id) {
        // 校验通过,执行查询逻辑
        // 业务逻辑...
        return Result.success(null);
    }
}

注意:@Valid和@Validated的区别:@Valid是JSR380规范提供的注解,支持嵌套校验(如实体类中包含另一个实体类字段,需校验该嵌套字段时使用);@Validated是Spring提供的注解,支持分组校验,可根据不同场景(如新增、修改)执行不同的校验规则,两者均可触发参数校验,开发中可根据需求选择。

三、全局异常捕获:统一处理参数校验错误

当参数校验失败时,Spring Boot会抛出对应的异常(请求体参数校验失败抛出MethodArgumentNotValidException,路径参数/请求参数校验失败抛出ConstraintViolationException),若不进行处理,接口会返回默认的错误信息,不够友好且不符合项目统一的响应格式。

结合前文实现的全局异常处理框架,我们只需新增对这两种异常的捕获,提取校验失败的提示信息,封装为统一的响应格式(如Result对象),即可实现参数校验错误的统一处理,无需在每个接口中单独处理校验失败的情况,进一步简化代码。

3.1 全局异常处理器新增校验异常捕获

在全局异常处理器(GlobalExceptionHandler)中,新增两个异常处理方法,分别捕获MethodArgumentNotValidException和ConstraintViolationException,提取校验失败的提示信息,返回统一的失败响应。

// 全局异常处理器:新增参数校验异常捕获(无导包,直接复制可用)
@RestControllerAdvice
public class GlobalExceptionHandler {
    // 统一响应格式(前文已实现,此处复用)
    @Data
    public static class Result<T> {
        private Integer code;
        private String message;
        private T data;
        public static <T> Result<T> success(T data) { /* 实现省略 */ }
        public static <T> Result<T> fail(Integer code, String message) { /* 实现省略 */ }
    }
    // 1. 捕获请求体参数校验失败异常(@RequestBody + @Valid)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 提取校验失败的第一条提示信息(也可提取所有提示信息,根据需求调整)
        String errorMsg = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
        // 返回统一失败响应(状态码400:参数错误)
        return Result.fail(400, errorMsg);
    }
    // 2. 捕获路径参数/请求参数校验失败异常(@PathVariable/@RequestParam + 校验注解)
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
        // 提取校验失败的第一条提示信息
        String errorMsg = e.getConstraintViolations().iterator().next().getMessage();
        // 返回统一失败响应
        return Result.fail(400, errorMsg);
    }
    // 其他异常处理方法(前文已实现,此处省略)
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) { /* 实现省略 */ }
}

3.2 校验效果演示

当前端传参不符合校验规则时,接口会直接返回统一的失败响应,无需手动处理,示例如下:

通过这种方式,不仅统一了参数校验失败的响应格式,还彻底摒弃了接口中的if-else判断,让代码更简洁、更优雅,同时提升了接口的友好性和可维护性。

四、自定义校验注解:满足特殊场景需求

JSR380提供的默认注解,只能满足基础的参数校验场景,而在实际开发中,经常会遇到一些特殊的校验需求(如手机号格式、身份证号、邮政编码等),此时@Pattern注解虽然可以实现,但正则表达式分散在各个字段上,无法复用,且代码可读性差。

通过自定义校验注解,可将特殊的校验逻辑封装起来,实现校验逻辑的复用,同时让代码更简洁、更规范。下面以“手机号格式校验”为例,详细讲解自定义校验注解的实现步骤,其他特殊场景可参考此步骤仿写。

4.1 步骤1:自定义校验注解(@Phone)

自定义校验注解需添加JSR380规范的相关元注解(@Target、@Retention、@Constraint),指定注解的作用范围、保留策略,以及关联的校验器(Validator),同时定义校验失败的提示信息(可自定义)。

// 自定义手机号校验注解(@Phone)
@Target({ElementType.FIELD, ElementType.PARAMETER}) // 注解作用范围:字段、参数
@Retention(RetentionPolicy.RUNTIME) // 保留策略:运行时生效
@Constraint(validatedBy = PhoneValidator.class) // 关联校验器(核心,实现校验逻辑)
public @interface Phone {
    // 校验失败提示信息(默认值,可自定义)
    String message() default "手机号格式不正确";
    // 分组校验(默认分组,可根据需求扩展)
    Class<?>[] groups() default {};
    // 负载(默认,无需修改)
    Class<? extends Payload>[] payload() default {};
}

4.2 步骤2:实现校验器(PhoneValidator)

校验器需实现ConstraintValidator接口,重写initialize(初始化)和isValid(校验逻辑)两个方法,其中isValid方法是核心,用于实现具体的校验逻辑(如手机号正则匹配),校验通过返回true,校验失败返回false。

// 手机号校验器(与@Phone注解关联,实现校验逻辑)
public class PhoneValidator implements ConstraintValidator<Phone, String> {
    // 手机号正则表达式(标准手机号格式:13-19开头,共11位)
    private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
    /**
     * 初始化方法(可选,用于获取注解中的参数,此处无需使用)
     */
    @Override
    public void initialize(Phone constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }
    /**
     * 核心校验逻辑
     * @param value 待校验的参数(手机号字符串)
     * @param context 校验上下文(无需修改)
     * @return true:校验通过,false:校验失败
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 校验逻辑:非空判断 + 正则匹配
        if (value == null || value.isEmpty()) {
            // 若需要校验手机号非空,可在此处返回false;若允许为空,返回true(结合@NotBlank使用)
            return false;
        }
        // 正则匹配手机号格式
        return value.matches(PHONE_REGEX);
    }
}

4.3 步骤3:使用自定义校验注解

自定义注解实现完成后,使用方式与JSR380默认注解完全一致,直接作用于实体类字段或接口参数上,配合@Valid注解触发校验,校验失败后会被全局异常处理器捕获,返回自定义的提示信息。

// 示例:修改UserRegisterDTO,使用@Phone注解替代@Pattern
@Data
public class UserRegisterDTO {
    // 其他字段不变...

    // 手机号:使用自定义@Phone注解,替代原来的@Pattern,可复用
    @NotBlank(message = "手机号不能为空")
    @Phone(message = "手机号格式不正确(请输入13-19开头的11位手机号)")
    private String phone;
}

注意:自定义校验注解可灵活扩展,若需要校验身份证号、邮政编码等,只需复制上述步骤,修改注解名称、正则表达式和校验逻辑即可;若校验逻辑复杂(如校验手机号是否已被注册),可在isValid方法中注入Service,调用业务逻辑实现校验(需添加@Component注解,让校验器被Spring管理)。

五、进阶优化:分组校验(可选,提升灵活性)

在实际开发中,同一个实体类可能会用于不同的接口场景(如新增用户和修改用户),不同场景的参数校验规则可能不同(如新增用户需要校验用户名非空,修改用户可允许用户名不变)。此时可通过分组校验,实现不同场景下的差异化校验,进一步提升参数校验的灵活性。

分组校验的核心是定义分组接口(空接口,无需实现任何方法),在校验注解中指定分组,在接口中通过@Validated指定需要执行的分组,即可实现差异化校验。示例如下:

// 1. 定义分组接口(空接口,用于区分不同场景)
public interface AddGroup {} // 新增场景分组
public interface UpdateGroup {} // 修改场景分组

// 2. 实体类中指定注解的分组
@Data
public class UserDTO {
    // 新增时校验非空,修改时无需校验(id由前端传入,无需校验)
    @NotNull(message = "用户ID不能为空", groups = AddGroup.class)
    private Long id;

    // 新增和修改时都需要校验非空和长度
    @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间", groups = {AddGroup.class, UpdateGroup.class})
    private String username;
}

// 3. 接口中指定分组,触发对应分组的校验
@RestController
@RequestMapping("/user")
public class UserController {
    // 新增用户:仅执行AddGroup分组的校验
    @PostMapping("/add")
    public Result<Void> add(@Validated(AddGroup.class) @RequestBody UserDTO userDTO) {
        // 业务逻辑...
        return Result.success();
    }

    // 修改用户:仅执行UpdateGroup分组的校验
    @PutMapping("/update")
    public Result<Void> update(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
        // 业务逻辑...
        return Result.success();
    }
}

六、总结

本文通过JSR380规范注解、全局异常捕获、自定义校验注解三个核心环节,实现了Spring Boot前端传参的优雅校验,彻底摒弃了繁琐的if-else判断,让参数校验逻辑更简洁、更规范、更可复用。

核心要点总结:1. 基础校验使用JSR380默认注解(@NotBlank、@Size等),配合@Valid触发校验;2. 全局异常捕获校验失败异常,统一返回响应格式;3. 特殊场景使用自定义校验注解,实现校验逻辑复用;4. 复杂场景可通过分组校验,实现差异化校验需求。

通过这种方式,不仅提升了代码的可读性和可维护性,还降低了后期的维护成本,同时让接口返回的提示信息更友好,符合企业级开发的规范,是Spring Boot接口开发中参数校验的最优实践之一。

到此这篇关于Spring Boot 参数校验全局处理(JSR380 + 自定义校验)的文章就介绍到这了,更多相关Spring Boot 参数校验全局内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文