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

一、前言

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

二、JSR303简介

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

Hibernate官网

官网介绍:

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

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

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

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

三、导入依赖


   org.springframework.boot
    spring-boot-starter-validation

四、常用注解

约束注解名称 约束注解说明

@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参数校验与全局异常处理

五、@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 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 {
    @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  Result success(T data){
        return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
    }
    //执行失败
    public static  Result failed(String msg){
        msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
        return new Result(ERROR_CODE,msg,"");
    }
    //传入错误码的方法
    public static  Result failed(int code,String msg){
        msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
        return new Result(code,msg,"");
    }
    //传入错误码的数据
    public static  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&#x7248;&#x672C;&#x592A;&#x9AD8;&#x4E86;,所有要添加下面的依赖才触发。


    org.hibernate.validator
    hibernate-validator
    6.0.18.Final

4. 普通测试结果

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

5. 我们把异常返回给页面

@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){

    if (bindingResult.hasErrors()){
        Map 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&#x7684;&#x6570;&#x636E;&#x6709;&#x4E8C;&#x4E49;&#x6027;。这样对于前端就不知道里面是数据还是报错信息了哈,这样就可以直接前端展示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参数校验与全局异常处理
【全网最全】springboot整合JSR303参数校验与全局异常处理

九、自定义校验

1.定义自定义校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

//编写自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator {

    private Set set=new HashSet();

    //初始化方法
    @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[] payload() default { };

    int[] vals() default {};

}

3. 实体类添加一个新的校验属性

注意:我们上面做了分组,如果属性不指定分组,则不会生效,现在我们的部分属性校验已没有起作用,现在只有 brandId&#x548C;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参数校验与全局异常处理
【全网最全】springboot整合JSR303参数校验与全局异常处理

十、总结

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

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

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

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

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

Original: https://www.cnblogs.com/wang1221/p/16717949.html
Author: 小王写博客
Title: 【全网最全】springboot整合JSR303参数校验与全局异常处理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/615399/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Python Json使用

    Python Json使用 本篇主要介绍一下 python 中 json的使用 如何把 dict转成json 、object 转成json 、以及json转成对象 等等。。 jso…

    Java 2023年6月9日
    073
  • Java 元注解

    学习地址:https://blog.csdn.net/sw5131899/article/details/54947192 java注解使用是相当频繁,特别是在搭建一些框架时,用到…

    Java 2023年6月5日
    088
  • 数据库连接查询总结

    建表SQL create table account ( account_id bigint PRIMARY KEY AUTO_INCREMENT, name varchar(64…

    Java 2023年6月6日
    067
  • Spring PathMatchingResourcePatternResolver

    Spring PathMatchingResourcePatternResolver PathMatchingResourcePatternResolver是ResourcePat…

    Java 2023年6月7日
    080
  • JAVA复习总体大纲

    1 java基础. [1].变量— 数据类型 变量名=值;数据类型:1.基本数据类型. byte[1字节] short[2字节] int[4字节] long[8字节] …

    Java 2023年6月5日
    067
  • java 初始化

    这里的主要内容是 &#x521D;&#x59CB;&#x5316;相关的内容,其中还会穿插其他的内容 构造器初始化 静态数据初始化 显示的静态初始化 非静态…

    Java 2023年6月5日
    093
  • 大厂钟爱的全链路压测有什么意义?四种压测方案详细对比分析

    全链路压测? 基于实际的生产业务场景和系统环境,模拟海量的用户请求和数据,对整个业务链路进行各种场景的测试验证,持续发现并进行瓶颈调优,保障系统稳定性的一个技术工程。 针对业务场景…

    Java 2023年6月15日
    086
  • Dokcer运行Nacos容器自动退出问题

    Dokcer运行Nacos容器自动退出问题 参考博文 学生党,租的云服务器,2核2G。使用Docker运行Nacos容器的时候发现总是自动退出。Nacos日志里面没有明显的报错信息…

    Java 2023年6月8日
    068
  • Java 8 并发性基础

    https://www.ibm.com/developerworks/cn/java/j-jvmc2/index.html http://www.nurkiewicz.com/20…

    Java 2023年5月29日
    085
  • Springboot中spring-data-jpa实现拦截器

    以下是jpa 拦截器的配置。 第一步: 实现 hibernate接口。重写方法 第二步: 在yml中配置 第三步:结果 java;gutter:true;2019-06-19 17…

    Java 2023年5月30日
    078
  • Spring-Cloud-Config学习笔记(一):使用本地存储

    Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server,您可以为所有环境中的应用程序管理其外部属性。它非常适合spri…

    Java 2023年5月30日
    072
  • SpringBoot注解@NotNull,@NotBlank,@Valid自动判定空值

    搭建springboot项目,我们都是采用的Restful接口,那么问题来了,当前端调用接口或者是其他项目调用时,我们不能单一靠调用方来控制参数的准确性,自己也要对一些非空的值进行…

    Java 2023年6月5日
    070
  • Spring Boot 打包方式的选择

    新建Spring Boot后,会自带打包方式,现在一般都是打包成jar包,当然你想打包成war包也可以,我就不介绍了!本文主要想谈谈自带的打包方式和assembly打包方式,这两者…

    Java 2023年6月5日
    0120
  • [JVM]逃逸分析

    JVM的内存分配策略 首先回顾一下 JVM的内存分配策略。 JVM的内存包括方法区、堆、虚拟机栈、本地方法栈、程序计数器。一般情况下JVM运行时的数据都是存在栈和堆上的。栈用来存放…

    Java 2023年6月5日
    072
  • SQL基础

    测试环境:https://www.w3schools.com/sql/trysql.asp?filename=trysql_asc 1. 基本概念 表中的一行为一条数据,一列即为一…

    Java 2023年6月7日
    081
  • 下面代码输出结果有何不同?

    #include using namespace std; void test1() { int a,x; for(a=0,x=0;a1&&!x++;a++) a+…

    Java 2023年6月7日
    069
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球