【全网最全】JSR303参数校验与全局异常处理(从理论到实践别用if判断参数了)
一、前言
我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断, 为了安全
。因为前端很容易拜托,当测试使用 PostMan
来测试,如果后端没有校验,不就乱了吗?肯定会有很多异常的。今天小编和大家一起学习一下JSR303专门用于参数校验的,算是一个工具吧!
二、JSR303简介
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
Hibernate官网
官网介绍:
验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据。
Jakarta Bean Validation 2.0 - 为实体和方法验证定义了元数据模型和 API。默认元数据源是注释,能够通过使用 XML 覆盖和扩展元数据。API 不依赖于特定的应用程序层或编程模型。它特别不依赖于 Web 或持久层,并且可用于服务器端应用程序编程以及富客户端 Swing 应用程序开发人员。
三、导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId> </dependency>
四、常用注解
约束注解名称 | 约束注解说明 |
---|---|
@Null | 用于验证对象为null |
@NotNull | 用于对象不能为null,无法查检长度为0的字符串 |
@NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
@NotEmpty | 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来 |
@Size | 用于对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length | 用于String对象的大小必须在指定的范围内 |
@Pattern | 用于String对象是否符合正则表达式的规则 |
用于String对象是否符合邮箱格式 | |
@Min | 用于Number和String对象是否大等于指定的值 |
@Max | 用于Number和String对象是否小等于指定的值 |
@AssertTrue | 用于Boolean对象是否为true |
@AssertFalse | 用于Boolean对象是否为false |
所有的大家参考jar包
五、@Validated、@Valid区别
@Validated:
- Spring提供的
- 支持分组校验
- 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
- 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
@Valid:
- JDK提供的(标准JSR-303规范)
- 不支持分组校验
- 可以用在方法、构造函数、方法参数和成员属性(字段)上
- 可以加在成员属性(字段)上,能够独自完成级联校验
总结: @Validated用到分组时使用,一个学校对象里还有很多个学生对象需要使用@Validated在Controller方法参数前加上,@Valid加在学校中的学生属性上,不加则无法对学生对象里的属性进行校验!
区别参考博客地址
例子:
@Data public class School{@NotBlankprivate String id;private String name;@Valid // 需要加上,否则不会验证student类中的校验注解@NotNull // 且需要触发该字段的验证才会进行嵌套验证。private List<Student> list; }@Data public class Student {@NotBlankprivate String id;private String name;private int age;}@PostMapping("/test") public Result test(@Validated @RequestBody School school){}
六、常用使用测试
1. 实体类添加校验
import lombok.Data;import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import java.io.Serializable;@Data public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@NotNull(message = "修改必须有品牌id")private Long brandId;/*** 品牌名F*/@NotBlank(message = "品牌名必须提交")private String name;/*** 品牌logo地址*/@NotBlank(message = "地址必须不为空")private String logo;/*** 介绍*/private String descript;/*** 检索首字母*///正则表达式@Pattern(regexp = "^[a-zA-Z]$",message = "检索的首字母必须是字母")private String firstLetter;/*** 排序*/@Min(value = 0,message = "排序必须大于等于0")private Integer sort;}
2. 统一返回类型
import com.alibaba.druid.util.StringUtils; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;//统一返回结果 @Data @NoArgsConstructor @AllArgsConstructor @ApiModel public class Result<T> {@ApiModelProperty("响应码")private Integer code;@ApiModelProperty("相应信息")private String msg;@ApiModelProperty("返回对象或者集合")private T data;//成功码public static final Integer SUCCESS_CODE = 200;//成功消息public static final String SUCCESS_MSG = "SUCCESS";//失败public static final Integer ERROR_CODE = 201;public static final String ERROR_MSG = "系统异常,请联系管理员";//没有权限的响应码public static final Integer NO_AUTH_COOD = 999;//执行成功public static <T> Result<T> success(T data){return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);}//执行失败public static <T> Result failed(String msg){msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;return new Result(ERROR_CODE,msg,"");}//传入错误码的方法public static <T> Result failed(int code,String msg){msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;return new Result(code,msg,"");}//传入错误码的数据public static <T> Result failed(int code,String msg,T data){msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;return new Result(code,msg,data);} }
3. 测试类
@PostMapping("/add") public Result add(@Valid @RequestBody BrandEntity brandEntity) {return Result.success("成功"); }
==遇到的坑==:小编在公司的项目中添加没什么问题,但是就是无法触发校验,看到的是 Springboot版本太高了
,所有要添加下面的依赖才触发。
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.18.Final</version> </dependency>
4. 普通测试结果
5. 我们把异常返回给页面
@PostMapping("/add") public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){if (bindingResult.hasErrors()){Map<String,String> map = new HashMap<>();bindingResult.getFieldErrors().forEach(item ->{map.put(item.getField(),item.getDefaultMessage());});return Result.failed(400,"提交的数据不合规范",map);}return Result.success("成功"); }
6. 异常处理结果
{"code": 400,"data": {"name": "品牌名必须提交","logo": "地址必须不为空"},"msg": "提交的数据不合规范" }
七、抽离全局异常处理
1. 心得体会
上面我们要在每个校验的接口上面写,所以我们要抽离出来做个全局异常。并且要改进一下,原来的是把错误信息放到data里,但是正常情况下的data是返回给前端的数据。我们这样把异常数据放进去,会使 data的数据有二义性
。这样对于前端就不知道里面是数据还是报错信息了哈,这样就可以直接前端展示msg里面的提示即可!
2. 书写ExceptionControllerAdvice
import com.wang.test.demo.response.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;@Slf4j @RestControllerAdvice(basePackages = "com.wang.test.demo.controller") public class ExceptionControllerAdvice {@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handleVaildException(MethodArgumentNotValidException e){log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());BindingResult bindingResult = e.getBindingResult();StringBuffer stringBuffer = new StringBuffer();bindingResult.getFieldErrors().forEach(item ->{//获取错误信息String message = item.getDefaultMessage();//获取错误的属性名字String field = item.getField();stringBuffer.append(field + ":" + message + " ");});return Result.failed(400, stringBuffer + "");}@ExceptionHandler(value = Throwable.class)public Result handleException(Throwable throwable){log.error("错误",throwable);return Result.failed(400, "系统异常");} }
3. 测试结果
{"code": 400,"data": "","msg": "logo:地址必须不为空 name:品牌名必须提交 " }
八、分组校验
1. 需求
我们在做校验的时候,通常会遇到一个实体类的添加和修改,他们的校验规则是不同的,所以分组显得尤为重要。他可以帮助我们少建一个冗余的实体类,所以我们必须要会的。
2. 创建分组接口(不需写任何内容)
public interface EditGroup { } public interface AddGroup { }
3. 在需要二义性的字段上添加分组
/*** 品牌id*/ @NotNull(message = "修改必须有品牌id",groups = {EditGroup.class}) @Null(message = "新增不能指定id",groups = {AddGroup.class}) private Long brandId; // 其余属性我们不变
4. 不同Controller添加校验规则
注意:我们要进行分组,所以 @Valid
不能使用了,要使用 @Validated
。相信大家已经看到上面的他俩区别了哈!
@PostMapping("/add") public Result add(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity){return Result.success("成功"); }@PostMapping("/edit") public Result edit(@Validated({EditGroup.class}) @RequestBody BrandEntity brandEntity){return Result.success("成功"); }
5. 测试
九、自定义校验
1.定义自定义校验器
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set;//编写自定义的校验器 public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {private Set<Integer> set=new HashSet<Integer>();//初始化方法@Overridepublic void initialize(ListValue constraintAnnotation) {int[] value = constraintAnnotation.vals();for (int i : value) {set.add(i);}}/*** 判断是否校验成功* @param value 需要校验的值* @param context* @return*/@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {return set.contains(value);} }
2. 定义一个注解配合校验器使用
@Documented @Constraint(validatedBy = { ListValueConstraintValidator.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) public @interface ListValue {// 使用该属性去Validation.properties中取String message() default "{com.atguigu.common.valid.ListValue.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };int[] vals() default {};}
3. 实体类添加一个新的校验属性
==注意==:我们上面做了分组,如果属性不指定分组,则不会生效,现在我们的部分属性校验已没有起作用,现在只有 brandId和showStatus
起作用。
/*** 显示状态[0-不显示;1-显示]*/ @NotNull(groups = {AddGroup.class, EditGroup.class}) @ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "必须为0或者1") private Integer showStatus;
4. 测试
十、总结
这样就差不多对JSR303有了基本了解,满足基本开发没有什么问题哈!看到这里了,收藏点赞一波吧,整理了将近一天!!谢谢大家了!!
【全网最全】JSR303参数校验与全局异常处理(从理论到实践别用if判断参数了)相关推荐
- 【Java】参数校验与统一异常处理
Java参数校验与统一异常处理 [前言]参数校验是接口开发不可或缺的环节,校验参数在以前基本上依靠大量的if/else控制语句来实现,后来可以使用反射+自定义注解的形式进行校验,但是复用性不是很好.其 ...
- 全网最全JavaScript正则表达式( 校验数字和字母)
<script type="text/javascript">function doCheck() { var reg = /^([a-zA-Z0-9]+[_|\_|\ ...
- 使用 Spring Validation 优雅地进行参数校验
引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(UserVO userVO) {if(user ...
- JSR303参数校验
1.Spring mvc 的表单验证 客户端表单验证:代码写在js上,容易被攻击. 服务端表单验证 :不容易被攻击,它是在后台进行验证就不会被人恶意攻击,不容易被人随意登陆其他的信息或者窃取他人信息. ...
- SpringBoot Validation优雅的参数校验
前言:大多数项目中都需要后台对传过来的对象进行校验,所以经常需要写一些字段校验的代码,比如特殊字段非空.字段长度限制和邮箱格式验证等等.之前我们可能都是使用if-else-,写这些与业务逻辑关系不大的 ...
- Spring Validation最佳实践及其实现原理,参数校验没那么简单!
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:六点半起床 juejin.im/post/685654110 ...
- Spring Validation 最佳实践及其实现原理,参数校验没那么简单!
之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation.本文会详细介绍Spring Validation各种场景下 ...
- Spring Boot参数校验以及分组校验的使用
简介:做web开发基本上每个接口都要对参数进行校验,如果参数比较少,还比较容易处理,一但参数比较多了的话代码中就会出现大量的if-else语句.虽然这种方式简单直接,但会大大降低开发效率和代码可读性. ...
- bean validation校验方法参数_Spring Validation最佳实践及其实现原理,参数校验没那么简单!
本文同名博客老炮说Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/实战项目等文章资料 顺便再给大家推荐一套Spring ...
最新文章
- 解决PhpStorm卡顿的问题
- mysql user表添加记录_《MySQL数据操作与查询》- 返校复习课练习题,创建数据库user_system,创建数据表user及user_ext...
- table表格固定前几列,其余的滚动
- 【大数据-Hadoop】dbeaver
- php获取页面的可视内容高度,网页制作技巧:获取页面可视区域的高度_css
- Android 自定义View实现画背景和前景(ViewGroup篇)
- Python 第三方模块之 numpy.linalg - 线性代数
- 127.0.0.1 zxt.php,恭喜您!序列号购买成功!
- 官网Tensorflow 移动开发流程
- 自动驾驶 7-1 Carla 概述 - 自动驾驶汽车模拟Carla Overview - Self-Driving Car Simulation
- 对vue.config.js中的代理服务器的理解
- Verilog语言生成4位伪随机码
- Android 源码编译步骤实录
- 关于我考研的这一年随记 —— 2022暨南大学电子信息计算机技术专业初试第三复试第一
- 【js】:利用javascript打开网页
- 市场调研报告-全球与中国商业虚拟化平台市场现状及未来发展趋势
- 虚拟化、文件系统、查找文件
- 主板风扇转不开机是什么问题_cpu风扇转主板不启动怎么办
- 记录下我磕磕碰碰的三个月找工作经历,offer拿到手软
- 最细RGB颜色表,建议收藏
热门文章
- springboot小区物业管理系统maven idea1562
- 微服务-长轮询异常:SecurityManager accessible to the calling code
- excel 根据单元格内容自动调整列宽
- dig命令的常见用法详解
- oracle入门学习
- Sharding:分表、分库、分片和分区
- 双系统删除Linux引导(MbrFix.exe)
- THINKPHP5获取当前页面URL信息
- 文件改日期 电梯卡dump_求修改电梯卡的日期 及楼层更改成整栋楼都可刷
- 《A diagonal quasi-Newton updating method for unconstrained optimization》文献算法实现