【全网最全】springboot整合JSR303参数校验与全局异常处理(springboot参数校验异常拦截)

  本篇文章为你整理了【全网最全】springboot整合JSR303参数校验与全局异常处理(springboot参数校验异常拦截)的详细内容,包含有spring jsr303 springboot参数校验异常拦截 springboot参数校验不生效 jsr303数据校验 【全网最全】springboot整合JSR303参数校验与全局异常处理,希望能帮助你了解 【全网最全】springboot整合JSR303参数校验与全局异常处理。

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

  

 

  四、常用注解

  
所有的大家参考jar包
 

  五、@Validated、@Valid区别

  @Validated:

  Spring提供的

  支持分组校验

  可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

  由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid

  @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 = 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

   //初始化方法

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

  
 

  这样就差不多对JSR303有了基本了解,满足基本开发没有什么问题哈!看到这里了,收藏点赞一波吧,整理了将近一天!!谢谢大家了!!

  欢迎大家关注小编的微信公众号!!
 

  有缘人才能看到,自己网站,欢迎访问!!!

  点击访问!欢迎访问,里面也是有很多好的文章哦!

  以上就是【全网最全】springboot整合JSR303参数校验与全局异常处理(springboot参数校验异常拦截)的详细内容,想要了解更多 【全网最全】springboot整合JSR303参数校验与全局异常处理的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: