Spring Boot | 一种优雅的参数校验方案(个人总结)
itomcoil 2025-07-06 12:58 2 浏览
1、前言
在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用if ... else ...手动进行参数验证。当参数个数大于3个时,使用if ... else ...进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。
2、常用注解
下面列举一些常用的验证注解:
- 1. @NotNull:值不能为null;
- 2. @NotEmpty:字符串、集合或数组的值不能为空,即长度大于0;
- 3. @NotBlank:字符串的值不能为空白,即不能只包含空格;
- 4. @Size:字符串、集合或数组的大小是否在指定范围内;
- 5. @Min:数值的最小值;
- 6. @Max:数值的最大值;
- 7. @DecimalMin:数值的最小值,可以包含小数;
- 8. @DecimalMax:数值的最大值,可以包含小数;
- 9. @Digits:数值是否符合指定的整数和小数位数;
- 10. @Pattern:字符串是否匹配指定的正则表达式;
- 11. @Email:字符串是否为有效的电子邮件地址;
- 12. @AssertTrue:布尔值是否为true;
- 13. @AssertFalse:布尔值是否为false;
- 14. @Future:日期是否为将来的日期;
- 15. @Past:日期是否为过去的日期;
3、实现示例
3.1 创建项目,添加依赖
本示例中使用的spring boot 版本为2.7.7。
使用IDEA创建一个spring boot项目。在项目的pom.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>
3.2 创建示例实体类
创建一个User实体类,在实体类中需要对属性进行如下验证:
- o name - 用户姓名,不能为空;
- o password - 密码,不能为空,长度不能小于6;
- o age - 年龄,大于0小于150;
- o phone - 手机号,满足手机号格式;
User实体类具体代码如下:
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}#34;, message = "手机号格式不正确")
private String phone;
}
3.3 创建控制器类
创建一个简单的控制器类,用于演示参数验证功能。
控制器代码如下:
import cn.ddcherry.springboot.demo.util.R;
import cn.ddcherry.springboot.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("/save")
public R save(@Valid @RequestBody User user) {
return R.ok(user);
}
}
我们在参数user前添加@Valid注解,表示验证该参数。
其中R是封装的一个简易工具类,用于统一返回结果格式。代码如下:
import lombok.Data;
import java.io.Serializable;
@Data
public class R<T> implements Serializable {
private int code;
private boolean success;
private T data;
private String msg;
private R(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
this.success = code == 200;
}
public static <T> R<T> ok(T data) {
return new R<>(200, data, null);
}
public static <T> R<T> error(String msg) {
return new R<>(500, null, msg);
}
}
3.4 定义全局异常处理类
全局异常处理类代码如下:
import cn.ddcherry.springboot.demo.util.R;
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 R handleError(BindException e) {
BindingResult bindingResult = e.getBindingResult();
return R.error(bindingResult.getFieldError().getDefaultMessage());
}
}
我们在全局异常处理类中使用ExceptionHandler捕获BindException异常,获取参数验证异常信息,最后返回统一的异常结果格式。
3.5 测试
在接口测试工具中测试接口。
以密码长度不足6位为例,返回的结果如下图所示:
image-20230925143558493
3.6 小结
至此,我们就简单地讲述了Spring Boot项目使用@Valid注解进行参数验证的实现步骤。示例的验证逻辑流程如下图所示:
参数验证通过参数验证失败客户端调用接口执行参数验证逻辑执行控制器后续业务逻辑抛出BindException异常全局异常处理器捕获BindException异常获取错误信息,返回自定义错误消息体
4、进阶
4.1 @Valid与@Validated的区别
用于参数校验的注解通常有两个:@Valid和@Validated。它们的区别有如下几点:
区别 | @Valid | @Validated |
来源 | @Valid 是Java标准注解 | @Validated 是Spring框架定义的注解。 |
是否支持分组验证 | 不支持 | 支持 |
使用位置 | 构造函数、方法、方法参数、成员属性 | 类、方法、方法参数,不能用于成员属性 |
是否支持嵌套校验 | 支持 | 不支持 |
4.2 自定义验证注解
除了框架自带的注解,平时的工作中可能需要我们自定义验证注解处理特定的业务需求。这里汪小成将上面User类中的手机号格式验证改成使用自定义注解的验证方式。
4.2.1 定义注解
@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 {};
}
说明:
- o @Constraint(validatedBy = {PhoneValidator.class}):用于指定验证器类;
- o @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}):指定@Phone注解可以作用在方法、字段、构造函数、参数以及类型上;
4.2.2 定义验证器类
public class PhoneValidator implements ConstraintValidator<Phone, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(PhoneValidator.class);
private static final String REGEX = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}#34;;
@Override
public boolean isValid(String s, ConstraintValidatorContext context) {
boolean result = false;
try {
result = Pattern.matches(REGEX, s);
} catch (Exception e) {
LOGGER.error("验证手机号格式时发生异常,异常信息:", e);
}
return result;
}
}
4.2.3 使用注解
@Data
public class User {
// 省略其它代码
- // @Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}#34;, message = "手机号格式不正确")
+ @Phone
private String phone;
}
这样我们就成功地使用自定义注解@Phone验证手机号格式了。
使用自定义注解实现业务验证的一个比较大的优点是可以复用。所有需要进行手机号格式验证的属性,只需要添加上@Phone注解就可以了。如果后期我们需要修改手机号的验证规则,只需要修改PhoneValidator类中的验证逻辑,就可以作用于所有添加了@Phone注解的字段了。
相关推荐
- zabbix企业微信告警(zabbix5.0企业微信告警详细)
-
zabbix企业微信告警的前提是用户有企业微信且创建了一个能够发送消息的应用,具体怎么创建可以协同用户侧企业微信的管理员。第一步:企业微信准备我们需要的内容包括企业ID,应用的AgentId和应用的S...
- 基于centos7部署saltstack服务器管理自动化运维平台
-
概述SaltStack是一个服务器基础架构集中化管理平台,具备配置管理、远程执行、监控等功能,基于Python语言实现,结合轻量级消息队列(ZeroMQ)与Python第三方模块(Pyzmq、PyCr...
- 功能实用,效率提升,Python开发的自动化运维工具
-
想要高效的完成日常运维工作,不论是代码部署、应用管理还是资产信息录入,都需要一个自动化运维平台。今天我们分享一个开源项目,它可以帮助运维人员完成日常工作,提高效率,降低成本,它就是:OpsManage...
- centos定时任务之python脚本(centos7执行python脚本)
-
一、crontab的安装默认情况下,CentOS7中已经安装有crontab,如果没有安装,可以通过yum进行安装。yuminstallcrontabs二、crontab的定时语法说明*代表取...
- Fedora 41 终于要和 Python 2.7 说再见了
-
红帽工程师MiroHroncok提交了一份变更提案,建议在Fedora41中退役Python2.7,并放弃仍然依赖Python2的软件包。Python2已于2020年1...
- 软件测试|使用docker搞定 Python环境搭建
-
前言当我们在公司的电脑上搭建了一套我们需要的Python环境,比如我们的版本是3.8的Python,那我可能有一天换了一台电脑之后,我整套环境就需要全部重新搭建,不只是Python,我们一系列的第三方...
- 环境配置篇:Centos如何安装Python解释器
-
有小伙伴时常会使用Python进行编程,那么如何配置centos中的Python环境呢?1)先安装依赖yuminstallgccgcc-c++sqlite-devel在root用户下操作:1...
- (三)Centos7.6安装MySql(centos8.3安装docker)
-
借鉴文章:centos7+django+python3+mysql+阿里云部署项目全流程。这里我只借鉴安装MySql这一部分。链接:https://blog.csdn.net/a394268045/a...
- Centos7.9 如何安装最新版本的Docker
-
在CentOS7.9系统中安装最新版本的Docker,需遵循以下步骤,并注意依赖项的兼容性问题:1.卸载旧版本Docker(如已安装)若系统中存在旧版Docker,需先卸载以避免冲突:sudoy...
- Linux 磁盘空间不够用?5 招快速清理文件,释放 10GB 空间不是梦!
-
刚收到服务器警告:磁盘空间不足90%!装软件提示Nospaceleftondevice!连日志都写不进去,系统卡到崩溃?别慌!今天教你5个超实用的磁盘清理大招,从临时文件到无用软件一键搞定...
- Playwright软件测试框架学习笔记(playwright 官网)
-
本文为霍格沃兹测试开发学社学员学习笔记,人工智能测试开发进阶学习文末加群。一,Playwright简介Web自动化测试框架。跨平台多语言支持。支持Chromium、Firefox、WebKit...
- 为SpringDataJpa集成QueryObject模式
-
1.概览单表查询在业务开发中占比最大,是所有CRUDBoy的入门必备,所有人在JavaBean和SQL之间乐此不疲。而在我看来,该部分是最枯燥、最没有技术含量的“伪技能”。1.1.背景...
- 金字塔测试原理:写好单元测试的8个小技巧,一文总结
-
想必金字塔测试原理大家已经很熟悉了,近年来的测试驱动开放在各个公司开始盛行,测试代码先写的倡议被反复提及。鉴于此,许多中大型软件公司对单元测试的要求也逐渐提高。那么,编写单元测试有哪些小技巧可以借鉴和...
- 测试工程师通常用哪个单元测试库来测试Java程序?
-
测试工程师在测试Java程序时通常使用各种不同的单元测试库,具体选择取决于项目的需求和团队的偏好。我们先来看一些常用的Java单元测试库,以及它们的一些特点: 1.JUnit: ·描述:JUn...
- JAVA程序员自救之路——SpringAI评估
-
背景我们用SpringAI做了大模型的调用,RAG的实现。但是我们做的东西是否能满足我们业务的要求呢。比如我们问了一个复杂的问题,大模型能否快速准确的回答出来?是否会出现幻觉?这就需要我们构建一个完善...
- 一周热门
- 最近发表
-
- zabbix企业微信告警(zabbix5.0企业微信告警详细)
- 基于centos7部署saltstack服务器管理自动化运维平台
- 功能实用,效率提升,Python开发的自动化运维工具
- centos定时任务之python脚本(centos7执行python脚本)
- Fedora 41 终于要和 Python 2.7 说再见了
- 软件测试|使用docker搞定 Python环境搭建
- 环境配置篇:Centos如何安装Python解释器
- (三)Centos7.6安装MySql(centos8.3安装docker)
- Centos7.9 如何安装最新版本的Docker
- Linux 磁盘空间不够用?5 招快速清理文件,释放 10GB 空间不是梦!
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)