SpringBoot中处理校验逻辑的两种方式:Hibernate Validator+全局异常处理

news2024/10/7 18:22:48

最近正在开发一个校园管理系统,需要对请求参数进行校验,比如说非空啊、长度限制啊等等,可选的解决方案有两种:

  • 一种是用 Hibernate Validator 来处理
  • 一种是用全局异常来处理

两种方式,我们一一来实践体验一下。

一、Hibernate Validator

Spring Boot 已经内置了 Hibernate Validator 校验框架,这个可以通过 Spring Boot 官网查看和确认。

第一步,进入 Spring Boot 官网,点击 learn 这个面板,点击参考文档。

第二步,在参考文档页点击「依赖的版本」。

第三步,在依赖版本页就可以查看到所有的依赖了,包括版本号。

PS:如果发现没有起效,可能是依赖版本冲突了,手动把 Hibernate Validator 依赖添加到 pom.xml 文件就可以了。

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.17.Final</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

通过 Hibernate Validator 校验框架,我们可以直接在请求参数的字段上加入注解来完成校验。

具体该怎么做呢

第一步,在需要验证的字段上加上 Hibernate Validator 提供的校验注解。

比如说我现在有一个用户名和密码登录的请求参数 LoginForm类:

@Data
@ApiModel(value = "用户·登录" , description = "用户表")
public class LoginForm {
    @ApiModelProperty(value = "登录名")
    @NotBlank(message = "登录名不能为空")
    private String username;
    @ApiModelProperty(value = "密码")
    @NotBlank(message = "密码不能为空")
    private String password;

    private String verifiCode;

    private Integer userType;

}

就可以通过 @NotBlank 注解来对用户名和密码进行判空校验。除了 @NotBlank 注解,Hibernate Validator 还提供了以下常用注解:

  • @NotNull:被注解的字段不能为 null;
  • @NotEmpty:被注解的字段不能为空;
  • @Min:被注解的字段必须大于等于其value值;
  • @Max:被注解的字段必须小于等于其value值;
  • @Size:被注解的字段必须在其min和max值之间;
  • @Pattern:被注解的字段必须符合所定义的正则表达式;
  • @Email:被注解的字段必须符合邮箱格式。

第二步,在对应的请求接口(SystemController.login())中添加 @Validated 注解,并注入一个 BindingResult 参数。

@ApiOperation("登录的方法")
    @PostMapping("/login")
    public Result login(@ApiParam("登录提交信息的form表单")@RequestBody@Validated LoginForm loginForm, HttpServletRequest request , BindingResult result){
        //loginForm中此时有客户端传进来的    private String username;
        //    private String password;
        //    private String verifiCode;
        //    private Integer userType;
        // 验证码校验
        HttpSession session = request.getSession();
        String sessionVerifiCode = (String)session.getAttribute("verifiCode");
        String loginVerifiCode = loginForm.getVerifiCode();
        if("".equals(sessionVerifiCode) || null == sessionVerifiCode){
            return Result.fail().message("验证码失效,请刷新后重试");
        }
        if (!sessionVerifiCode.equalsIgnoreCase(loginVerifiCode)){
            return Result.fail().message("验证码有误,请小心输入后重试");
        }
        // 从session域中移除现有验证码
        session.removeAttribute("verifiCode");
        // 分用户类型进行校验


        // 准备一个map用户存放响应的数据
        Map<String,Object> map=new LinkedHashMap<>();
        switch (loginForm.getUserType()){
            case 1:
                try {
                    Admin admin=adminService.login(loginForm);
                    if (null != admin) {
                        // 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈
                        map.put("token",JwtHelper.createToken(admin.getId().longValue(), 1));
                    }else{
                        throw new RuntimeException("用户名或者密码有误");
                    }
                    return Result.ok(map);
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    return Result.fail().message(e.getMessage());
                }
            case 2:
                try {
                    Student student =studentService.login(loginForm);
                    if (null != student) {
                        // 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈
                        map.put("token",JwtHelper.createToken(student.getId().longValue(), 2));
                    }else{
                        throw new RuntimeException("用户名或者密码有误");
                    }
                    return Result.ok(map);
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    return Result.fail().message(e.getMessage());
                }
            case 3:
                try {
                    Teacher teahcer =teacherService.login(loginForm);
                    if (null != teahcer) {
                        // 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈
                        map.put("token",JwtHelper.createToken(teahcer.getId().longValue(), 3));
                    }else{
                        throw new RuntimeException("用户名或者密码有误");
                    }
                    return Result.ok(map);
                } catch (RuntimeException e) {
                    e.printStackTrace();
                    return Result.fail().message(e.getMessage());
                }
        }
        return Result.fail().message("查无此用户");

    }

