Spring Boot参数校验以及分组校验的使用
简介: 做web开发基本上每个接口都要对参数进行校验,如果参数比较少,还比较容易处理,一但参数比较多了的话代码中就会出现大量的if-else语句。虽然这种方式简单直接,但会大大降低开发效率和代码可读性。所以我们可以使用validator组件来代替我们进行不必要的coding操作。本文将基于validator的介绍资料,同时结合作者自己在项目中的实际使用经验进行了总结。

作者 | 江岩
来源 | 阿里技术公众号
一 前言
做web开发有一点很烦人就是要对前端输入参数进行校验,基本上每个接口都要对参数进行校验,比如一些非空校验、格式校验等。如果参数比较少的话还是容易处理的一但参数比较多了的话代码中就会出现大量的if-else语句。
使用这种方式虽然简单直接,但是也有不好的地方,一是降低了开发效率,因为我们需要校验的参数会存在很多地方,并且不同地方会有重复校验,其次降低了代码可读性,因为在业务代码中掺杂了太多额外工作的代码。
所以我们可以使用validator组件来代替我们进行不必要的coding操作。本文基于validator的介绍资料,也结合自己在项目中的实际使用经验进行了总结,希望能帮到大家。
1 什么是validator
Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本 。需要注意的是,JSR只是一项标准,它规定了一些校验注解的规范,但没有实现,比如@Null、@NotNull、@Pattern等,它们位于 javax.validation.constraints这个包下。而hibernate validator是对这个规范的实现,并增加了一些其他校验注解,如 @NotBlank、@NotEmpty、@Length等,它们位于org.hibernate.validator.constraints这个包下。
如果我们的项目使用了Spring Boot,hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,需要添加如下依赖。

二 注解介绍
1 validator内置注解

hibernate validator中扩展定义了如下注解:

