SpringBoot 参数验证的几种方式

文章目录

  • SpringBoot 参数验证
    • 1、为什么要进行参数验证
    • 2、验证方式
      • 2.1 if 语句判断
      • 2.2 Assert
      • 2.3 Validator
        • 2.3.1 引入依赖
        • 2.3.2 定义参数实体类
        • 2.3.4 定义特定异常全局拦截方法
        • 2.3.5 定义校验类进行测试
        • 2.3.6 测试
      • 2.4 自定义验证注解
        • 2.4.1 定义自定义注解
        • 2.4.2 定义自定义验证器类
        • 2.4.3 应用自定义注解

SpringBoot 参数验证

1、为什么要进行参数验证

在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数进行校验,例如登录的时候需要校验用户名和密码是否为空,添加用户的时候校验用户邮箱地址、手机号码格式是否正确。 靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。

进行参数验证是软件开发中的一个重要环节,其主要原因包括但不限于以下几点:

  1. 数据完整性与准确性:确保接收到的数据是完整且准确的,避免因错误或恶意的数据输入导致系统异常或数据损坏。
  2. 安全防护:防止注入攻击(如SQL注入)、跨站脚本攻击(XSS)等安全威胁,通过验证可以过滤掉非法输入,增强系统安全性。
  3. 性能优化:提前验证参数可以减少不必要的数据库查询或业务逻辑执行,从而提升系统整体性能。
  4. 用户体验:及时向用户提供清晰的错误提示,指导他们正确输入信息,避免提交表单后才告知错误,提高了用户体验。
  5. 代码可维护性:集中处理参数验证逻辑,使得业务逻辑代码更加清晰,易于理解和维护。
  6. 遵循最佳实践:参数验证是编程和Web开发中的一个基本最佳实践,遵循这些原则可以减少错误和漏洞,提升软件质量。
  7. 减少异常处理:通过前端和后端的双重验证,可以减少运行时异常的发生,使得程序更加稳定可靠。
  8. 合规性:对于涉及用户隐私或敏感信息的应用,参数验证也是遵守数据保护法规(如GDPR)的一个重要方面。

因此,参数验证是构建高质量、安全、易用的应用程序不可或缺的一环。

2、验证方式

2.1 if 语句判断

    @PostMapping("/parameterCheck")@Operation(summary = "参数校验", description = "嵌套参数校验-测试")public CommonResult<TestDto> parameterCheck(@RequestBody TestDto dto) {if (dto == null){throw new RuntimeException("参数不能为空");}return CommonResult.SUCCESS(dto);}

缺点

  1. 代码可读性和维护性降低:当if条件复杂或嵌套层次过多时,代码可读性大大降低,使得维护和理解代码变得更加困难。开发者可能需要花费更多时间去梳理逻辑关系。
  2. 容易出错:复杂的if条件判断容易出现逻辑错误,比如漏写某个条件分支,或条件判断逻辑失误,导致程序行为不符合预期。
  3. 测试难度增加:if语句尤其是嵌套和多重if的情况下,会生成多个代码路径,这意味着需要编写更多的测试用例来覆盖所有可能的执行路径,增加了测试的复杂度和成本。
  4. 性能影响:虽然现代编译器会对代码进行优化,但在某些情况下,特别是深度嵌套或大量if判断时,可能会对程序的执行效率产生负面影响,尤其是在循环内部或者高频调用的代码块中。
  5. 扩展性差:随着需求变化,频繁修改或增减if条件会使代码结构变得混乱,不利于后期的扩展和修改。
  6. 难以调试:当if逻辑出错时,定位问题可能比较困难,特别是在没有明确错误信息或日志记录的情况下。

因此,在设计代码时,推荐采用诸如策略模式、状态模式等设计模式来替代复杂的if判断,或者使用Switch语句(在适用的情况下)来提高代码的清晰度和可维护性。同时,也可以考虑利用函数式编程的思想,将逻辑分解为更小的、可重用的函数,以提高代码的模块化程度。

