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

news2024/12/24 20:18:04

一、前言

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

    @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有了基本了解,满足基本开发没有什么问题哈!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[面试八股] Mysql

1、Mysql中的索引类型 &#xff08;1&#xff09;普通索引&#xff08;2&#xff09;唯一索引&#xff08;3&#xff09;主键索引&#xff08;4&#xff09;组合索引&#xff08;5&#xff09;全文索引 缺点 1、虽然索引大大提高了查询速度&#xff0c;同时却会降低更新表的速…

Mysql高级部分学习笔记(二)——事务锁

一、buffer pool 1.1 缓冲池&#xff08;buffer pool&#xff09; 简介&#xff1a;InnoDB是基于磁盘存储的&#xff0c;并将其中的数据按页的方式进行管理。因此InnoDB可视为基于磁盘的数据库系统。由于CPU的速度和磁盘IO速度的巨大鸿沟&#xff0c;需要**缓冲池(buffer poo…

windows下通过uiAutomation技术获取ui元素

最近接个需求&#xff0c;要求获取 windows 下 ui 元素&#xff0c;经一番搜索后了解到可通过工具 UISpy.exe 或 inspect.exe 来进行查看。以软件 davinci resolve 为例&#xff1a; 右侧即 UISpy 工具&#xff0c;根据内容可以看出已捕获到 davinci 界面的各属性及对应值。而 …

ItextPdf 字体显示差异分析与处理

ItextPdf 同span下字体显示差异分析与处理 在文章itext7 字体问题解答与相应源代码分析 中是分析了框架的字体设置与相关代码&#xff0c;在本篇文章里将对其生效效果进行分析和相关问题进行处理&#xff08;可持续更新&#xff0c;问题请留言&#xff09; 问题一 问题说明&…

将无风险资产与两种风险资产进行组合

目录 最优风险资产组合。 计算权重的公式。 应用。 最优风险资产组合。 曲线 AB 是两种风险资产的权衡取舍线。 A 点为资产组合中仅有风险资产 1 的情况。将 O 点与 A 点相连&#xff0c;便得到无风险资产与单个风险资产的权衡取舍线。 实际上&#xff0c;曲线 AB 上任一点…

【青训营】分布式理论初探

本文内容总结自 字节跳动青年训练营 第五届后端组 分布式理论初探 一、概述 分布式系统是计算机程序的集合&#xff0c;这些程序利用横跨多个独立计算节点的计算资源实现共同目标&#xff0c;可分为分布式计算、分布式存储、分布式数据库。 其优势是&#xff1a;去中心化、…

ESP32设备驱动-TEA5767收音机模块驱动

TEA5767收音机模块驱动 1、TEA5767介绍 TEA5767HN 是一款用于低压应用的单芯片电子调谐 FM 立体声收音机,具有完全集成的中频 (IF) 选择性和解调功能,频率范围从76—108MHZ自动数字调谐。 该收音机完全无需调整,高灵敏度,高稳定性,低噪音,收音模块。只需要最少的小型低…

Win10安装MySQL、Pycharm连接Mysql、Pycharm中运行django

目录 一、windows系统mysql相关操作 1、检查当前系统是否已安装mysql 1. 按win r 键&#xff08;调出运行窗口&#xff09; 2. 输入service.msc&#xff0c;点击[ 确定 】 3.打开服务列表 - 检查是否有mysql服务 2、windows安装mysql 1.下载mysql 2. 解压 mysql 到自己…

【面试题:三个线程轮流打印A ,B,C】

面试题&#xff1a;三个线程轮流打印A &#xff0c;B&#xff0c;C面试介绍说明思考方案代码实现Print 打印基类APrint 打印字符A 线程CPrint 打印字符C 线程PrintConstant 常量类StrRun 启动类测试结果总结面试介绍说明 当时是2022 年3月 在深圳面试的一家公司。由于疫情比较…

内网渗透(三)之基础知识-域环境的介绍和优点

系列文章 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 注&#xff1a;阅读本编文章前&#xff0c;请先阅读系列文章&#xff0c;以免造成看不懂的情况&#xff01;&#xff01; 域介绍 域的介绍 Windows域是计算机网络的一种形式&#…

