《学会 SpringBoot · 参数校验》

news2024/9/20 9:19:05

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • 参数校验
      • 使用步骤
      • 验证效果
      • 全局异常
      • 常用校验
      • 关于 @RequestBody
    • 总结陈词

CSDN.gif

写在前面的话

此篇博文介绍一下 SpringBoot 中的参数校验基本用法。

SpringBoot 版本:3.3.2


参数校验

使用步骤

Step1、引入依赖

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

注意:SpringBoot 2.3 以后默认 spring-boot-starter-web 组件不包含该依赖需要单独引入。

Step2、实体添加注解

public class ZyTeacherInfo {

    @Schema(description = "教师编号")
    @NotBlank(message = "教师编号不能为空")
    private java.lang.String teaCode;
    
    @Schema(description = "教师名称")
    @Size(min = 2, max = 8, message = "教师名称长度需在2-8位")
    private java.lang.String teaName;
}

Step3、接口参数添加 @Validated

@Operation(summary = "更新教师信息表JSON")
@PostMapping("/updateJson")
public void updateJson(@RequestBody @Validated ZyTeacherInfo zyTeacherInfo) {
    zyTeacherInfo.setModifiedTime(new Date());
    zyTeacherInfoService.update(zyTeacherInfo);
}

Tips:推荐用@Validated注解,因为它能够支持 Spring 提供的校验注解,并且具有更好的集成性,@Valid注解是 Java 标准库提供的,用于在任何地方触发参数校验。


验证效果

启动服务,测试一下不传入参的清空,接口提示如下:
可以看到参数未通过返回的信息很不友好,我们需要通过全局异常捕获来处理一下返回友好的提示信息。

{
  "timestamp": "2024-07-29T05:54:49.411+00:00",
  "status": 400,
  "error": "Bad Request",
  "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.lw.sbdemo2.web.ZyTeacherInfoController.updateJson(com.lw.sbdemo2.entity.ZyTeacherInfo) with 2 errors: [Field error in object 'zyTeacherInfo' on field 'teaName': rejected value []; codes [Size.zyTeacherInfo.teaName,Size.teaName,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [zyTeacherInfo.teaName,teaName]; arguments []; default message [teaName],8,2]; default message [教师名称长度需在2-8位]] [Field error in object 'zyTeacherInfo' on field 'teaCode': rejected value []; codes [NotBlank.zyTeacherInfo.teaCode,NotBlank.teaCode,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [zyTeacherInfo.teaCode,teaCode]; arguments []; default message [teaCode]]; default message [教师编号不能为空]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:144)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:224)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat com.github.xiaoymin.knife4j.extend.filter.basic.JakartaServletSecurityBasicAuthFilter.doFilter(JakartaServletSecurityBasicAuthFilter.java:55)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)\r\n\tat java.base/java.lang.Thread.run(Thread.java:842)\r\n",
  "message": "Validation failed for object='zyTeacherInfo'. Error count: 2",
  "errors": [
    {
      "codes": [
        "Size.zyTeacherInfo.teaName",
        "Size.teaName",
        "Size.java.lang.String",
        "Size"
      ],
      "arguments": [
        {
          "codes": [
            "zyTeacherInfo.teaName",
            "teaName"
          ],
          "arguments": null,
          "defaultMessage": "teaName",
          "code": "teaName"
        },
        8,
        2
      ],
      "defaultMessage": "教师名称长度需在2-8位",
      "objectName": "zyTeacherInfo",
      "field": "teaName",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "Size"
    },
    {
      "codes": [
        "NotBlank.zyTeacherInfo.teaCode",
        "NotBlank.teaCode",
        "NotBlank.java.lang.String",
        "NotBlank"
      ],
      "arguments": [
        {
          "codes": [
            "zyTeacherInfo.teaCode",
            "teaCode"
          ],
          "arguments": null,
          "defaultMessage": "teaCode",
          "code": "teaCode"
        }
      ],
      "defaultMessage": "教师编号不能为空",
      "objectName": "zyTeacherInfo",
      "field": "teaCode",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotBlank"
    }
  ],
  "path": "/zyTeacherInfo/updateJson"
}

