前言

我们都知道在平时写controller时候,都需要对请求参数进行后端校验,一般我们可能会这样写

public String add(UserVO userVO) {
if(userVO.getAge() == null){
return "年龄不能为空";
}
if(userVO.getAge() > 120){
return "年龄不能超过120";
}
if(userVO.getName().isEmpty()){
return "用户名不能为空";
}
// 省略一堆参数校验...
return "OK";
}

业务代码还没开始写呢,光参数校验就写了一堆判断。这样写虽然没什么错,但是给人的感觉就是:不优雅,不专业,代码可读性也很差,一看就是新手写的代码

作为久经战争的老司机怎么能这样呢,大神是不允许这样代码出现的,其实SpringBoot提供整合了参数校验解决方案spring-boot-starter-validation

整合使用

在SpringBootv2.3之前的版本只需要引入 web 依赖就可以了他包含了validation校验包在此之后SpringBoot版本就独立出来了需要单独引入依赖

<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

内置的校验注解有很多,罗列如下:

注解 校验功能
@AssertFalse 必须是false
@AssertTrue 必须是true
@DecimalMax 小于等于给定的值
@DecimalMin 大于等于给定的值
@Digits 可设定最大整数位数和最大小数位数
@Email 校验是否符合Email格式
@Future 必须是将来的时间
@FutureOrPresent 当前或将来时间
@Max 最大值
@Min 最小值
@Negative 负数(不包括0)
@NegativeOrZero 负数或0
@NotBlank 不为null并且包含至少一个非空白字符
@NotEmpty 不为null并且不为空
@NotNull 不为null
@Null 为null
@Past 必须是过去的时间
@PastOrPresent 必须是过去的时间,包含现在
@Pattern 必须满足正则表达式
@PositiveOrZero 正数或0
@Size 校验容器的元素个数

单个参数校验

使用很简单只需要在需要校验controller上加上@Validated注解在需校验参数上加上@NotNull,@NotEmpty之类参数校验注解就行了,

@Validated
@GetMapping("/home")
public class ProductController {
public Result index(@NotBlank String name, @Email @NotBlank String email) {
return ResultResponse.success();
}
}

对象参数校验

在上面的基础上只需要在对象参数前面加上@Validated注解,然后在需要校验的对象参数的属性上面加上

@NotNull,@NotEmpty之类参数校验注解就行了,

 @PostMapping("/user")
public Result index1(@Validated @RequestBody UserParams userParams) {
log.info("info test######");
log.error("error test #####");
return ResultResponse.success(userParams);
}
@Data
public class UserParams { @NotBlank
private String username;
private int age;
@NotBlank
private String addr;
@Email
private String email; }

参数校验异常信息处理

上面我们进行了参数校验,默认当参数校验没通过后会通过异常方式来抛出错误信息MethodArgumentNotValidException是在校验时抛出的还包括很多其他异常信息,这时我们可以通过全局异常捕获信息来处理这些参数校验异常

全局异常处理类只需要在类上标注@RestControllerAdvice,并在处理相应异常的方法上使用@ExceptionHandler注解,写明处理哪个异常即可