第三步,为控制层(SystemController)创建一个切面,将通知注入到 BindingResult 对象中,然后再判断是否有校验错误,有错误的话返回校验提示信息,否则放行。

@Aspect
@Component
@Order(2)
@Slf4j
public class BindingResultAspect {
    @Pointcut("execution(src* com.atguigu.myzhxy.controller.*.*(..))")
    public void BindingResult() {
    }

    @Around("BindingResult()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                BindingResult result = (BindingResult) arg;
                if (result.hasErrors()) {
                    FieldError fieldError = result.getFieldError();
                    if(fieldError!=null){
                        return Result.validateFailed(fieldError.getDefaultMessage());
                    }else{
                        return Result.validateFailed();
                    }
                }
            }
        }
        return joinPoint.proceed();
    }
}

第四步,访问登录接口,用户名和密码都不传入的情况下,就会返回“用户名不能为空”的提示信息。

可以看得出,Hibernate Validator 带来的优势有这些:

  • 验证逻辑与业务逻辑进行了分离,降低了程序耦合度;
  • 统一且规范的验证方式,无需再次编写重复的验证代码。

不过,也带来一些弊端,比如说:

  • 需要在请求接口的方法中注入 BindingResult 对象,而这个对应在方法体中并没有用到
  • 只能校验一些非常简单的逻辑,涉及到数据查询就无能为力了。

二、全局异常处理

使用全局异常处理的优点就是比较灵活,可以处理比较复杂的逻辑校验,在校验失败的时候直接抛出异常,然后进行捕获处理就可以了。

第一步,新建一个自定义异常类 ApiException。

public class ApiException extends RuntimeException {
    private IErrorCode errorCode;

    public ApiException(IErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }

    public ApiException(String message) {
        super(message);
    }

    public ApiException(Throwable cause) {
        super(cause);
    }

    public ApiException(String message, Throwable cause) {
        super(message, cause);
    }

    public IErrorCode getErrorCode() {
        return errorCode;
    }
}

第二步,新建一个断言处理类 Asserts,简化抛出 ApiException 的步骤

public class Asserts {
    public static void fail(String message) {
        throw new ApiException(message);
    }

    public static void fail(IErrorCode errorCode) {
        throw new ApiException(errorCode);
    }
}

第三步,新建一全局异常处理类 GlobalExceptionHandler,对异常信息进行解析,并封装到统一的返回对象 ResultObject 中。

@ControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(value = ApiException.class)
    public ResultObject handle(ApiException e) {
        if (e.getErrorCode() != null) {
            return ResultObject.failed(e.getErrorCode());
        }
        return ResultObject.failed(e.getMessage());
    }
}

全局异常处理类用到了两个注解,@ControllerAdvice@ExceptionHandler

@ControllerAdvice 是一个特殊的 @Component(可以通过源码看得到),用于标识一个类,这个类中被以下三种注解标识的方法:@ExceptionHandler@InitBinder@ModelAttribute,将作用于所有@Controller 类的接口上。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
}

@ExceptionHandler 注解的作用就是标识统一异常处理,它可以指定要统一处理的异常类型,比如说我们自定义的 ApiException。

第四步,在需要校验的地方通过 Asserts 类抛出异常 ApiException。还拿用户登录这个接口来说明吧。

