SpringMVC使用hibernate-validator进行参数校验最佳实践记录
作者:kusedexingfu 发布时间:2022-11-12 23:44:04
在我们用Controller接收参数后,往往需要对参数进行校验。如果我们手写校验的话,就会有一堆的判空代码,看起来很不优雅,写起来也费时费力。下面来看下通过hibernate-validator来进行优雅的参数校验。
首先需要引入依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
hibernate-validator注解
hibernate-validator提供了很多注解来让我们进行参数校验:
常用的校验注解如下表所示:
注解 | 说明 |
---|---|
@Null | 被注释的元素必须为null |
@AssertTrue | 被注释的元素必须为true |
@AssertFalse | 被注释的元素必须为false |
@DecimalMin(value=,message=) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value=,message=) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
被注释的元素必须是电子邮箱地址 |
主要区分下@NotNull、@NotEmpty、@NotBlank 3个注解的区别:
(1)@NotNull:任何对象的value不能为null。
(2)@NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null。
(3)@NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0。
需要注意如下几点:
(1)除了@Empty要求字符串不能全是空格,其他的字符串校验都是允许空格的。
(2)message是可以引用常量的,但是如@Size里max不允许引用对象常量,基本类型常量是可以的。message是错误提示信息,是可以返回给前台的。
(3)大部分规则校验都是允许参数为null,即当不存在这个值时,就不进行校验了。
不太常用的校验注解如下表所示:
注解 | 说明 |
---|---|
@Null | 被注释的元素必须为null |
@AssertTrue | 被注释的元素必须为true |
@AssertFalse | 被注释的元素必须为false |
@DecimalMin(value=,message=) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value=,message=) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
被注释的元素必须是电子邮箱地址 |
代码实战
1.、使用BindingResult获取检验结果
我们可以使用BindingResult获取检验结果,构造友好的返回信息
Controller中的代码如下:
@Api(tags = "校验框架")
@RestController
@RequestMapping("/validate")
public class ValidatedController {
@ApiOperation(value = "bindValidate")
@PostMapping("bindValidate")
public ValidatedVO bindValidate(@RequestBody @Validated(value= {ValidatedGroup.DELET.class}) ValidatedVO validatedVO, BindingResult result) {
if (result.hasErrors()) {
StringBuilder message = new StringBuilder("参数校验失败:");
List<ObjectError> errors = result.getAllErrors();
for (ObjectError error : errors) {
message.append(error.getDefaultMessage()).append(".");
}
throw HttpError.error(111, message.toString());
}
return validatedVO;
}
}
我们用以下实体类来作为Controller的参数接收实体
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ValidatedVO {
//更新、删除时不能为空
@NotNull(message = "id不能为空", groups = {ValidatedGroup.UPDATE.class, ValidatedGroup.DELET.class})
private Long id;
//新增、更新时不能为空
@NotBlank(message = "name不能为空", groups = {ValidatedGroup.CREATE.class ,ValidatedGroup.UPDATE.class})
private String name;
//查询时不能为空
@NotBlank(message = "queryParam不能为空", groups = {ValidatedGroup.QUERY.class})
private String queryParam;
}
分组的类:
public class ValidatedGroup {
public interface CREATE{}
public interface DELET{}
public interface UPDATE{}
public interface QUERY{}
}
需要注意的是,@Validated支持分组校验,即校验注解中的groups属性。这个为我们的校验也提供了便利。我们可以在Controller需要校验的参数前用@Validated的value属性来表示需要校验的分组,那么就会只会校验实体对象中拥有相同的分组的属性。这样我们就可以只用一个实体满足不同的场景了。
启动项目后,我们在swagger上请求,传递空的json字符串【{}】,返回结果:
{
"code": 111,
"message": "参数校验失败:id不能为空."
}
然而每次都需要在请求进来时用BindingResult做处理,很不优雅。
2.通过@RestControllerAdvice统一处理参数校验信息
如果我们不用BindingResult获取校验结果,即不做处理,框架就会抛出异常,响应400码,返回一些不友好的错误信息。即用如下代码:
@ApiOperation(value = "bindValidate")
@PostMapping("bindValidate")
public ValidatedVO bindValidate(@RequestBody @Validated(value= {ValidatedGroup.DELET.class}) ValidatedVO validatedVO/*, BindingResult result*/) {
/*if (result.hasErrors()) {
StringBuilder message = new StringBuilder("参数校验失败:");
List<ObjectError> errors = result.getAllErrors();
for (ObjectError error : errors) {
message.append(error.getDefaultMessage()).append(".");
}
throw HttpError.error(111, message.toString());
}*/
return validatedVO;
}
那么我们就可以使用@RestControllerAdvice来对异常进行处理,进行友好信息的提示
如果对@RestControllerAdvice和@ControllerAdvice不了解的,可以去查询学习,这里不进行讲述
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public CommonErrorVO handle(Exception e){
if (e instanceof HttpError) {
HttpError error = (HttpError)e;
return CommonErrorVO.builder().code(error.getCode()).message(error.getMessage()).build();
} else if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
StringBuilder message = new StringBuilder("");
validException.getBindingResult().getAllErrors().forEach(err -> {
message.append(err.getDefaultMessage()).append(".");
});
return CommonErrorVO.builder().code(888).message(message.toString()).build();
} else {
return CommonErrorVO.builder().code(999).message(e.getMessage()).build();
}
}
}
MethodArgumentNotValidException异常即为hibernate-validator校验不通过抛出的异常信息,我们从其中获取到校验失败的信息来进行组装。
如上,我们通过@Validated配合@RestControllerAdvice完成了优雅的参数校验。
为了体现分组校验的便利性,我做了如下的测试,如果每个请求参数都是空的json字符串【{}】的话,那么返回的信息如下。
@Api(tags = "校验框架")
@RestController
@RequestMapping("/validate")
public class ValidatedController {
/**
* @Validated注解表示开启Spring的校验机制,支持分组校验,声明在入参上。
* @param validatedVO
* @return
*/
@ApiOperation(value = "分组校验:QUERY参数校验")//返回 【queryParam不能为空】
@PostMapping("queryValidate")
public ValidatedVO queryValidate(@RequestBody @Validated(value= {ValidatedGroup.QUERY.class}) ValidatedVO validatedVO) {
return validatedVO;
}
@ApiOperation(value = "分组校验:CREATE参数校验")// 返回 【name不能为空】
@PostMapping("createValidate")
public ValidatedVO createValidate(@RequestBody @Validated(value= {ValidatedGroup.CREATE.class}) ValidatedVO validatedVO) {
return validatedVO;
}
@ApiOperation(value = "分组校验:UPDATE参数校验") // 返回【id不能为空.name不能为空.】
@PostMapping("updateValidate")
public ValidatedVO updateValidate(@RequestBody @Validated(value= {ValidatedGroup.UPDATE.class}) ValidatedVO validatedVO) {
return validatedVO;
}
@ApiOperation(value = "分组校验:DELETE参数校验") //返回 【id不能为空】
@PostMapping("deleteValidate")
public ValidatedVO deleteValidate(@RequestBody @Validated(value= {ValidatedGroup.DELET.class}) ValidatedVO validatedVO) {
return validatedVO;
}
}
来源:https://blog.csdn.net/kusedexingfu/article/details/116676271


