Spring中的数据校验---JSR303

news2024/11/14 22:32:10

介绍–什么是JSR303

JSR 303是Java中的一项规范,用于定义在Java应用程序中执行数据校验的元数据模型和API。JSR 303的官方名称是"Bean Validation",它提供了一种在Java对象级别上执行验证的方式,通常用于确保输入数据的完整性和准确性。

JSR 303中最常见的用法是使用注解在Java Bean上添加验证规则。以下是一些常用的注解:
此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。
注:可以使用注解的方式进行验证

JSR 303 基本的校验规则

空检查
  • @Null 验证对象是否为null
  • @NotNull 验证对象是否不为null, 无法查检长度为0的字符串
  • @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0, 只对字符串, 且会去掉前后空格.
  • @NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
  • @AssertTrue 验证 Boolean 对象是否为 true
  • @AssertFalse 验证 Boolean 对象是否为 false
其他校验

@Size(min, max): 检查值的长度是否在指定范围内。
@Min(value): 检查数字值是否大于等于指定值。
@Max(value): 检查数字值是否小于等于指定值。
@Pattern(regex): 使用正则表达式检查字符串值。
@Email: 检查字符串是否为有效的电子邮件地址等。

在实体类或者vo类使用验证规则,可以大幅度减轻数据校验的规范性价比。

具体使用

添加依赖

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <scope>provided</scope>
    </dependency>

对需要使用校验的字段添加注解

信息可以进行传值设置,不然就是默认值

@NotEmpty(message = "品牌名必须填写")
	@Schema(description = "品牌名")
	private String name;

默认信息如下
在这里插入图片描述

在控制层开启校验

在控制层开启该注解@Validated即可开启校验

   public Result<String> update(@Validated @RequestBody PmsBrandVO vo){
            pmsBrandService.update(vo);
            return Result.ok();
    }

自定义校验失败

上诉的步骤中,校验失败,不满足条件的会抛出异常,所以为了和前端配合需要进行自定义异常处理,返回前端一个json,而不是服务端抛出异常
BindException
在需要返回json的校验bean后跟,BindingResult,校验绑定结果类,对异常进行处理

    public Result<String> save(@Validated({AddGroup.class}) @RequestBody PmsBrandVO vo, BindingResult result){
//
// 形参单个添加这个可以,但是批量很难实现,为此需要自定义异常处理
// 需要取求掉该注解,才可以将异常抛出


        if (result.hasErrors())
        {
            String message = result.getFieldErrors().stream()
                    .map(fieldError -> {
                        // 获取到错误字段
                        String field = fieldError.getField();

                        // 获取到自定义的错误消息提示
                        String errMessage = fieldError.getDefaultMessage();

                        // 返回拼接的错误消息字符串
                        return field + ":" + errMessage;
                    })
                    .collect(Collectors.joining(", ")); // 将错误消息用逗号分隔
log.info("错误消息:{}",message);
// 创建一个 Result 对象,将错误消息传递给它
            return Result.error(message);


        }else {
            pmsBrandService.save(vo);

            return Result.ok();
        }

但是上诉只样也只是对单个控制器校验校验为此,需要自己,定义异常处理结果

自定义异常处理器 处理校验失败异常

1.把控制层的结构异常结果绑定类进行删除,让控制器将异常进行抛出
2.自定义异常处理

/**
 * 异常处理器
 * 用于集中处理所有异常情况,并确保返回给前端的是处理过的 JSON 信息而不是异常信息。
 */

@Slf4j
@RestControllerAdvice//监听rescontroller的增强方法 advice增强 对应还有controllelrAdvice
public class ServerExceptionHandler {
    /**
     * 处理自定义异常
     * @param ex 抛出的自定义异常
     * @return 包含异常信息的 Result 对象
     */

    @ExceptionHandler(ServerException.class)//捕获的异常类型
    public Result<String> handleException(ServerException ex) {

        return Result.error(ex.getCode(), ex.getMsg());
    }

    /**
     * 处理 Spring MVC 参数绑定、Validator 校验不正确的异常
     * @param ex 抛出的绑定异常
     * @return 包含异常信息的 Result 对象
     */

    @ExceptionHandler(BindException.class)
    public Result<String> bindException(BindException ex) {
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null;
        return Result.error(fieldError.getDefaultMessage());
    }
    /**
     * 处理访问被拒绝的异常
     * @param ex 抛出的访问被拒绝异常
     * @return 包含异常信息的 Result 对象
     */

    @ExceptionHandler(AccessDeniedException.class)
    public Result<String> handleAccessDeniedException(Exception ex) {

        return Result.error(ErrorCode.FORBIDDEN);
    }
    /**
     * 未知异常类型,用于处理未捕获的其他异常情况
     */

    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception ex) {
        log.error(ex.getMessage(), ex);
        return Result.error(ErrorCode.INTERNAL_SERVER_ERROR);
    }

}

