如何在Spring Boot中优雅地进行参数校验

1. 前言

在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用if ... else ...手动进行参数验证。当参数个数大于3个时,使用if ... else ...进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。

在Java中,注解(Annotation)是一种代码标记,通常用于提供元数据,这些元数据可以被编译器或运行时环境使用。这些注解通常用于框架和库中,以实现更加灵活和可配置的代码。

2. 常用注解描述

  1. @NotNull

    • 描述:标记一个值不能为null。
    • 示例:
    java 复制代码
    public class User {  
        @NotNull  
        private String name;  
        // ...  
    }
  2. @NotEmpty

    • 描述:标记一个集合(如List、Set等)不能为空。
    • 示例:
    java 复制代码
    public class User {  
        @NotEmpty  
        private List<String> interests;  
        // ...  
    }
  3. @NotBlank

    • 描述:标记一个字符串不能为空白(即null、空字符串或只包含空格)。
    • 示例:
    java 复制代码
    public class User {  
        @NotBlank  
        private String username;  
        // ...  
    }
  4. @Size

    • 描述:标记一个字符串或集合的大小必须在指定的范围内。
    • 示例:
    java 复制代码
    public class User {  
        @Size(min = 2, max = 50)  
        private String username;  
        // ...  
    }
  5. @Min@Max

    • 描述:标记一个数值必须在指定的最小值和最大值之间。
    • 示例:
    java 复制代码
    public class User {  
        @Min(18)
        @Max(60)  
        private int age;  
        // ...  
    }
  6. @DecimalMin@DecimalMax

    • 描述:标记一个浮点数或双精度数必须在指定的最小值和最大值之间。
    • 示例:
    java 复制代码
    public class User {  
        @DecimalMin("0.01") @DecimalMax("100.00")  
        private double discount;  
        // ...  
    }
  7. @Digits

    • 描述:标记一个整数或浮点数必须在指定的精度和总数值范围内。
    • 示例:
    java 复制代码
    public class User {  
        @Digits(integer = 3, fraction = 2) // 总长度为5,3位整数,2位小数。例如:"123.45" 是合法的。  
        private BigDecimal amount;  
        // ...  
    }
  8. @Pattern

    • 描述:标记一个字符串必须匹配指定的正则表达式。通常用于验证输入格式。例如电子邮件地址、电话号码格式等。@Pattern注解在javax.validation.constraints包中。@Pattern(regexp = "^\w{5,}$")表示长度在5-20之间,由字母、数字、下划线组成的字符串。@Pattern注解用于类字段上,例如用户密码字段。
    • 示例:
    java 复制代码
    public class User { 
        @Pattern(regexp = "^[a-zA-Z0-9]*$") 
        private String password; //... 
    } 
  9. @Email

    • 描述:标记一个字符串必须是一个有效的电子邮件地址。
    • 示例:
    java 复制代码
    @Email
    private String emailAddress;
  10. @AssertTrue@AssertFalse

    • 描述:标记一个布尔值必须为true或false。
    • 示例:
    java 复制代码
    @AssertTrue
    private boolean isValid;
    
    @AssertFalse
    private boolean isNotValid;
  11. @Future

    • 描述:标记一个日期必须是在未来某个时间点之后。
    • 示例:
    java 复制代码
    @Future
    private Date expiryDate;
  12. @Past

    • 描述:标记一个日期必须是在过去某个时间点之前。
    • 示例:
    java 复制代码
    @Past
    private Date purchaseDate;

这些注解通常与验证框架(如Hibernate Validator)一起使用,以在运行时验证对象的属性。

3. 注解使用

  1. 在项目的pom.xml文件中添加如下依赖:
xml 复制代码
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>
  1. 在实体类中使用上述注解,代码如下:

    java 复制代码
    package com.yyqq.demo.entity;
    
    import lombok.Data;
    import javax.validation.constraints.*;
    
    @Data
    public class User {
        @NotBlank(message = "用户姓名不能为空")
        private String name;
    
        @NotBlank(message = "密码不能为空")
        @Size(min = 6, message = "密码长度不能少于6位")
        private String password;
    
        @Min(value = 0, message = "年龄不能小于0岁")
        @Max(value = 150, message = "年龄不应超过150岁")
        private Integer age;
    
        @Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\d{8}$", message = "手机号格式不正确")
        private String phone;
    }
  2. 控制器类使用验证,代码如下:

    java 复制代码
    import com.yyqq.demo.util.Result;
    import com.yyqq.demo.entity.User;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.validation.Valid;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @PostMapping("/add")
        public Result add(@Valid @RequestBody User user) {
         	return Result.success(user);
        }
    }
  3. Result是封装结果的一个类,用于返回统一的结果,代码如下:

    java 复制代码
    package com.yyqq.demo.util;
    
    import lombok.Data;
    import java.io.Serializable;
    
    @Data
    public class Result<T> implements Serializable {
        private int code;
        private boolean success;
        private T data;
        private String msg;
    
        private Result(int code, T data, String msg) {
            this.code = code;
            this.data = data;
            this.msg = msg;
            this.success = code == 200;
        }
    
        public static <T> Result<T> sucess(T data) {
        	return new Result<>(200, data, null);
        }
    
        public static <T> Result<T> fail(String msg) {
        	return new Result<>(500, null, msg);
        }
    }
  4. 定义全局异常处理类,我们在全局异常处理类中使用ExceptionHandler捕获BindException异常,获取参数验证异常信息,最后返回统一的异常结果格式,代码如下:

    java 复制代码
    package com.yyqq.demo.util;
    
    import com.yyqq.demo.util.Result;
    import org.springframework.validation.BindException;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(BindException.class)
        public Result handleError(BindException e) {
            BindingResult bindingResult = e.getBindingResult();
            return Result.fail(bindingResult.getFieldError().getDefaultMessage());
        }
    }