三 使用
使用起来比较简单,都是使用注解方式使用。具体来说分为单参数校验、对象参数校验,单参数校验就是controller接口按照单参数接收前端传值,没有封装对象进行接收,如果有封装对象那就是对象参数校验。
1 单参数校验
单参数校验只需要在参数前添加注解即可,如下所示:
public Result deleteUser(@NotNull(message = "id不能为空") Long id) {
// do something
}
但有一点需要注意,如果使用单参数校验,controller类上必须添加@Validated注解,如下所示:
@RestController
@RequestMapping("/user")
@Validated // 单参数校验需要加的注解
public class UserController {
// do something
}
2 对象参数校验
对象参数校验使用时,需要先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Validated 注解,如下所示:
public Result addUser(@Validated UserAO userAo) {
// do something
}
public class UserAO {
@NotBlank
private String name;
@NotNull
private Integer age;
……
}
注解分组
在对象参数校验场景下,有一种特殊场景,同一个参数对象在不同的场景下有不同的校验规则。比如,在创建对象时不需要传入id字段(id字段是主键,由系统生成,不由用户指定),但是在修改对象时就必须要传入id字段。在这样的场景下就需要对注解进行分组。
1)组件有个默认分组Default.class, 所以我们可以再创建一个分组UpdateAction.class,如下所示:
public interface UpdateAction {
}
2)在参数类中需要校验的属性上,在注解中添加groups属性:
public class UserAO {
@NotNull(groups = UpdateAction.class, message = "id不能为空")
private Long id;
@NotBlank
private String name;
@NotNull
private Integer age;
……
}
如上所示,就表示只在UpdateAction分组下校验id字段,在默认情况下就会校验name字段和age字段。
然后在controller的方法中,在@Validated注解里指定哪种场景即可,没有指定就代表采用Default.class,采用其他分组就需要显示指定。如下代码便表示在addUser()接口中按照默认情况进行参数校验,在updateUser()接口中按照默认情况和UpdateAction分组对参数进行共同校验。
public Result addUser(@Validated UserAO userAo) {
// do something
}
public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserAO userAo) {
// do something
}
对象嵌套
如果需要校验的参数对象中还嵌套有一个对象属性,而该嵌套的对象属性也需要校验,那么就需要在该对象属性上增加@Valid注解。
public class UserAO {
@NotNull(groups = UpdateAction.class, message = "id不能为空")
private Long id;
@NotBlank
private String name;
@NotNull
private Integer age;
@Valid
private Phone phone;
……
}
public class Phone {
@NotBlank
private String operatorType;
@NotBlank
private String phoneNum;
}
3 错误消息的捕获
参数校验失败后会抛出异常,我们只需要在全局异常处理类中捕获参数校验的失败异常,然后将错误消息添加到返回值中即可。捕获异常的方法如下所示,返回值Result是我们系统自定义的返回值类。
@RestControllerAdvice(basePackages= {"com.alibaba.dc.controller","com.alibaba.dc.service"})
public class GlobalExceptionHandler {
@ExceptionHandler(value = {Throwable.class})
Result handleException(Throwable e, HttpServletRequest request){
// 异常处理
}
}
需要注意的是,如果缺少参数抛出的异常是MissingServletRequestParameterException,单参数校验失败后抛出的异常是ConstraintViolationException,get请求的对象参数校验失败后抛出的异常是BindException,post请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException,不同异常对象的结构不同,对异常消息的提取方式也就不同。如下图所示:
1)MissingServletRequestParameterException
if(e instanceof MissingServletRequestParameterException){
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
String msg = MessageFormat.format("缺少参数{0}", ((MissingServletRequestParameterException) e).getParameterName());
result.setMessage(msg);
return result;
}
2)ConstraintViolationException异常
if(e instanceof ConstraintViolationException){
// 单个参数校验异常
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
Set<ConstraintViolation<?>> sets = ((ConstraintViolationException) e).getConstraintViolations();
if(CollectionUtils.isNotEmpty(sets)){
StringBuilder sb = new StringBuilder();
sets.forEach(error -> {
if (error instanceof FieldError) {
sb.append(((FieldError)error).getField()).append(":");
}
sb.append(error.getMessage()).append(";");
});
String msg = sb.toString();
msg = StringUtils.substring(msg, 0, msg.length() -1);
result.setMessage(msg);
}
return result;
}
3)BindException异常
if (e instanceof BindException){
// get请求的对象参数校验异常
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
List<ObjectError> errors = ((BindException) e).getBindingResult().getAllErrors();
String msg = getValidExceptionMsg(errors);
if (StringUtils.isNotBlank(msg)){
result.setMessage(msg);
}
return result;
}
private String getValidExceptionMsg(List<ObjectError> errors) {
if(CollectionUtils.isNotEmpty(errors)){
StringBuilder sb = new StringBuilder();
errors.forEach(error -> {
if (error instanceof FieldError) {
sb.append(((FieldError)error).getField()).append(":");
}
sb.append(error.getDefaultMessage()).append(";");
});
String msg = sb.toString();
msg = StringUtils.substring(msg, 0, msg.length() -1);
return msg;
}
return null;
}
4)MethodArgumentNotValidException异常
if (e instanceof MethodArgumentNotValidException){
// post请求的对象参数校验异常
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
List<ObjectError> errors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
String msg = getValidExceptionMsg(errors);
if (StringUtils.isNotBlank(msg)){
result.setMessage(msg);
}
return result;
}
原文链接
本文为阿里云原创内容,未经允许不得转载。
Spring Boot参数校验以及分组校验的使用的更多相关文章
- spring boot 参数传递(spring boot 参数传数 arg0 每一个参数 arg0#{arg0},arg1 #{arg1})
spring boot 参数传数 arg0 每一个参数 arg0#{arg0},arg1 #{arg1} @Select("select * from sys_user where nam ...
- Spring Boot 参数校验
1.背景介绍 开发过程中,后台的参数校验是必不可少的,所以经常会看到类似下面这样的代码 这样写并没有什么错,还挺工整的,只是看起来不是很优雅而已. 接下来,用Validation来改写这段 2.Spr ...
- Validated 注解完成 Spring Boot 参数校验
1. @Valid 和 @Validated @Valid 注解,是 Bean Validation 所定义,可以添加在普通方法.构造方法.方法参数.方法返回.成员变量上,表示它们需要进行约束校验. ...
- Spring Boot参数校验
1. 概述 作为接口服务提供方,非常有必要在项目中加入参数校验,比如字段非空,字段长度限制,邮箱格式验证等等,数据校验常用到概念:JSR303/JSR-349: JSR303是一项标准,只提供规范不提 ...
- spring boot+自定义 AOP 实现全局校验
最近公司重构项目,重构为最热的微服务框架 spring boot, 重构的时候遇到几个可以统一处理的问题,也是项目中经常遇到,列如:统一校验参数,统一捕获异常... 仅凭代码 去控制参数的校验,有时候 ...
- spring boot参数验证
必须要知道 简述 JSR303/JSR-349,hibernate validation,spring validation 之间的关系 JSR303 是一项标准,JSR-349 是其的升级版本,添加 ...
- Spring Boot 进行优雅的字段校验
Controller层 VS Service层 一般推荐与业务无关的放在Controller层中进行校验,而与业务有关的放在Service层中进行校验. 那么如何将参数校验写的优雅美观呢,如果都是if ...
- 记一次spring boot参数初始化的问题
背景:接手一个项目,看到一个配置参数的引用: @Value("${webSocket.id}") 再看看配置application.yml: ... webSocket: id: ...
- Spring boot 参数相关注解
最近使用swagger的在线文档调试接口时发现老是报参数问题,最后发现是方法中参数上的注解有问题,今天把填的坑做一下总结. 1. RequestParam 该注解有两个属性: name/value:表 ...
- spring boot输入数据校验(validation)
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
随机推荐
- Java 学习分享
建议语言入门可以先看看视频,学习网站可以是github,极客时间,infoQ等,然后去看书深入研究学习.学习最有效的方式一定是自己动手写代码,而不是看别人的代码,自己下载Intelli Idea多敲敲 ...
- Linux SVN 拉取代码报错 svn: E210007: Unable to connect to a repository at URL
原因:Linux缺少组件,导致无法支持 SVN协议 解决办法 yum install -y cyrus-sasl cyrus-sasl-plain cyrus-sasl-ldap
- 智慧党建3D可视化方案,扩大党建文化宣传数字网络影响力
信息技术的快速发展加快了社会分化解构,重构了人际传播渠道.随着党员中网民."数字原住民"比重持续攀升,党员工作生活信息化.网络化.数据化持续加深,传统的党建方式对党员,特别是年轻一 ...
- RecyclerView瀑布流优化方案探讨
目录介绍 01.规则瀑布流实现 02.不规则瀑布流实现 2.1 实现方式 2.2 遇到问题 03.瀑布流上拉加载 04.给瀑布流设置分割线 05.自定义Manager崩溃 06.如何避免刷新抖动 07 ...
- 用Vue3.0 写过组件吗?如果想实现一个 Modal你会怎么设计?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.组件设计 组件就是把图形.非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式 现在有一个场景,点击新增与编辑都弹框出来进行 ...
- 工作记录:TypeScript从入门到项目实战(进阶篇)
内置对象的使用 JavaScript中有许多内置对象,可以直接在JavaScript程序中使用,同样的,TypeScript也延续了这些内置对象 全局的对象( global objects )或称标准 ...
- KALI 安装搜狗输入法
原文网址 https://www.cnblogs.com/liuxingbusi/p/9277127.html ------------------------------------------ 一 ...
- elementui树形表格分页
效果图 如果你刚好需求中需要如上效果那么只需要吧代码复制过去直接用即可,注意写在nextTick中 前提是vue加elementui 代码如下 /** * 树形表格分页 * @param ...
- @Value static静态变量注入
@Component public class Config { @Value("${config1}") private static String config1; } 使用上 ...
- UE427-C++实现摄像机视角的移动,类似开镜效果
教程 方法 调整相机视野和弹簧臂的长度 //自带的tick函数内 需要使用DeltaTime if (bZoomIn) { ZoomFactor += DeltaTime / 0.5f; } else ...