当前位置 : 主页 > 编程语言 > java >

springboot整合JSR303参数校验与全局异常处理

来源:互联网 收集:自由互联 发布时间:2023-02-04
一、前言 我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,​

一、前言

我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,​​为了安全​​。因为前端很容易拜托,当测试使用​​PostMan​​来测试,如果后端没有校验,不就乱了吗?肯定会有很多异常的。今天小编和大家一起学习一下JSR303专门用于参数校验的,算是一个工具吧!

二、JSR303简介

​​JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。​​ ​​Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。​​

​​Hibernate官网​​

官网介绍:

验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据。

springboot整合JSR303参数校验与全局异常处理_java

Jakarta Bean Validation 2.0 - 为实体和方法验证定义了元数据模型和 API。默认元数据源是注释,能够通过使用 XML 覆盖和扩展元数据。API 不依赖于特定的应用程序层或编程模型。它特别不依赖于 Web 或持久层,并且可用于服务器端应用程序编程以及富客户端 Swing 应用程序开发人员。

springboot整合JSR303参数校验与全局异常处理_数据_02

三、导入依赖

<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对象是否符合正则表达式的规则

@Email

用于String对象是否符合邮箱格式

@Min

用于Number和String对象是否大等于指定的值

@Max

用于Number和String对象是否小等于指定的值

@AssertTrue

用于Boolean对象是否为true

@AssertFalse

用于Boolean对象是否为false

所有的大家参考jar包

springboot整合JSR303参数校验与全局异常处理_spring_03

五、@Validated、@Valid区别

@Validated:

  • Spring提供的
  • 支持分组校验
  • 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
  • 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid

@Valid:

  • JDK提供的(标准JSR-303规范)
  • 不支持分组校验
  • 可以用在方法、构造函数、方法参数和成员属性(字段)上
  • 可以加在成员属性(字段)上,能够独自完成级联校验

总结:​​@Validated用到分组时使用,一个学校对象里还有很多个学生对象需要使用@Validated在Controller方法参数前加上,@Valid加在学校中的学生属性上,不加则无法对学生对象里的属性进行校验!​​

例子:

@Datapublic class School{ @NotBlank private String id; private String name; @Valid // 需要加上,否则不会验证student类中的校验注解 @NotNull // 且需要触发该字段的验证才会进行嵌套验证。 private List<Student> list;}@Datapublic 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;@Datapublic 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@ApiModelpublic 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. 普通测试结果

springboot整合JSR303参数校验与全局异常处理_数据_04

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. 测试

springboot整合JSR303参数校验与全局异常处理_java_05

springboot整合JSR303参数校验与全局异常处理_spring_06

九、自定义校验

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 = constraintAnnotation.vals(); for (int i : value) { set.add(i); } } /** * 判断是否校验成功 * @param value 需要校验的值 * @param context * @return */ @Override public 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. 测试

springboot整合JSR303参数校验与全局异常处理_spring_07

springboot整合JSR303参数校验与全局异常处理_spring_08

十、总结

这样就差不多对JSR303有了基本了解,满足基本开发没有什么问题哈!

上一篇:Mysql大数据表处理方案
下一篇:没有了
网友评论