知是行的主意,行是知的功夫。这篇文章主要讲述全网最全JSR303参数校验与全局异常处理(从理论到实践别用if判断参数了)相关的知识,希望能为你提供帮助。
@[toc]
一、前言我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,为了安全
。因为前端很容易拜托,当测试使用PostMan
来测试,如果后端没有校验,不就乱了吗?肯定会有很多异常的。今天小编和大家一起学习一下JSR303专门用于参数校验的,算是一个工具吧!
二、JSR303简介JSR-303 是 java EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
Hibernate官网
官网介绍:
验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据。
文章图片
【全网最全JSR303参数校验与全局异常处理(从理论到实践别用if判断参数了)】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 |
文章图片
五、@Validated、@Valid区别@Validated:
- Spring提供的
- 支持分组校验
- 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
- 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
- JDK提供的(标准JSR-303规范)
- 不支持分组校验
- 可以用在方法、构造函数、方法参数和成员属性(字段)上
- 可以加在成员属性(字段)上,能够独自完成级联校验
@Validated用到分组时使用,一个学校对象里还有很多个学生对象需要使用@Validated在Controller方法参数前加上,@Valid加在学校中的学生属性上,不加则无法对学生对象里的属性进行校验!
例子:
@Data
public class School@NotBlank
private String id;
private String name;
@Valid// 需要加上,否则不会验证student类中的校验注解
@NotNull// 且需要触发该字段的验证才会进行嵌套验证。
private List<
Student>
list;
@Data
public class Student @NotBlank
private 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 = https://www.songbingjia.com/android/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 = https://www.songbingjia.com/android/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 = https://www.songbingjia.com/android/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>
();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation)
int[] value = https://www.songbingjia.com/android/constraintAnnotation.vals();
for (int i : value)
set.add(i);
/**
* 判断是否校验成功
* @param value需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context)
returnset.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有了基本了解,满足基本开发没有什么问题哈!看到这里了,收藏点赞一波吧,整理了将近一天!!谢谢大家了!!
< hr>
有缘人才能看到,自己网站,欢迎访问!!!
点击访问!欢迎访问,里面也是有很多好的文章哦!
推荐阅读
- Transforme结构(位置编码 | Transformer Architecture: The Positional Encoding)
- 一款超牛逼的 Linux 终端复用神器(附安装使用教程)
- 字节跳动算法工程师面试总结,头条抖音后端技术3面题(Linux)
- 实战案例 : Tomcat9 利用memcached1.6实现会话Cluster (同一主机no sticky 模式)
- 001如何访问Linux系统
- 002UNIX/Linux特征简介
- 这次我会结合我前面所发的搭建DHCP服务,dns服务和HTTP服务,中核做一个实验有什么不懂得,可以评论区交流一下,也可以私信我,葱鸭!
- Swift进阶黄金之路
- centos7 安装psutil