全局异常

之前的博文《框架封装 · 统一异常处理和返回值包装》,介绍了 SpringBoot 框架的统一异常和返回值包装的处理过程,这里就可以使用到。
参考代码如下所示,可以整体捕捉 Throwable 异常,内部处理,也可以单独捕捉 MethodArgumentNotValidException 异常,单独处理。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Throwable.class)
    public ResultModel jsonErrorHandler(HttpServletRequest req, Throwable e) throws Exception {
        log.error("请求发生异常,URL:{},HTTP_METHOD:{},IP:{},错误信息:{}", req.getRequestURL()
                .toString(), req.getMethod(), req.getRemoteAddr(), e.getMessage());
        ResultModel resultModel;
        if (e instanceof ApiException) {
            resultModel = ResultModel.fail(((ApiException) e).getCode()
                    .getCode(), ((ApiException) e).getErrorMessage(), ((ApiException) e).getCode()
                    .getMessage());
        } else if (e instanceof NoHandlerFoundException) {
            resultModel = ResultModel.fail(ResponseCodeEnum.EX_PAGE_404, e.getMessage());
        } else if (e instanceof BindException) {
            List<String> errorInformation = ((BindException) e).getBindingResult()
                    .getAllErrors()
                    .stream()
                    .map(error -> Optional.ofNullable(error.getDefaultMessage())
                            .orElse("default message"))
                    .toList();
            resultModel = ResultModel.fail(ResultModel.ERROR_CODE, null, errorInformation.get(0));
        } else {
            resultModel = ResultModel.fail(ResultModel.ERROR_CODE, null, e.getMessage());
        }
        return resultModel;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultModel handleValidationExceptions(Exception ex) {
        log.error(ex.getMessage());
        // 从异常中获取字段错误信息
        FieldError fieldError = ((MethodArgumentNotValidException) ex).getBindingResult()
                .getFieldError();
        if (fieldError != null) {
            // 获取错误提示信息
            String errorMessage = fieldError.getDefaultMessage();
            log.error(errorMessage);
            return ResultModel.fail(ResultModel.ERROR_CODE, null, errorMessage);
        } else {
            // 如果没有字段错误,返回默认错误信息
            log.error(ex.getMessage());
            return ResultModel.fail(ResultModel.ERROR_CODE, null, "请求参数验证失败");
        }
    }
}

修改后,测试效果如下:
image.png


常用校验

@NotNull:用于标记字段或方法参数不能为空。非null
@NotEmpty:用于标记集合、数组、字符串不能为空。非空集合、数组、字符串
@NotBlank:用于标记字符串不能为空且长度必须大于0。非null且非空字符串
@Size:用于标记集合、数组、字符串长度必须在指定范围内
@Min:用于标记数字类型的最小值
@Max:用于标记数字类型的最大值
@Email:用于标记字符串必须为邮箱格式

@NotNull 用于一般的非空校验,@NotEmpty用于集合、数组、字符串的非空校验,@NotBlank则用于字符串的非空校验且长度必须大于0。


关于 @RequestBody

@Validated,主要用于对复杂对象(如实体类)进行校验,验证其属性是否符合约束条件。
如果前面例子调整一下,去掉 @RequestBody,然后使用x-www-form-urlencoded方式请求,是否可行。

@Operation(summary = "更新教师信息表JSON")
@PostMapping("/updateJson")
public void updateJson(@Validated ZyTeacherInfo zyTeacherInfo) {
    zyTeacherInfo.setModifiedTime(new Date());
    zyTeacherInfoService.update(zyTeacherInfo);
}

结果很明显,参数正常传递,@Validated 没效果。
如果是实体入参,又想要校验,可以实现自定义参数绑定器,将请求参数映射到实体类,这个后面再补充。

总结陈词

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

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

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

相关文章

连锁美业门店收银系统Java源码-如何设置门店仓库自提时间?博弈美业实操