这样就可以做到统一处理,异常结果

高级功能

自定义校验注解

如果现有的异常处理结果满足不了我们对字段的需求,那么可以进行自定义校验注解,在自定义校验注解之前,我们需要了解自定义校验注解

自定义注解

注解有些元注解,以及生命周期都是很简单的概念,这里讲一下大致
自定义注解主要通过@interface关键字来定义。
自定义注解的组成包括:

注解声明:使用@interface关键字。
元注解(Meta-annotations):
用于注解其他注解的注解。常用的元注解有@Target、@Retention、- @Documented和@Inherited。

  • @Target:指定注解可以应用的Java元素类型(如METHOD, FIELD等)。
  • @Retention:指定注解在哪一个级别可用,生命周期(源代码中(SOURCE)、类文件中(CLASS)或运行时(RUNTIME)),而我们定义的大部分注解都是在运行时候,用来进行操作日志保存和权限校验
    注解体:定义注解的属性。
    其他的注解关键字,点开任意注解都可以了解大概
演示

我这里定义自定义注解,模拟操作前进行的日志保存
首先启动开启注解功能

@EnableAspectJAutoProxy
/**
 *  所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
 */
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Prelog {

   String message() default "执行前先打印的日志信息";
}

在我的业务控制器上添加注解
在这里插入图片描述

好了现在我的注解定义好了,并且让我的方法使用上了注解,但是这样注解是没办法知道我们的业务逻辑的,
所以需要实现他的逻辑,这里运用到了aop详细了解aop思想,大概就是对目标做增强,在不改变源码的基础上

引入aop的依赖

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

定义一个切面类,对我自定义注解进行逻辑实现

@Aspect
@Component
@Slf4j
public class PrelogAspect {


    //切点:使用LogAnnotation注解标识的方法都进行切入,也可以使用通配符配置具体要切入的方法名
    @Pointcut("@annotation(com.mall.Annotation.Prelog)")
    public void pointCut(){

    }

    //环绕通知

    /**
     * 在AOP中,joinPoint.proceed()方法用于继续执行切入点处的原始方法。换句话说,它实际上调用了被切入的方法,无论是类的构造函数、方法或字段初始化等。
     * 在你的情况下,你的切点是使用@Prelog注解标识的方法,因此当切点匹配到一个被@Prelog注解标记的方法时,joinPoint.proceed()方法会执行该方法。因此,在@Around通知中,joinPoint.proceed()执行的就是被@Prelog注解标记的方法。
     * 所以,Object jsonResult = joinPoint.proceed();这行代码实际上执行了被@Prelog注解标记的方法,并将其结果存储在jsonResult变量中。这个变量可以在切面中进一步处理或返回给调用方。
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("开始执行注解逻辑");
            // 获取目标方法签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取目标方法
            Method method = signature.getMethod();
            // 获取注解值
            Prelog annotation = method.getAnnotation(Prelog.class);
            // 获取属性
            String message = annotation.message();
            System.out.println(message);


            // 执行目标方法
            Object jsonResult = joinPoint.proceed(); // 执行方法

            return jsonResult;
        } catch (Exception e) {
            e.printStackTrace();
            throw e; // 抛出异常
        }
    }

}

访问被标记注解的接口
在这里插入图片描述

当然自定义注解配合aop还可以做权限校验,访问接口前判断是否有对应权限

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PermissionAspect {

    @Around("@annotation(CheckPermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckPermission annotation = signature.getMethod().getAnnotation(CheckPermission.class);
        String permission = annotation.value();

        // 这里模拟权限校验逻辑
        if (!hasPermission(permission)) {
            throw new SecurityException("没有权限执行此操作");
        }

        return joinPoint.proceed(); // 执行原方法
    }

    private boolean hasPermission(String permission) {
    /**
    *根据该用户在系统的上下文 对照是否拥有该权限
    **/
        // 模拟权限校验逻辑,实际中应替换为具体的校验逻辑
        // 例如,检查当前用户是否拥有该权限
        // 这里简单模拟总是返回true
        return true;
    }
}

好了大概了解自定义注解和aop的原理进行实现,自定义校验数据的注解

自定义校验注解实现

1.定义校验注解 我这里定义的是判断字段的值是否是我设置的集合中的值

/**
 * 1.编写一个自定义注解作用于多个元素校验
 */
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {OptionListConstraint.class })//3.指定使用什么校验器 可以指定多个校验器
public @interface OptionList {
    /**
     * 1.1改造validate注解的基本属性
     * 注解的类型只能是基本类型和布尔值以及枚举,字符串
     * @return
     */
    //2.定义自己需要的注解属性
    int[] values()   default  {0,1};//默认该注解的这个属性是0,1
    String message() default "必须提交指定的数值";//原min注解的默认消息定义在租界中华这个

