快速上手:SpringBoot自定义请求参数校验
zhezhongyun 2025-09-04 22:09 13 浏览
作者:UncleChen
来源:
http://unclechen.github.io/
最近在工作中遇到写一些API,这些API的请求参数非常多,嵌套也非常复杂,如果参数的校验代码全部都手动去实现,写起来真的非常痛苦。正好Spring轮子里面有一个Validation,这里记录一下怎么使用,以及怎么自定义它的返回结果。
一、Bean Validation基本概念
Bean Validation是Java中的一项标准,它通过一些注解表达了对实体的限制规则。通过提出了一些API和扩展性的规范,这个规范是没有提供具体实现的,希望能够Constrain once, validate everywhere。现在它已经发展到了2.0,兼容Java8。
hibernate validation实现了Bean Validation标准,里面还增加了一些注解,在程序中引入它我们就可以直接使用。
Spring MVC也支持Bean Validation,它对hibernate validation进行了二次封装,添加了自动校验,并将校验信息封装进了特定的BindingResult类中,在SpringBoot中我们可以添加implementation('
org.springframework.boot:spring-boot-starter-validation')引入这个库,实现对bean的校验功能。
二、基本用法
在name属性上,添加@NotBlank和@Size(max=10)的注解,表示User对象的name属性不能为字符串且长度不能超过10个字符。
然后我们暂时不添加任何多余的代码,直接写一个UserController对外提供一个RESTful的GET接口,注意接口的参数用到了@Validated注解。
启动SpringBoot程序,发一个测试请求看一下:
http://127.0.0.1:8080/validation/get?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&password=1
返回的结果是,注意此时的HTTP STATUS CODE = 400:
此时已经可以实现参数的校验了,但是返回的结果不太友好,下面看一下怎么定制返回的消息。在定制返回结果前,先看下一下内置的校验注解有哪些,在这里我不一个个去贴了,写代码的时候根据需要进入到源码里面去看即可。
早期Spring版本中,都是在Controller的方法中添加Errors/BindingResult参数,由Spring注入Errors/BindingResult对象,再在Controller中手写校验逻辑实现校验。新版本提供注解的方式(Controller上面bean加一个@Validated注解),将校验逻辑和Controller分离。
三、自定义校验
3.1 自定义注解
显然除了自带的NotNull、NotBlank、Size等注解,实际业务上还会需要特定的校验规则。
假设我们有一个参数address,必须以Beijing开头,那我们可以定义一个注解和一个自定义的Validator。
然后在User.java中增加一个address属性,并给它加上上面这个自定义的注解,这里我们定义了一个可以传入start参数的注解,表示应该以什么开头。
@StartWithValidation(message = "Param 'address' must be start with 'Beijing'.", start = "Beijing") private String address;
除了定义可以作用于属性的注解外,其实还可以定义作用于class的注解(@Target({TYPE})),用于校验class的实例。
3.2 自定义Validator
第一步,实现一个Validator。(这种方法不需要我们的bean里面有任何注解之类的东西)
第二步,修改Controller代码,注入上面的UserValidator实例,并给Controller的方法参数加上@Validated注解,即可完成和前面自定义注解一样的校验功能。
这个方法和自定义注解的区别在于不需要在Bean里面添加注解,并且可以更加灵活的把一个Bean里面所有的Field的校验代码都搬到一起,而不是每一个属性都去加注解,如果校验的属性非常多,且默认注解的能力又不够的话,这种方式也是不错的,可以避免大量的自定义注解。
3.3 以编程的方式校验(手动)
这种方式可以算是原始的Hibernate-Validation的方式。直接看代码,这里有一个比较不同的是,可以使用Hibernate-Validation的Fail fast mode。因为前面的方式,都将所有的参数都验证完了,再把错误返回。有时我们希望遇到一个参数错误,就立即返回。
设置fast-fail为true可以达到这个目的。不过貌似不能再用@Validated注解方法参数了,而是要用ValidatorFactory创建Validator。
在实际开发中,不必每次都编写代码创建Validator,可以采用@Configuration的方式创建,然后再@Autowired注入到每个需要使用Validator的Controller当中。
3.4 定义分组校验
有的时候,我们会有两个不同的接口,但是会使用到同一个Bean来作为VO(意思是两个接口的URI不同,但参数中都用到了同一个Bean)。而在不同的接口上,对Bean的校验需求可能不一样,比如接口2需要校验studentId,而接口1不需要。那么此时就可以用到校验注解的分组groups。
到这里,也可以带一嘴Valid和Validated注解的区别,其代码注释写着后者是对前者的一个扩展,支持了group分组的功能。
3.5 定制返回码和消息
第二节中定义了一个ServiceResponse,其实作为一个开放的API,不论用户传入任何参数,返回的结果都应该是预先定义好的格式,并且可以写明在接口文档中,即使发生了校验失败,应该返回一个包含错误码code(发生错误时一般大于0)和message字段。
{ "code": 51000, "message": "Param 'name' must be less than 10 characters." }
的结果,而HTTP STATUS CODE一直都是200。
为了实现这个目的,我们加一个全局异常处理方法。
在上面的方法中,我们处理了BindException(非请求body参数,例如@RequestParam接收的)和
MethodArgumentNotValidException(请求body里面的参数,例如@RequestBody接收的),这两类Exception里面都有一个BindingResult对象,它里面有一个包装成FieldError的List,保存着Bean对象出现错误的Field等信息。
取出它里面defaultMessage,放到统一的ServiceResponse返回即可实现返回码和消息的定制。由于消息内容是有注解默认的DefaultMessage决定的,为了按照自定义的描述返回,在Bean对象的注解上需要手动赋值为希望返回的消息内容。
@NotBlank(message = "Param 'name' can't be blank.") @Size(max=10,message = "Param 'name' must be less than 10 characters.") private String name;
这样当name参数长度超过10时,就会返回
{ "code": 51000, "message": "Param 'name' must be less than 10 characters." }
这里的FieldError fieldError = ex.getFieldError();只会随机返回一个出错的属性,如果Bean对象的多个属性都出错了,可以调用ex.getFieldErrors()来获得,这里也可以看到Spring Validation在参数校验时不会在第一次碰到参数错误时就返回,而是会校验完成所有的参数。
如果不想手动编程去校验,那么这里可以只读取一个随机的FieldError,返回它的错误消息即可。
3.6 更加细致的返回码和消息
其实还有一种比较典型的自定义返回,就是错误码(code)和消息(message)是一一对应的,比如:
- 51001:字符串长度过长
- 51002:参数取值过大
…
这种情况比较特殊,一般当参数错误的时候,会返回一个整体的参数错误的错误码,然后携带参数的错误信息。但有时,业务上就要不同的参数错误,既要错误码不同,错误信息也要不同。我想了下,有两种思路。
- 第一种:通过message同时包含错误码和错误信息,在全局异常捕获方法中,再把它们拆开。
- 第二种:手动校验,抛出自定义的Exception(里面带有code、message)。手动校验这里,如果每一个Controller都去写一遍,确实比较费劲,可以结合AOP来实现,或者抽出一个基类BaseController的方式。
四、小结
其实在实际的工作中,肯定还有更复杂的校验逻辑,但是不一定非要都用框架去实现,框架里面的实现(比如注解)应该是一个比较简单通用的校验,能够达到复用,减少重复的劳动。而更加复杂的逻辑校验,一定是存在具体业务当中的,最好是在业务代码里面实现。
还有一点需要注意,Spring Validation的isValid方法,如果返回false,那么Controller不再会被调用,而是直接返回。如果你在Controller上面加了AOP进行接口调用统计的话,可能会漏掉。这个时候,我们不应该让Controller不调用,建议这种情况在AOP里面对Controller的参数切面进行校验后,抛出统一的业务异常。
五、参考资料:
http://benweizhu.github.io/blog/2014/07/19/spring-validation-by-example/
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation
https://reflectoring.io/bean-validation-with-spring-boot/
相关推荐
- perl基础——循环控制_principle循环
-
在编程中,我们往往需要进行不同情况的判断,选择,重复操作。这些时候我们需要对简单语句来添加循环控制变量或者命令。if/unless我们需要在满足特定条件下再执行的语句,可以通过if/unle...
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
-
CHAPTER1TheThreePresentsofD'ArtagnantheElderCHAPTER2TheAntechamber...
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
-
CHAPTER3TheAudienceCHAPTER5TheKing'SMusketeersandtheCardinal'SGuard...
- CHAPTER 3 The Audience 第三章 接见
-
CHAPTER3TheAudienceCHAPTER3TheAudience第三章接见M.DeTrévillewasatt...
- 别搞印象流!数据说明谁才是外线防守第一人!
-
来源:Reddit译者:@assholeeric编辑:伯伦WhoarethebestperimeterdefendersintheNBA?Here'sagraphofStea...
- V-Day commemorations prove anti-China claims hollow
-
People'sLiberationArmyhonorguardstakepartinthemilitaryparademarkingthe80thanniversary...
- EasyPoi使用_easypoi api
-
EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- 【开发者成长】JAVA 线上故障排查完整套路!
-
线上故障主要会包括CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。同时例如jstack、jmap等工具也是不囿于一个方面的问题...
- 使用 Python 向多个地址发送电子邮件
-
在本文中,我们将演示如何使用Python编程语言向使用不同电子邮件地址的不同收件人发送电子邮件。具体来说,我们将向许多不同的人发送电子邮件。使用Python向多个地址发送电子邮件Python...
- 提高工作效率的--Linux常用命令,能够决解95%以上的问题
-
点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...
- linux常用系统命令_linux操作系统常用命令
-
系统信息arch显示机器的处理器架构dmidecode-q显示硬件系统部件-(SMBIOS/DMI)hdparm-i/dev/hda罗列一个磁盘的架构特性hdparm-tT/dev/s...
- 小白入门必知必会-PostgreSQL-15.2源码编译安装
-
一PostgreSQL编译安装1.1下载源码包在PostgreSQL官方主页https://www.postgresql.org/ftp/source/下载区选择所需格式的源码包下载。cd/we...
- Linux操作系统之常用命令_linux系统常用命令详解
-
Linux操作系统一、常用命令1.系统(1)系统信息arch显示机器的处理器架构uname-m显示机器的处理器架构uname-r显示正在使用的内核版本dmidecode-q显示硬件系...
- linux网络命名空间简介_linux 网络相关命令
-
此篇会以例子的方式介绍下linux网络命名空间。此例中会创建两个networknamespace:nsa、nsb,一个网桥bridge0,nsa、nsb中添加网络设备veth,网络设备间...
- 一周热门
- 最近发表
-
- perl基础——循环控制_principle循环
- CHAPTER 2 The Antechamber of M de Treville 第二章 特雷维尔先生的前厅
- CHAPTER 5 The King'S Musketeers and the Cardinal'S Guards 第五章 国王的火枪手和红衣主教的卫士
- CHAPTER 3 The Audience 第三章 接见
- 别搞印象流!数据说明谁才是外线防守第一人!
- V-Day commemorations prove anti-China claims hollow
- EasyPoi使用_easypoi api
- 关于Oracle数据库12c 新特性总结_oracle数据库12514
- 【开发者成长】JAVA 线上故障排查完整套路!
- 使用 Python 向多个地址发送电子邮件
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- opacity 属性 (32)
- transition 属性 (33)