1. 门店仓库自提时间&#xff0c;是当客户在小程序上购买实物商品时&#xff0c;预约上门提货的时间 2. 门店仓库自提时间&#xff0c;需要由各门店&#xff08;店主、店长、店员&#xff09;在PAD上进行设置 ▶ 操作路径&#xff1a; • 第一步&#xff1a; 进入【我的】页…

怎么进行图片压缩?对图片文件的大小进行压缩的四个方法介绍

怎么进行图片压缩&#xff1f;图片压缩是一种常见的技术&#xff0c;用于减小图像文件的大小&#xff0c;同时尽可能地保持图像的视觉质量和细节。这一过程不仅适用于个人用户想要节省存储空间或提高网页加载速度&#xff0c;也对于专业摄影师、网站设计师和应用程序开发者来说…

【OceanBase诊断调优】—— clog盘满问题排查

背景 日志盘&#xff0c;即clog盘&#xff0c;是oceanbase中用于记录事务日志信息。在日常运行中其存储量会随着事务处理情况不断变化。在一些特殊场景下会出现clog盘占用量超过阈值的情况。4.x架构下的clog盘&#xff0c;日志盘进行了租户级拆分&#xff0c;意味着无法再以3.…

Redis缓存数据库进阶——Redis缓存数据同步问题(8)

Redis缓存使用问题 数据一致性 只要使用到缓存&#xff0c;无论是本地内存做缓存还是使用 redis 做缓存&#xff0c;那么就会存在数据同步的问题。 我以 Tomcat 向 MySQL 中写入和删改数据为例&#xff0c;来给你解释一下&#xff0c;数据的增删改操作具体是如何进行的。 我…

Java中的集合相关知识汇总

总结 Java集合 从数据结构可以分为&#xff1a;数组、Set、Map、队列、栈&#xff1b;从多线程安全可以分为线程安全与非线程安全的集合从关联关系可以总结如下(不包含多线程安全类)&#xff1a; 点线框表示接口&#xff1b; 折线框表示抽象类&#xff1b; 实线框表示实现类…

动态代理IP VS 静态代理IP,分别适合什么业务场景?

随着全球化进程的加深&#xff0c;使用IP代理服务的用户与日俱增。本文以“动态IP代理与静态IP代理”为探讨话题&#xff0c;对它们之间的区别、特点与应用场景作深度的对比分析。 一、含义上的区别 动态IP代理&#xff1a;互联网服务提供商&#xff08;ISP&#xff09;向用户…

Pytorch中reshape,view,transpose以及permute的详细原理及应用

在深度学习中&#xff0c;我们经常会遇到需要对张量进行形状变换的情况。PyTorch 提供了多种方法来改变张量的形状&#xff0c;包括 reshape, view, transpose和permute 。本文总结了其它博客的精华&#xff0c;详细介绍这些方法的原理和应用场景。 目录 一、张量的存储方式 …

使用 Elasticsearch 和 LlamaIndex 保护 RAG 中的敏感信息和 PII 信息

作者&#xff1a;来自 Elastic Srikanth Manvi 在这篇文章中&#xff0c;我们将研究在 RAG&#xff08;检索增强生成&#xff09;流程中使用公共 LLMs 时保护个人身份信息 (personal identifiable information - PII) 和敏感数据的方法。我们将探索使用开源库和正则表达式屏蔽 …

探索 Redis 不同集群架构的性能与应用

1. 引言 Redis的集群配置成为了提高数据可靠性和服务可用性的关键。本文将带领大家了解Redis的四种主要集群架构&#xff0c;并重点分析哨兵模式和Redis Cluster架构和优势。 2. Redis的四种集群架构 2.1 单实例Redis 使用单个 Redis 实例提供服务。适用于小规模应用&#…

C语言 #具有展开功能的排雷游戏

文章目录 前言 一、整个排雷游戏的思维梳理 二、整体代码分布布局 三、游戏主体逻辑实现--test.c 四、整个游戏头文件的引用以及函数的声明-- game.h 五、游戏功能的具体实现 -- game.c 六、老六版本 总结 前言 路漫漫其修远兮&#xff0c;吾将上下而求索。 一、整个排…