    Class<?>[] groups() default { };
//做校验的时候自定义负载参数
    Class<? extends Payload>[] payload() default { };
}

2.对注解进行实现
jsr中的注解进行注解校验都是通过实现校验器接口实现的,点开@Constrain注解
在这里插入图片描述

所以需要实现该接口

/**
 * 2.对校验器进行重写
 * 实现该接口的俩个方法
 * 参数<校验注解,校验对象类型>
 *     就是自定义注解中的逻辑实现
 */
public class OptionListConstraint implements ConstraintValidator<OptionList, Integer> {
       private Set<Integer> set= new HashSet<Integer>();
    //初始化方法

    // 参数为校验注解
    // 可以在该方法中获取校验注解中的属性值
    @Override
    public void initialize(OptionList constraintAnnotation) {
       //1.得到赋于注解的数值
        int[] values = constraintAnnotation.values();

        //2.将数值赋值给set
        for (int i : values) {
            set.add(i);
        }
    }

    /**
     * 判断是否校验成功
     * @param value object to validate 需要校验的对象 也就是赋值的属性字段的值
     * @param context context in which the constraint is evaluated 上下文对象
     *
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        // 判断当前的值是否在set集合中
        return set.contains(value);
    }
}

校验分组

这个行为类似于范围限制,比如,对于id主键字段,我们在新增时候是需要前端不传递的,修改又需要前端传递,为此对与不同状态进行分组处理

  1. 定义俩个接口 表示不同组别 无需写什么方法
    在这里插入图片描述
    2.限定字段校验范围,那些字段是什么组别的时候进行校验
    在这里插入图片描述
    3.控制层 对应校验赋值时标明组别
    在这里插入图片描述

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

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

相关文章

app软件开发的费用大约多少

现在手机的用户在增多&#xff0c;大部分的人都是通过手机软件去操作完成的东西比较多&#xff0c;所以导致现在的流量都在手机端。那么不管是电脑端还是手机端都是在同时发展的&#xff0c;使用电脑的人群只是倾向在了工作人群&#xff0c;而手机的流量大部分是来自于生活中。…

HarmonyOS—配置编译构建信息

在进行应用/服务的编译构建前&#xff0c;需要对工程和编译构建的Module进行设置。API Version 9、API Version 8与API Version 4~7的构建体系不同&#xff0c;因此在设置编译构建信息时也存在差异&#xff1a; API Version 9&#xff1a;需要对构建配置文件、构建脚本、应用依…

Springboot+vue的考勤管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的考勤管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

边缘计算网关的重要作用-天拓四方

随着物联网技术的迅猛发展&#xff0c;数据量的爆炸式增长对数据处理和分析提出了更高的要求。边缘计算网关作为连接物理世界和数字世界的桥梁&#xff0c;正逐渐受到各行业的重视。本文将从行业背景、功能特点以及带来的效益等方面&#xff0c;探讨边缘计算网关在当前及未来的…

HTML中自定义鼠标右键菜单

今天突然有人跟我提到了HTML中如何自定义鼠标右键菜单&#xff0c;这里大概记录一下吧&#xff0c;方便下次直接复制。免得还去看API文档。 文章目录 HTML中自定义鼠标右键菜单结果如下所示可以稍微改一下鼠标悬浮到右键菜单时的样式结果如下所示 只在某个特定的div才可以显示…

来不及了!大学必须完成的四件事!

老师们常说&#xff0c;上大学就轻松了 其实不然 大学不是人生的终点&#xff0c;而是新的起跑线 不是休息站&#xff0c;而是进入社会的最后冲刺跑道 大学生活苦乐参半&#xff0c;成人世界即将来临 出了校门&#xff0c;你会发现社会复杂多变&#xff0c;需要不断学习 稍…

C++之结构体以及通讯录管理系统

1&#xff0c;结构体基本概念 结构体属于自定义的数据概念&#xff0c;允许用户存储不同的数据类型 2&#xff0c;结构体的定义和使用 语法&#xff1a;struct 结构体名{ 结构体成员列表}&#xff1b; 通过结构体创建变量的方式有三种&#xff1a; 1&#xff0c;struct …

在Jupyter-lab中使用RDKit画分子2D图

在Jupyter-lab中使用RDKit画分子2D图 在做完分子对接后&#xff0c;想看看筛选后的分子的结构。因此想利用Jupyter-lab来画分子的2D图。 1. 安装Jupyter-lab与RDKit 系统&#xff1a;Win11已安装conda RDKit 是一个功能强大、灵活易用的化学信息学工具包&#xff0c;广泛应…

前端CSS常考问题总结

目录 CSS盒模型 CSS选择器的优先级 隐藏元素的方法 px和rem的区别是什么? 重绘重排有什么区别? 重排&#xff08;回流&#xff09;&#xff1a; 重绘&#xff1a; 浏览器的渲染机制: 浏览器如何解析CSS&#xff1f; 元素水平垂直居中的方式 CSS的哪些属性哪些可以…

队列循环——C++引用详解【入队元素不引用 出队元素引用】(C语言版)

出队元素引用 入队元素不引用解释&#xff1a; 在给定的代码中&#xff0c;对于 DeQueue 函数的参数使用引用 (&)&#xff0c;而对于 EnQueue 函数的参数没有使用引用&#xff0c;是基于函数内部对这些参数值的修改方式来确定的。 让我们分析一下每个函数&#xff1a; En…

SQL-Labs靶场“29-31”关通关教程

君衍. 一、二十九关 基于错误的WAF单引号注入1、源码分析2、HTTP参数污染3、联合查询注入4、updatexml报错注入 二、三十关 基于错误的WAF双引号注入1、源码分析2、联合查询注入3、updatexml报错注入 三、三十一关 基于错误的WAF双引号括号注入1、源码分析2、联合查询注入3、up…

windows10下如何通过命令行方式新增一个外部WLAN的Wifi热点并连接

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、背景 最近碰到2个家人windows下连接电脑热点的问题&#xff0c;一个是在某个培训场所显示的热点名称不是公布的热点名称&#xff0c;而是一个乱码&#xff0c;在家里和工作单位热点则显示正常&#xff…

【二叉搜索树】【前后指针】Leetcode 501. 二叉搜索树中的众数

【二叉搜索树】【前后指针】Leetcode 501. 二叉搜索树中的众数 解法1 中序遍历双指针解法2 我的复杂方法 先中序遍历到数组&#xff0c;之后hashmap遍历判断众数 之后转化为数组输出 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;-----------…

(已解决)emoji及其特殊符号在vue中的使用

问题叙述 使用问题&#xff1a;在表情库中点击后无法展示对应的emoji表情&#xff0c;仅仅显示Unicode代码编号&#xff0c;按照vue eslint规则更改添加分号后&#xff0c;没有报错但是依旧无法正确渲染。 问题源码&#xff1a; <template><div><!-- 被点击的…

KubeSphere简介,功能介绍,优势,架构说明及应用场景

KubeSphere 是在目前主流容器调度平台 Kubernetes 之上构建的企业级分布式多租户容器平台&#xff0c;提供简单易用的操作界面以及向导式操作方式&#xff0c;在降低用户使用容器调度平台学习成本的同时&#xff0c;极大减轻开发、测试、运维的日常工作的复杂度&#xff0c;旨…

【问题解决】| 关于torch无法使用GPU的一些实验探索,思考

文章目录 1 研究背景2 问题解决2.1 问题一&#xff0c;这两个版本分别是怎么得到的&#xff1f;2.2 问题二&#xff0c;Windows下安装CUDA Tookit 是必须的吗&#xff1f;2.3 问题三&#xff0c;驱动版本必须大于等于运行时版本吗&#xff1f;2.4 问题四&#xff0c;只运行pip …

力扣110 平衡二叉树 Java版本

文章目录 题目描述代码 题目描述 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#xff1a; 输入&#xff1a;root [3,9,…

python笔记_程序流程控制

A&#xff0c;顺序控制 程序从上到下逐行执行 python定义变量时&#xff0c;会合法地向前引用 age 1 age2 age 1 age2 age 1 age 1 ——>错误&#xff0c;age应在age2之前 B&#xff0c;分支控制 1&#xff0c;单分支if 语法 if 条件表达式 &#xff1a; 代码块 说明…

代码随想录day10(2)字符串:反转字符串Ⅱ (leetcode541)

题目要求&#xff1a;给定一个字符串 s 和一个整数 k&#xff0c;从字符串开头算起, 每计数至 2k 个字符&#xff0c;就反转这 2k 个字符中的前 k 个字符。如果剩余字符少于 k 个&#xff0c;则将剩余字符全部反转。如果剩余字符小于 2k 但大于或等于 k 个&#xff0c;则反转前…

两天学会微服务网关Gateway-Gateway简介

锋哥原创的微服务网关Gateway视频教程&#xff1a; Gateway微服务网关视频教程&#xff08;无废话版&#xff09;_哔哩哔哩_bilibiliGateway微服务网关视频教程&#xff08;无废话版&#xff09;共计17条视频&#xff0c;包括&#xff1a;1_Gateway简介、2_Gateway工作原理、3…