DMTet 阅读笔记

介绍 主页 https://nv-tlabs.github.io/DMTet/论文pdf https://nv-tlabs.github.io/DMTet/assets/dmtet.pdf视频汇报 https://slideslive.com/38967642/deep-marching-tetrahedra-a-hybrid-representation-for-highresolution-3d-shape-synthesis?refhomepage疑似代码 在nvdi…

【手写 Promise 源码】第十八篇 - EventLoop 简介

theme: fancy 一&#xff0c;前言 近期公司项目比较忙&#xff0c;粘了老博客几篇 Spring 源码来充数&#xff0c;周末腾出时间把 Promise 做个收尾&#xff1b; 在开始 EventLoop 前&#xff0c;我对 Promise 源码部分进行了简单回顾&#xff0c;并更新了【Promise 源码学习…

【链表面试题】解决环形链表和相交链表问题

在力扣上发现这样的几道题&#xff0c;尝试做了一下&#xff0c;也发现了一个关于这类题的一种做法&#xff1a;快慢指针的使用。废话不多说&#xff0c;上例题 目录 一、环形链表 1.定义&#xff08;概念&#xff09; 2.如何判断是否为环形链表 1.快慢指针 2.为什么快指针…

限期出国|CSC资助赴世界top50名校英国曼彻斯特大学访学

我们先为J老师40天获得佐治亚理工学院&#xff08;美国三大理工学院之一&#xff09;的访问学者邀请函&#xff0c;又成功申报CSC。后因其担心被美国拒签要求重新申请英国名校&#xff0c;10天后拿到跻身世界top50英国曼彻斯特大学的offer&#xff0c;后经ATAS审批、CSC改派、使…

linux基本功系列-help命令实战

文章目录前言&#x1f680;&#x1f680;&#x1f680;一. help命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示某个命令的帮助信息3.2 查看某个命令的简述3.3 以伪man手册格式输出cd信息四. windows中的help总结前言&#x1f680;&#x1f680;&#x1f680; 想要学好Lin…

车载网络 - Autosar网络管理 - 基本概念

Autosar作为当前车载行业使用最为广泛的一种汽车开发系统架构&#xff0c;网上也有很多相关的介绍&#xff1b;不过我看很多有完整的来讲一下这个规范的&#xff0c;一般都是只讲了其中一部分&#xff0c;我这就准备搞出来一套完整版本的Autosar网络管理的规范、测试设计、自动…

新C++(7):多态那些事儿_下

"当人类悬浮到腐朽&#xff0c;有谁愿追随彗星漂流哦~"一、多态原理(1)虚函数表指针(虚表指针)紧接上一篇sizeof(Base)这一小段说起。class Base1 { public:void func(){} private:int _a; };class Base2 { public:virtual void func() {} private:int _a; };我们知道…

【我刚毕业,学习Java开发工程师能学会吗?没有基础?】

对于Java专业来说&#xff0c;学历还是有一定的要求。一般都是本科学历&#xff0c;至少也有个大专&#xff0c;其次就是年龄越年轻越好。现在转行Java的年轻人很多&#xff0c;学历这方面越高越有竞争力一些&#xff0c;尤其是在后期的职业晋升阶段。如果想走管理路线&#xf…

耗时一周整体,这4款黑科技电脑软件,功能强大到离谱

闲话少说&#xff0c;直上狠货。 1、有道云笔记 有道云这是一款国民级的文稿编辑器&#xff0c;俗话说得好&#xff0c;好记性不如烂笔头&#xff0c;强大实用的笔记软件&#xff0c;能让你的工作与学习事半功倍。5大文稿类型&#xff0c;让记录得心应手&#xff0c;随时进行创…

第十四章 集合(Set)

一、Set 接口&#xff08;P518&#xff09; 1. Set接口基本介绍 &#xff08;1&#xff09;无序&#xff08;添加和取出的顺序不一致&#xff09;&#xff0c;没有索引 &#xff08;2&#xff09;不允许重复元素&#xff0c;所以最多包含一个 null 2. Set接口的常用方法 和 …