2.2 Assert

    @PostMapping("/parameterCheck")@Operation(summary = "参数校验", description = "嵌套参数校验-测试")public CommonResult<TestDto> parameterCheck(@RequestBody TestDto dto) {Assert.isNull(dto.getName(), "姓名不能为空");Assert.isNull(dto.getSex(), "性别不能为空");return CommonResult.SUCCESS(dto);}

使用Assert语句进行参数校验在Java等编程语言中较为常见,尤其是在单元测试中用于验证预期结果。然而,在生产代码中过度依赖Assert进行参数校验也存在一些缺点:

  1. 非异常处理机制Assert主要用于开发阶段的自我检查,它抛出的是AssertionError,这是一种错误而非异常。在默认的Java虚拟机设置下,生产环境通常不启用断言(即-ea标志未设置),这意味着断言不会执行,从而无法起到参数校验的作用。
  2. 用户体验不佳:即便在启用了断言的环境中,AssertionError通常是直接终止程序的,没有被捕获和处理的机制,这会导致程序突然崩溃,给用户带来不友好的体验。
  3. 缺乏灵活性Assert主要用于验证程序内部不变性条件,其信息更多服务于开发者调试,而不能提供丰富的错误信息反馈或自定义错误处理逻辑。
  4. 不利于维护和调试:由于Assert在生产环境中默认不启用,可能导致某些错误在开发阶段未被发现,而在生产环境中因为不同的配置导致问题浮现,增加了问题排查的难度。
  5. 不适用于所有类型的应用程序:对于要求高稳定性和错误处理逻辑复杂的应用,直接使用Assert进行参数校验并不合适,因为它缺乏控制异常流和提供恢复机制的能力。

2.3 Validator

Validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;Validator专门用来进行接口参数校验,例如常见的必填校验,email格式校验,用户名必须位于6到12之间等等。

2.3.1 引入依赖

注意:如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖。我这里使用的SpringBoot版本是3.0.0,因此手动引入了。

        <!-- 如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。如果spring-boot版本大于2.3.x,则需要手动引入依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
2.3.2 定义参数实体类

常见的约束注解如下:

注解功能
@AssertFalse可以为null,如果不为null的话必须为false
@AssertTrue可以为null,如果不为null的话必须为true
@DecimalMax设置不能超过最大值
@DecimalMin设置不能超过最小值
@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future日期必须在当前日期的未来
@Past日期必须在当前日期的过去
@Max最大不得超过此最大值
@Min最大不得小于此最小值
@NotNull不能为null,可以是空
@Null必须为null
@Pattern必须满足指定的正则表达式
@Size集合、数组、map等的size()值必须在指定范围内
@Email必须是email格式
@Length长度必须在指定范围内
@NotBlank字符串不能为null,字符串trim()后也不能等于""
@NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于""
@Range值必须在指定范围内
@URL必须是一个URL
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDto {@NotBlank(message = "姓名不能为空")@Schema(description = "姓名")private String name;@NotNull(message = "年龄不能为空")@Schema(description = "年龄")@Min(value = 0, message = "年龄不能小于0")@Max(value = 200, message = "年龄不能大于200")private Integer age;//性别只允许为男或女@NotBlank(message = "性别不能为空")@Pattern(regexp = "^(男|女)$", message = "性别必须为'男'或'女'")@Schema(description = "性别")private String sex;@Valid@Schema(description = "嵌套对象")private TestDtoObj testDtoObj;
}
import com.example.demo.annotation.PhoneNumber;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {@PhoneNumber@NotBlank(message = "手机号1不能为空")@Schema(description = "手机号1")private String phone1;@Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")@NotBlank(message = "手机号不能为空")@Schema(description = "手机号2")private String phone2;@NotBlank(message = "密码不能为空")@Size(min = 6, max = 16, message = "密码长度必须在6到16个字符之间")@Schema(description = "密码")private String password;@NotBlank(message = "邮箱不能为空")@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")@Schema(description = "邮箱")private String email;@Digits(integer = 4, fraction = 2, message = "整数位数必须在4位以内小数位数必须在2位以内")@Schema(description = "小数")private Double num;@URL(message = "url格式错误")@Schema(description = "地址")private String url;@Past(message = "日期必须为过去日期")@Schema(description = "过去日期")private LocalDate pastDate;@Future(message = "日期必须为将来日期")@Schema(description = "将来日期")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")private LocalDateTime futureDate;}
2.3.4 定义特定异常全局拦截方法