猜你喜欢
- 在前面的文章中,我们分析了淘宝android客户端的一些界面实现和用户体验,今天这篇文章,主要介绍如何使用自定义控件,实现抢购倒计时的功能。
- 效果图如下所述:布局<?xml version="1.0" encoding="utf-8"?
- 前言本文讲解了在Spring 应用中创建Bean的多种方式,包括自动创建,以及手动创建注入方式,实际开发中可以根据业务场景选择合适的方案。方
- mapper.xml文件<?xml version="1.0" encoding="UTF-8"
- 在eclipse中默认的maven,它加载的是国外的镜像,那样速度会比较慢,如果使用国内镜像,比如阿里的中央仓库;速度会快很多。那如何修改m
- 智能终端设备的多点触控操作为我们带来了种种炫酷体验,这也使得很多A
- 1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,
- 在2020.1.1版本之前IDEA pom文件导包是这样的最近新装新版本IDEA之后,这个图标没有了,对于习惯旧操作没有图标了还真不习惯。就
- System.getProperty()的作用及使用最近在看一些代码时,很多地方都用到了System.getProperty()、Syste
- Ribbon是Spring Cloud Netflix全家桶中负责负载均衡的组件,它是一组类库的集合。通过Ribbon,程序员能在不涉及到具
- 看过阿里巴巴开发手册的同学应该都会对Integer临界值127有点印象。原文中写的是:【强制】所有整型包装类对象之间值的比较,全部使用 eq
- JVM内部结构图Java虚拟机主要分为五个区域:方法区、堆、Java栈、PC寄存器、本地方法栈。下面来看一些关于JVM结构的重要问题。1.哪
- 在 C# 中反射技术应用广泛,至于什么是反射.........你如果不了解的话,请看下段说明,否则请跳过下段。广告一下:喜欢我文章的朋友请关
- Java源码系列三-工具类Arrays今天分享java的源码的第三弹,Arrays这个工具类的源码。因为近期在复习数据结构,了解到Array
- 本文实例讲述了C#移除所有事件绑定的方法。分享给大家供大家参考。具体分析如下:private delegate int DEL_TEST_E
- 前提前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景。为了模拟真实的交互场景,先定制一下整个交互流程。
- 正常状态是UP,跳闸是⼀种状态CIRCUIT_OPEN,可以通过/health查看,前提是工程中需要引入SpringBoot的actuato
- 前言该篇介绍的内容如题,就是利用redis实现接口的限流( 某时间范围内 最大的访问次数 ) 。正文 惯例,
- 传感器简单的介绍一下传感器: 就是设备用来感知周边环境变化的硬件。Android中的传感器包含在传感器框架中,属于android.hardw
- JNA(Java Native Access):建立在JNI之上的Java开源框架,SUN主导开发,用来调用C、C++代码,尤其是底层库文件