该接口需要查询数据库验证密码是否正确,如果密码不正确就抛出校验信息“密码不正确”。

 

  switch (loginForm.getUserType()){
            case 1:
                try {
                    Admin admin=adminService.login(loginForm);
                    if (null != admin) {
                        // 用户的类型和用户id转换成一个密文,以token的名称向客户端反馈
                        map.put("token",JwtHelper.createToken(admin.getId().longValue(), 1));
                    }else{
                        Asserts.fail("用户名或者密码有误");
                    }
                    return Result.ok(map);

也可以通过 debug 的形式,体验一下整个工作流程。

三、总结

实际开发中把两者结合在一起用,就可以弥补彼此的短板了,简单校验用 Hibernate Validator,复杂一点的逻辑校验,比如说需要数据库

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

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

相关文章

Kimi 200万字爆火,通义加码1000万,阿里笑而不语

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 我怎么感觉Kimi是一个“网红”产品呢?在没有任何预兆情况下&#xff0c;国产AI大模型Kimi突然爆火&#xff0c;最近我在很多平台上看到了Kimi的广告&#xff0c;感觉到处都在吹这个产品。 看见上面的新闻了吧&a…

全网最详细的 Ubuntu 18.04 安装Livox mid-360驱动,测试 fast_lio2

目录 一、前言 二、依赖的环境 三、 安装Livox-SDK2&#xff0c;fast_lio2 和 livox_ros_driver2 (1) 安装Livox-SDK2 (2) 安装 fast_lio2 和 livox_ros_driver2 四、mid-360 设备硬件设置 五、运行设备 六、topic信息查看 一、前言 Livox mid-360需要使用Livox-SDK2…

推特社交机器人分类

机器人有不同的种类。 cresci-17数据集中的三种不同的机器人类:传统垃圾机器人、社交垃圾机器人和假追随者。 传统的垃圾邮件机器人会生成大量推广产品的内容&#xff0c;并且可以通过频繁使用的形容词来检测; 社交垃圾邮件倾向于攻击或支持政治候选人&#xff0c;因此情绪是一…

基于单片机防酒驾酒精检测报警系统装置设计

**单片机设计介绍&#xff0c;基于单片机防酒驾酒精检测报警系统装置设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机防酒驾酒精检测报警系统装置的设计旨在通过单片机技术和酒精传感器&#xff0c;实现对驾驶员酒…

Maya 2024 for Mac/Win:重塑三维创意世界的利器

在数字化浪潮汹涌的当下&#xff0c;三维图形软件早已成为创意产业不可或缺的重要工具。而在这其中&#xff0c;Maya 2024以其卓越的性能和丰富的功能&#xff0c;赢得了无数设计师的青睐。无论是Mac还是Win平台&#xff0c;Maya 2024都能为您的三维创作提供强大的支持。 Maya…

操作系统原理-模拟动态分区首次适应分配和回收算法——沐雨先生

一、实验题目&#xff1a; 模拟动态分区首次适应分配和回收算法 二、实验目的&#xff1a; 通过本实验&#xff0c;可加深理解动态分区分配、回收程序的功能和具体实现&#xff0c;特别是对回收分区的合并的理解。 三、实验环境&#xff1a; 1、硬件&#xff1a;PC机及其兼容…

【WiFi】WiFi QoS映射关系及抓包分析

WiFi Aliance认证测试对应图 RFC8325 ​​​​​​RFC 8325https://datatracker.ietf.org/doc/html/rfc8325 RFC 8325 – WiFi QoS Mappings | mrn-cciew (mrncciew.com)https://mrncciew.com/2021/09/14/rfc-8325-wifi-qos-mappings/ 802.11 UP和DSCP映射关系 802.11 UP …

万兆车载以太网转换器 10G/2.5G多速车载以太网转换器-MC10GM

MC10GM转换器 一、产品简要分析 2.5G,5G,10G可切换万兆/多速车载以太网转换器。采用罗森博格H-MTD标准接口类型。实现将车载以太网标准2.5/5/10G BASE-T1转换为工业级2.5/5/10G 标准以太网&#xff0c;进而接入电脑或工控机. 产品实现2.5/5/10G Base-T1 和2.5/5/10G Base-R之间…

ubuntu卸载Anaconda

1. 删除配置的环境变量 sudo gedit ~/.bashrc # >>> conda initialize >>> # !! Contents within this block are managed by conda init !! __conda_setup"$(/work3/ai_tool/anaconda3/bin/conda shell.bash hook 2> /dev/null)" if [ $? -…

CI860K01 3BSE032444R1 参数说明书

ABB CI860K01 3BSE032444R1是一款ABB公司生产的通信接口模块。 这款模块是专为工业自动化环境设计的&#xff0c;能够在各种设备之间提供稳定和可靠的数据传输接口。它采用了先进的通信技术和严格的生产工艺&#xff0c;确保了产品的高质量和性能。此外&#xff0c;它的设计合…

为响应国家号召,搜维尔科技开启虚拟仿真实验室设备升级改造服务

近日&#xff0c;国务院发布了关于《推动大规模设备更新和消费品以旧换新行动方案》&#xff0c;该通知的发布表现出国家对于科技创新事业的高度重视。各行各业都在积极响应国家号召&#xff0c;加快数字化转型和设备升级与更新步伐。搜维尔科技为响应国家号召&#xff0c;将开…

Linux 系统Centos7.0记录安装Docker和安装jdk环境完整教程(建议收藏备用)

Linux 系统Centos7.0记录安装Docker和安装jdk环境完整教程&#xff08;建议收藏备用&#xff09; 一、安装前准备工作 1.1 查看服务器系统版本以及内核版本 cat /etc/redhat-release1.2 查看服务器内核版本 uname -r这里我们使用的是CentOS 7.9 系统&#xff0c;内核版本为…

【LVGL-文件系统移植】

LVGL-文件系统移植 ■ LVGL-文件系统移植■ 示例一&#xff1a;■ 示例二&#xff1a;视频实例■ 综合示例&#xff1a; ■ LVGL-文件系统移植 ■ 示例一&#xff1a; 在这里插入代码片■ 示例二&#xff1a;视频实例 在这里插入代码片■ 综合示例&#xff1a; /***********…

2024年第16届大广赛新命题发布-爱华仕箱包

2024年3月27日&#xff0c;2024年第16届大广赛发布了新的命题&#xff0c;爱华仕箱包命题&#xff0c;自2017年起&#xff0c;爱华仕箱包已连续8年担任全国大学生广告艺术大赛命题单位。 爱华仕现已实现百货、超市、电商、礼品、投标、海外市场6大零售网络的全覆盖&#xff0c…

一口气搞懂分库分表 12 种分片算法,大厂都在用

前言 本文是《ShardingSphere5.x分库分表原理与实战》系列的第五篇文章&#xff0c;我们一起梳理下ShardingSphere框架中的核心部分分片策略和分片算法&#xff0c;其内部针为我们提供了多种分片策略和分片算法&#xff0c;来应对不同的业务场景&#xff0c;本着拿来即用的原则…

CD盘里的cda文件如何拷取成mp3?

CDA并非一种独立的音频文件格式&#xff0c;而是指存储在音乐CD上的音轨文件。这种格式的起源可以追溯到CD制造商对一种在CD播放器上直接播放音轨的需求&#xff0c;而不是在计算机上存储音频文件。因此&#xff0c;CDA通常存在于音乐CD中&#xff0c;为提供一种便捷的音频存储…

python--切片

1.切片&#xff1a; 切片是编程语言为有序序列&#xff08;sequence&#xff09;准备的&#xff0c;用来切割或者截取某个片段 一个完整的切片是包含三个参数和两个冒号" : " ,用于分隔三个参数(start_index、end_index、step)。当只有一个“:”时&#xff0c;默认第…

JavaScript混淆工具选择与使用指南

摘要 本文介绍了什么是js混淆工具&#xff0c;以及为什么需要使用js混淆工具。详细解释了js混淆工具的实现原理和作用&#xff0c;探讨了如何选择合适的js混淆工具&#xff0c;列举了几款常用的js混淆工具&#xff0c;并对它们的特点和适用场景进行了分析。最后总结了js混淆工…

手把手教你绘画原型图:Axure的安装使用

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师&#xff0c;大模型&#xff0c;爬虫、ACM算法 &#x1f492; 公众号&#xff…

【C++】类和对象入门(从struct到class带你了解类和对象!)

&#x1f338;博主主页&#xff1a;釉色清风&#x1f338;文章专栏&#xff1a;C&#x1f338;今日语录&#xff1a;人生本就是一首代写的诗歌&#xff0c;而他们的文字浅薄&#xff0c;不该被潦草地印刷着。所以在我笔下&#xff0c;“一重山有一重山地错落&#xff0c;我有我…