Validator框架 抛出的特定异常为MethodArgumentNotValidException,该异常会将我们在参数校验注解自定义的message返回到e.getBindingResult().getFieldError().getDefaultMessage()中。

/*** 全局异常拦截* * @author zyw*/
@Slf4j
@RestControllerAdvice
public class BaseExceptionHandler {/*** 拦截参数校验异常* @param e* @param request* @return*/@ExceptionHandler(MethodArgumentNotValidException.class)public CommonResult<?> handleGlobalException(MethodArgumentNotValidException e, HttpServletRequest request) {log.error("请求地址'{}',发生系统异常'{}'", request.getRequestURI(), e.getBindingResult().getFieldError().getDefaultMessage());return CommonResult.ECEPTION(ResultCode.PARAMETER_EXCEPTION, e.getBindingResult().getFieldError().getDefaultMessage());}}
2.3.5 定义校验类进行测试
import com.example.demo.config.CommonResult;
import com.example.demo.model.dto.TestDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;@RestController
@Slf4j
@RequestMapping("knife4j")
@Tag(name = "knife4j测试控制器")
public class Knife4jController {@PostMapping("/parameterCheck")@Operation(summary = "参数校验", description = "嵌套参数校验-测试")public CommonResult<TestDto> parameterCheck(@Validated @RequestBody TestDto dto) {return CommonResult.SUCCESS(dto);}
}
2.3.6 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4 自定义验证注解

在Java项目中,自定义注解是一种强大的功能,允许开发者创建自己的注解类型来满足特定需求,比如验证、日志记录、性能监控等。我们通过自定义注解修饰特定的接口、方法、属性、类,可以实现更加灵活的功能。