4. 使用分组验证

  1. 用于插入记录时的分组验证,代码如下:
java 复制代码
package com.yyqq.demo.interceptor;
import javax.validation.groups.Default;

public interface Insert extends Default {
}
  1. 用于更新记录时的分组验证,代码如下:
java 复制代码
package com.yyqq.demo.interceptor;
import javax.validation.groups.Default;

public interface Update extends Default {
}
  1. 在实体类中进行分组标记,代码如下:
java 复制代码
package com.yyqq.demo.entity;

import lombok.Data;
import javax.validation.constraints.*;

@Data
public class User {
    @NotBlank(groups = {Insert.class, Update.class})
    @NotBlank(message = "用户姓名不能为空")
    private String name;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, message = "密码长度不能少于6位")
    private String password;

    @Min(value = 0, message = "年龄不能小于0岁")
    @Max(value = 150, message = "年龄不应超过150岁")
    private Integer age;

    @NotBlank(groups = {Insert.class, Update.class})
    @Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$", message = "手机号格式不正确")
    private String phone;
}
  1. 控制器类使用分组验证
java 复制代码
package com.yyqq.demo.controller;

import com.yyqq.demo.util.Result;
import com.yyqq.demo.entity.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/add")
    public Result add(@Validated(Insert.class) @RequestBody User user) {
     	return Result.success(user);
    }
    
    @PostMapping("/update")
    public Result update(@Validated(Update.class) @RequestBody User user) {
     	return Result.success(user);
    }
}

5. 自定义验证注解

除了框架自带的注解,平时的工作中可能需要我们自定义验证注解处理特定的业务需求。

  1. 定义注解
java 复制代码
package com.yyqq.demo.validate;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Retention(RUNTIME)
@Constraint(validatedBy = {PhoneValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
public @interface Phone {

    String message() default "手机号格式错误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  1. 定义验证器类
java 复制代码
package com.yyqq.demo.validate;

import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidator;
import java.util.regex.Pattern;

public class PhoneValidator implements ConstraintValidator<Phone, String> {
    private static final String REGEX = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$";

    @Override
    public boolean isValid(String s, ConstraintValidatorContext context) {
        boolean result = false;
        try {
            result = Pattern.matches(REGEX, s);
        } catch (Exception e) {
            System.out.println("验证手机号格式时发生异常,异常信息:" + e);
        }
        return result;
    }
}
  1. 实体类使用注解
java 复制代码
package com.yyqq.demo.validate;

public class User {

    //其他属性...

    //  @Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$", message = "手机号格式不正确")
    @Phone
    private String phone;
}

6. @Valid与@Validated的区别

用于参数校验的注解通常有两个:@Valid@Validated。它们的区别有如下几点:

区别 @Valid @Validated
来源 @Valid是Java标准注解 @Validated是Spring框架定义的注解。
是否支持分组验证 不支持 支持
使用位置 构造函数、方法、方法参数、成员属性 类、方法、方法参数,不能用于成员属性
是否支持嵌套校验 支持 不支持
相关推荐
袁袁袁袁满几秒前
Python之MoviePy视频编辑模块介绍与应用
开发语言·python·音视频·moviepy·视频编辑模块
蛔虫在他乡1 分钟前
从零开始的python学习生活1
python·学习·生活
java66666888810 分钟前
在Spring Boot中集成分布式日志收集方案
spring boot·分布式·jenkins
java66666888817 分钟前
深入理解Spring Boot中的配置加载顺序
java·spring boot·后端
懒大王爱吃狼18 分钟前
Python爬虫+数据分析+数据可视化图形-爬取高校排名数据
爬虫·python·信息可视化
春山之外22 分钟前
基于IIS的Windows系统Django项目本地部署
后端·python·django·iis·服务器部署
AllenIverrui42 分钟前
MyBatisPlus的使用
spring boot·spring·java-ee·mybatis
只是有点小怂1 小时前
【PYG】 PyTorch中size方法和属性
人工智能·pytorch·python
冯宝宝^1 小时前
图书管理系统
服务器·数据库·vue.js·spring boot·后端
西邮彭于晏1 小时前
差分进化算法
windows·python·算法