package cn.soboys.core;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.soboys.core.authentication.AuthenticationException;
import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultCode;
import cn.soboys.core.ret.ResultResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException; import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; /**
* @author kenx
* @version 1.0
* @date 2021/6/17 20:19
* 全局异常统一处理
*/
@RestControllerAdvice
public class GlobalExceptionHandler { /**
* 处理 json 请求体调用接口对象参数校验失败抛出的异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result jsonParamsException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
List errorList = CollectionUtil.newArrayList(); for (FieldError fieldError : bindingResult.getFieldErrors()) {
String msg = String.format("%s%s;", fieldError.getField(), fieldError.getDefaultMessage());
errorList.add(msg);
}
return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, errorList);
} /**
* 处理单个参数校验失败抛出的异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result ParamsException(ConstraintViolationException e) { List errorList = CollectionUtil.newArrayList();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
StringBuilder message = new StringBuilder();
Path path = violation.getPropertyPath();
String[] pathArr = StrUtil.splitToArray(path.toString(), ".");
String msg = message.append(pathArr[1]).append(violation.getMessage()).toString();
errorList.add(msg);
}
return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, errorList);
} /**
* @param e
* @return 处理 form data方式调用接口对象参数校验失败抛出的异常
*/
@ExceptionHandler(BindException.class)
public Result formDaraParamsException(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getField() + o.getDefaultMessage())
.collect(Collectors.toList());
return ResultResponse.failure(ResultCode.PARAMS_IS_INVALID, collect);
} /**
* 请求方法不被允许异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return ResultResponse.failure(ResultCode.METHOD_NOT_ALLOWED);
} /**
* @param e
* @return Content-Type/Accept 异常
* application/json
* application/x-www-form-urlencoded
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Result httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
return ResultResponse.failure(ResultCode.BAD_REQUEST);
} /**
* handlerMapping 接口不存在跑出异常
*
* @param e
* @return
*/
@ExceptionHandler(NoHandlerFoundException.class)
public Result noHandlerFoundException(NoHandlerFoundException e) {
return ResultResponse.failure(ResultCode.NOT_FOUND, e.getMessage());
} /**
* 认证异常
* @param e
* @return
*/
@ExceptionHandler(AuthenticationException.class)
public Result UnNoException(AuthenticationException e) {
return ResultResponse.failure(ResultCode.UNAUTHORIZED,e.getMessage());
} /**
*
* @param e 未知异常捕获
* @return
*/
@ExceptionHandler(Exception.class)
public Result UnNoException(Exception e) {
return ResultResponse.failure(ResultCode.INTERNAL_SERVER_ERROR, e.getMessage());
}
}

这里关于全局异常处理请参考我这篇SpringBoot优雅的全局异常处理

这里我返回的是自定义响应体api 请参考我这篇Spring Boot 无侵入式 实现RESTful API接口统一JSON格式返回

当然校验异常的处理还有其它方式,当参数在没有通过校验情况下会帮我们把错误信息注入到BindingResult对象中,会注入到对应controller方法,这个仅限于参数有@RequestBody或者@RequestParam修饰的参数才行

public String add1(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if(!fieldErrors.isEmpty()){
return fieldErrors.get(0).getDefaultMessage();
}
return "OK";
}

当然我推荐第一种可以通过全局异常处理的方式统一处理校验异常

如果每个Controller方法中都写一遍对BindingResult信息的处理,使用起来还是很繁琐。代码很冗余

当我们写了@validated注解,不写BindingResult的时候,SpringBoot 就会抛出异常。由此,可以写一个全局异常处理类来统一处理这种校验异常,从而免去重复组织异常信息的代码。

关注公众号猿人生获取更多干货分享