2.4.1 定义自定义注解
import com.example.demo.uitls.validator.PhoneNumberValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;/*** PhoneNumber : 手机号格式验证注解* 用于验证电话号码格式的注解。* 该注解可以应用于字段或参数上,以验证其是否为有效的电话号码格式。* 默认的错误消息是“无效的手机号码格式”,但可以通过message属性自定义。* 可以通过groups和payload属性来支持分组验证和负载信息。** @Documented 标记此注解将被包含在文档中。* @Constraint 标记此注解为约束注解,并指定PhoneNumberValidator类作为验证器。* @Target 指定此注解可以应用于字段和参数上。* @Retention 指定此注解在运行时保留。* @author zyw* @create 2024-05-31  15:38*/@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {/*** 验证失败时的错误消息,默认为“无效的手机号码格式”。* 可以通过将此属性设置为自定义错误消息来更改默认消息。** @return 验证失败时的错误消息。*/String message() default "无效的手机号码格式";/*** 定义验证的分组,默认为空组。* 可以通过将此属性设置为一个或多个分组类来指定字段应在哪些分组中进行验证。** @return 验证的分组类数组。*/Class<?>[] groups() default {};/*** 定义验证的负载信息,默认为空负载。* 可以通过将此属性设置为一个或多个负载类来携带额外的验证信息。** @return 验证的负载信息类数组。*/Class<? extends Payload>[] payload() default {};
}
2.4.2 定义自定义验证器类
import com.example.demo.annotation.PhoneNumber;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;/*** PhoneNumberValidator : 手机号验证器类** @author zyw* @create 2024-05-31  15:39*/public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$"; // 中国手机号码的简单正则表达式@Overridepublic boolean isValid(String phoneNumber, ConstraintValidatorContext context) {return phoneNumber != null && phoneNumber.matches(PHONE_PATTERN);}
}
2.4.3 应用自定义注解
import com.example.demo.annotation.PhoneNumber;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;/*** TestDtoObj :** @author zyw* @create 2024-05-31  15:47*/@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {@PhoneNumber@PhoneNumber(message = "手机号格式错误")@Schema(description = "手机号1")private String phone1;@Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")@NotBlank(message = "手机号不能为空")@Schema(description = "手机号2")private String phone2;}

*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestDtoObj {

@PhoneNumber
@PhoneNumber(message = "手机号格式错误")
@Schema(description = "手机号1")
private String phone1;@Pattern(regexp = "^1[3-9]\\d{9}$", message = "无效的手机号码格式")
@NotBlank(message = "手机号不能为空")
@Schema(description = "手机号2")
private String phone2;

}

在这里插入图片描述

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

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

相关文章

Python第二语言(八、Python包)

目录 1. 什么是Python包 2. 创包步骤 2.1 new包 2.2 查看创建的包 2.3 拖动文件到包下 3. 导入包 4. 安装第三方包 4.1 什么是第三方包 4.2 安装第三方包-pip 4.3 pip网络优化 1. 什么是Python包 包下有__init__.py就是包&#xff0c;无__init__.py就是文件夹。于Ja…

嵌入式学习——Linux高级编程复习(进程)——day39

1. 进程 进程是计算机科学中的一个核心概念&#xff0c;它是操作系统进行资源分配和调度的基本单位&#xff0c;代表了一个正在执行中的程序实例。当一个程序被加载到内存并开始执行时&#xff0c;它就变成了一个进程。 1. 程序&#xff1a;存放在外存中的一段代码的集合 2. 进…

牛客 NC129 阶乘末尾0的数量【简单 基础数学 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/aa03dff18376454c9d2e359163bf44b8 https://www.lintcode.com/problem/2 思路 Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff…

Python 很好用的爬虫框架:Scrapy:

了解Scrapy 爬虫框架的工作流程&#xff1a; 在scrapy中&#xff0c; 具体工作流程是这样的&#xff1a; 首先第一步 当爬虫引擎<engine>启动后&#xff0c; 引擎会到 spider 中获取 start_url<起始url> 然后将其封装为一个request对象&#xff0c; 交给调度器<…

Elastic Search 8.14:更快且更具成本效益的向量搜索,使用 retrievers 和重新排序提升相关性,RAG 和开发工具

作者&#xff1a;来自 Elastic Yaru Lin, Ranjana Devaji 我们致力于突破搜索开发的界限&#xff0c;并专注于为搜索构建者提供强大的工具。通过我们的最新更新&#xff0c;Elastic 对于处理以向量表示的大量数据的客户来说变得更加强大。这些增强功能保证了更快的速度、降低的…

Activity->Activity中动态添加Fragment->add和replace方式添加的区别

XML文件 Activity布局文件R.layout.activity_main <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/root_ll"android:orientation"v…

Linux本地搭建DataEase并发布公网远程访问进行数据分析

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

【RAG入门教程01】Langchian框架 v0.2介绍

LangChain 是一个开源框架&#xff0c;旨在简化使用大型语言模型 (LLM) 创建应用程序的过程。可以将其想象成一套使用高级语言工具进行搭建的乐高积木。 它对于想要构建复杂的基于语言的应用程序而又不必管理直接与语言模型交互的复杂性的开发人员特别有用。它简化了将这些模型…

数据库之PostgreSQL详解

一、PostgreSQL介绍 PostgreSQL是一个功能强大的 开源 的关系型数据库。底层基于C实现。 PostgreSQL的开源协议和Linux内核版本的开源协议是一样的。。BDS协议&#xff0c;这个协议基本和MIT开源协议一样&#xff0c;说人话&#xff0c;就是你可以对PostgreSQL进行一些封装&a…

如何在本地和远程删除 Git 分支

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;目前是武汉城市开发者社区主理人 擅长.net、C、python开发&#xff0c; 如果遇…

SpringBoot之Mybatis-plus实战

文章目录 MybatisPlus 介绍一、MyBatisPlus 集成步骤第一步、引入依赖第二步、定义mapper 二、注解TableNameTableldTableField 加解密实现步骤 在SpringBoot项目中使用Mybatis-plus&#xff0c;记录下来&#xff0c;方便备查。 MybatisPlus 介绍 为简化开发而生&#xff0c;官…

CSAPP Lab01——Data Lab完成思路

陪你把想念的酸拥抱成温暖 陪你把彷徨写出情节来 未来多漫长再漫长还有期待 陪伴你 一直到 故事给说完 ——陪你度过漫长岁月 完整代码见&#xff1a;CSAPP/datalab-handout at main SnowLegend-star/CSAPP (github.com) 01 bitXor 这道题是用~和&计算x^y。 异或是两个…

红黑树的介绍与实现

前言 前面我们介绍了AVL树&#xff0c;AVL树是一棵非常自律的树&#xff0c;有着严格的高度可控制&#xff01;但是正它的自律给他带来了另一个问题&#xff0c;即虽然他的查找效率很高&#xff0c;但是插入和删除由于旋转而导致效率没有那么高。我们上一期的结尾说过经常修改…

C语言:双链表

一、什么是双链表&#xff1f; 双链表&#xff0c;顾名思义&#xff0c;是一种每个节点都包含两个链接的链表&#xff1a;一个指向下一个节点&#xff0c;另一个指向前一个节点。这种结构使得双链表在遍历、插入和删除操作上都表现出色。与单链表相比&#xff0c;双链表不仅可以…

【机器学习】【遗传算法】【项目实战】药品分拣的优化策略【附Python源码】

仅供学习、参考使用 一、遗传算法简介 遗传算法&#xff08;Genetic Algorithm, GA&#xff09;是机器学习领域中常见的一类算法&#xff0c;其基本思想可以用下述流程图简要表示&#xff1a; &#xff08;图参考论文&#xff1a;Optimization of Worker Scheduling at Logi…

DVB-S系统发射端Matlab仿真及FPGA实现

DVB标准 Digital Video Broadcasting&#xff08;数字视频广播&#xff09;是一个完整的数字电视解决方案&#xff0c;其中包括DVB-C&#xff08;数字电视有线传输标准&#xff09;&#xff0c; DVB-T&#xff08;数字电视地面传输标准&#xff09;&#xff0c;DVB-S&#xff…

正确理解iOS中的同步锁

在 iOS 开发中&#xff0c;同步锁&#xff08;synchronized lock&#xff09;是一种用于管理多线程访问共享资源的机制&#xff0c;而不是某一种特定类型的锁。它涵盖了多种具体实现和技术&#xff0c;用于确保同一时间只有一个线程能够访问某个共享资源&#xff0c;从而避免数…

探地雷达正演模拟,基于时域有限差分方法,一

声明&#xff1a;本博客中的公式均是在Word中使用AxMath写好后截图使用的&#xff0c;欢迎引用&#xff0c;但请标注来源。 本系列会有四篇博客&#xff1a; 第一篇内容&#xff1a; 1、基础知识掌握 2、Maxwell方法差分求解原理 第二篇内容&#xff1a; 1、基于C的TE波波…

docker——基础知识

简介 一、什么是虚拟化和容器化 ​ 实体计算机叫做物理机&#xff0c;又时也称为寄主机&#xff1b; ​ 虚拟化&#xff1a;将一台计算机虚拟化为多态逻辑计算机&#xff1b; ​ 容器化&#xff1a;一种虚拟化技术&#xff0c;操作系统的虚拟化&#xff1b;将用户空间软件实…

mongodb总概

一、mongodb概述 mongodb是最流行的nosql数据库&#xff0c;由C语言编写。其功能非常丰富&#xff0c;包括: 面向集合文档的存储:适合存储Bson(json的扩展)形式的数据;格式自由&#xff0c;数据格式不固定&#xff0c;生产环境下修改结构都可以不影响程序运行;强大的查询语句…