WEB前端开发中如何实现大文件上传?

大文件上传是个非常普遍的场景&#xff0c;在面试中也会经常被问到&#xff0c;大文件上传的实现思路和流程。在日常开发中&#xff0c;无论是云存储、视频分享平台还是企业级应用&#xff0c;大文件上传都是用户与服务器之间交互的重要环节。随着现代网络应用的日益复杂化&…

实况照片怎么转换成gif动图?分享5种方法!

在当今这个视觉为王的时代&#xff0c;静态的照片已经难以满足我们追求生动、有趣的表达需求。你是否也曾想过&#xff0c;将那些精彩瞬间的实况照片转换成动感十足的GIF动图&#xff0c;为社交分享增添一抹亮色&#xff1f;今天&#xff0c;就让我们一起来探索实况照片转换成G…

自闭症怎么才能摘帽?

作为星贝育园自闭症康复中心的老师&#xff0c;经常会有家长满怀期待又焦虑地问我&#xff1a;“自闭症怎么才能摘帽&#xff1f;”今天&#xff0c;我就来为大家详细说一说。 首先&#xff0c;我们要明确&#xff0c;自闭症的“摘帽”并非一蹴而就&#xff0c;而是一个…

U盘目录损坏:诊断、恢复与防范全解析

U盘目录损坏&#xff1a;数据安全的隐形挑战 在数字化生活日益普及的今天&#xff0c;U盘作为便携式数据存储设备&#xff0c;几乎成为了每个人工作、学习和生活中不可或缺的一部分。然而&#xff0c;U盘目录损坏问题却时常发生&#xff0c;给数据安全和用户体验带来了巨大挑战…

Python数值计算(10)

继续说多项式的数值及拟合&#xff0c;这次主要讨论关于多项式拟合的函数fit。定义如下&#xff1a; classmethod polynomial.polynomial.Polynomial.fit (x, y, deg, domainNone, rcondNone, fullFalse, wNone, windowNone, symbolx) Polynomial类下面有一个函数fit&#xf…

弄懂这5条深层逻辑,你也将通透豁达

01 如果正面解决不了问题&#xff0c;不妨试试从侧面或者反面进行解决。 比如&#xff0c;食堂的锅破了一个洞&#xff0c;如果你多次反映都没能解决破洞的问题&#xff0c;那不妨直接把锅捅穿&#xff0c;让锅没有办法使用&#xff0c;进而升级问题&#xff0c;把做饭不方便…

深入源码:解析SpotBugs (3) Detector

文章目录 OpcodeStackDetector常用套路调用栈visit code类检测方法检测代码行检测 前面的博客也提到过&#xff0c;Spotbugs 里面 Detector2 与 Detector&#xff0c;FindBugs2 与 FindBugs&#xff0c;GUI2与GUI&#xff0c;可以视为 Spotbugs 与 FindBugs 新老技术的碰撞&…

基于微信小程序+SpringBoot+Vue的网络安全科普系统(带1w+文档)

基于微信小程序SpringBootVue的网络安全科普系统(带1w文档) 基于微信小程序SpringBootVue的网络安全科普系统(带1w文档) 优质的网络安全科普系统不仅可以单纯的满足工作人员管理的日常工作需求&#xff0c;还可以满足用户的需求。可以降低工作人员的工作压力&#xff0c;提高效…

课程制作及教学体验革命,AI视频生成工具如何落地教育行业?

“大力发展数字教育”&#xff0c;这是2024年政府工作报告中提到的教育任务之一。同时&#xff0c;“人工智能”行动首次写入政府工作报告&#xff0c;意味着各行各业均有新的发展空间。那么&#xff0c;数字教育应该怎么做&#xff1f;此前&#xff0c;2024年全国教育工作会议…

MySQL查询执行(三):显示随机消息

假设有如下表结构&#xff1a; -- 创建表words CREATE TABLE words (id int(11) NOT NULL AUTO_INCREMENT,word varchar(64) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB;--数据生成存储过程 delimiter ;; create procedure idata() begindeclare i int;set i0;while i<…