SpringBoot Validation优雅的全局参数校验的更多相关文章

  1. SpringBoot实现通用的接口参数校验

    本文介绍基于Spring Boot和JDK8编写一个AOP,结合自定义注解实现通用的接口参数校验. 缘由 目前参数校验常用的方法是在实体类上添加注解,但对于不同的方法,所应用的校验规则也是不一样的,例 ...

  2. 如何优雅的做参数校验-JSR330

    前言: 本文不是讲@Validate.@Valid是如何使用的.区别是什么,想看这些内容的请换篇文章. 背景: 当前端传过来的参数是进行对称性加密.base64加密等处理后过的参数时,在control ...

  3. springboot @valid与@validated的参数校验使用总结

    好久没在这平台写博客了,最近整理了这东西,先给出总结 // @Valid只能用在controller,@Validated可以用在其他被spring管理的类上 // @Valid可以加在成员变量上(本 ...

  4. 使用Spring Validation优雅地校验参数

    写得好的没我写得全,写得全的没我写得好 引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(Use ...

  5. SpringBoot 参数校验的方法

    Introduction 有参数传递的地方都少不了参数校验.在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全.试想一下,如果在controller层中没有经过任何校验的参数通过s ...

  6. SpringBoot Validation参数校验 详解自定义注解规则和分组校验

    前言 Hibernate Validator 是 Bean Validation 的参考实现 .Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的 ...

  7. 全局异常处理及参数校验-SpringBoot 2.7 实战基础 (建议收藏)

    优雅哥 SpringBoot 2.7 实战基础 - 08 - 全局异常处理及参数校验 前后端分离开发非常普遍,后端处理业务,为前端提供接口.服务中总会出现很多运行时异常和业务异常,本文主要讲解在 Sp ...

  8. SpringBoot接口 - 如何优雅的对参数进行校验?

    在以SpringBoot开发Restful接口时, 对于接口的查询参数后台也是要进行校验的,同时还需要给出校验的返回信息放到上文我们统一封装的结构中.那么如何优雅的进行参数的统一校验呢? @pdai ...

  9. 【全网最全】springboot整合JSR303参数校验与全局异常处理

    一.前言 我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,为了安全.因为前端很容易拜托,当测试使 ...

随机推荐

  1. jQurey判断下一项是否为指定项、下一项是否有指定项

    jQurey判断下一项是否为指定项.下一项是否有指定项 此例子中,如果某个列表项没有二级列表,那么去掉它的展开.收起按钮.就是前边那个减号. 此时我们需要判断VOC综合治理技术这一项是否含有二级菜单, ...

  2. Java集合详解(五):Hashtable原理解析

    概述 本文是基于jdk8_271版本进行分析的. Hashtable与HashMap一样,是一个存储key-value的双列集合.底层是基于数组+链表实现的,没有红黑树结构.Hashtable默认初始 ...

  3. [bug] springboot 静态资源 layui.css 404

    目录结构 引用路径 <link rel="stylesheet" href="../static/layui/css/layui.css" type=&q ...

  4. 【SecureCRT配置】修改默认卷屏行数当做一个操作,屏幕输出有上百行,当需要将屏幕回翻时,这个设置会有很大帮助,默认为500行,可以改为10000行,不用担心找不到了。 选项 => 全局选项 => Default Session => Edit Default Settings => Terminal => Emulation => Scrollback 修改为32000。

    SecureCRT配置屏幕内容输出到log文件 SecureCRT看不到前几分钟操作的内容,或者想把通过vi命令查看的日志输出到log文件(在懒得下载日志文件的情况下),所以接下来就这样操作: 文件保 ...

  5. http协议工作原理及工作流程

    什么是url ? url = 协议 + 域名 + 资源路径 比如: https://www.baidu.com/index.html http : 超文本传输协议 https: 安全套接字协议 HTT ...

  6. 7.10-11 visudo、sudo

    7.10 visudo:编辑 sudoers文件     visudo命令是专门用来编辑/etc/sudoers这个文件的,同时提供语法检查等功能./etc/sudoers文件是sudo命令的配置文件 ...

  7. 使用Tomcat插件控制台中文乱码解决方案(IDEA)(Day_51)

    解决方案 1. File -> Settings... 2. 搜索 Runner (运行程序),在 'VM options:' 中添加:-Dfile.encoding=GB2312 注:GB23 ...

  8. 在react中使用redux并实现计数器案例

    React + Redux 在recat中不使用redux 时遇到的问题 在react中组件通信的数据是单向的,顶层组件可以通过props属性向下层组件传递数据,而下层组件不能向上层组件传递数据,要实 ...

  9. GO语言的JSON01---序列化

    package main import ( "encoding/json" "fmt" ) /* 定义待序列化结构体 属性一定要可见,否则json包无法访问 * ...

  10. 基础BaseController

    1.依赖状态枚举 /** * status enum */ public enum Status { SUCCESS(0, "success", "成功"), ...