Validation校检使用及验证反模式问题,详细

2023-05-16

  • Validation校检
    • 认识
    • 基本使用
      • 验证 Spring MVC 控制器的输入
      • 验证 service层输入
      • 验证持久化层-实体输入
      • 使用验证组为不同的用例验证不同的对象
      • 自定义验证错误
      • 自定义验证器
      • 以编程方式验证
    • 验证反模式(使用问题)
      • 仅在持久层中进行验证
      • 使用霰弹式验证
      • 使用验证组进行用例验证

Validation校检

教程学习 - reflectoring.io

反模式学习 - reflectoring.io

认识

  • 常见注解

    @NotNull:   说一个字段不能为空。
    @NotEmpty:  表示列表字段不能为空。
    @NotBlank: 表示字符串字段不能是空字符串(即它必须至少有一个字符)。
    @Min@Max: 表示数字字段仅在其值高于或低于某个值时才有效。
    @Pattern:   表示一个字符串字段只有在匹配某个正则表达式时才有效。
    @Email:    表示字符串字段必须是有效的电子邮件地址。
    
  • @Validated@Valid

    • 使用这两个注解,spring会进行自动验证

      @Validated:用于类级别

      @Valid:用在方法参数或字段。

基本使用

验证 Spring MVC 控制器的输入

  1. 验证请求正文

    • 如果不满足条件则会触发MethodArgumentNotValidException异常
    • 使用@Valid校检规则会生效
    public class Input {
    	// 最小10,最大30	
      @Min(10)
      @Max(30)
      private int age;
    }
    
    @RestController
    class ValidateRequestBodyController {
    	
      @PostMapping("/validateBody")
      // 执行该方法前都会去触发Validator对参数进行校检	
      ResponseEntity<String> validateBody(@Valid @RequestBody Input input) {
        return ResponseEntity.ok("valid");
      }
    }
    
  2. 验证路径变量和请求参数

    • 在类上添加@Validated注解,用spring处理方法参数上的校检

    • 相比@Valid,该注解失败将会触发错误500以及ConstraintViolationException异常。

      如果想返回错误400,则需要添加自定义异常处理

    @RestController
    @Validated
    public class ValidateRequestBodyController {
      // 路径变量
      @GetMapping("/validatePathVariable/{id}")
      ResponseEntity<String> validatePathVariable( @PathVariable("id") @Min(5) int id) {
        return ResponseEntity.ok("valid");
      }
    
      // 请求参数
      @GetMapping("/validateRequestParameter")
      ResponseEntity<String> validateRequestParameter(@RequestParam("param") @Min(5) int param) {
        return ResponseEntity.ok("valid");
      }
    	
      // 自定义异常处理
      @ExceptionHandler(ConstraintViolationException.class)  // 拦截ConstraintViolationException异常
      @ResponseStatus(HttpStatus.BAD_REQUEST) // 返回错误400
      ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
        return new ResponseEntity<>( "validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
      }
    }
    

验证 service层输入

  • 结合@Validated@Valid,可以验证任何 Spring 组件的输入

    @Service
    @Validated
    class ValidatingService{
        void validateInput(@Valid Input input){
          //省略...
        }
    }
    
    // 测试
    @ExtendWith(SpringExtension.class)
    @SpringBootTest
    class ValidatingServiceTest {
    
      @Autowired
      private ValidatingService service;
    
      @Test
      void whenInputIsInvalid_thenThrowsException(){
        assertThrows(ConstraintViolationException.class, () -> {
          service.validateInput(input);
        });
      }
    }
    

验证持久化层-实体输入

  • 不建议在该层进行验证,因为意味着上面业务代码已经使用了可能导致无法预料的错误的潜在无效对象

    参考文章

  • 验证方式:

    @Entity
    public class Input {
      @Min(1)
      @Max(10)
      private int numberBetweenOneAndTen;
    }
    

使用验证组为不同的用例验证不同的对象

  • 用于区分验证对象

    如:仅在更新时触发验证、或者仅在新增时触发验证。

  • 步骤:

    1. 定义标记接口

      interface OnCreate {} // 新增标记接口
      interface OnUpdate {} // 更新标记接口
      
    2. 使用

      class InputWithGroups {
        @Null(groups = OnCreate.class) // 新增时,不允许空值
        @NotNull(groups = OnUpdate.class) // 更新时, 允许空值
        private Long id; 
      }
      
      @Service
      @Validated // 必须条件
      class ValidatingServiceWithGroups {
      
          @Validated(OnCreate.class) // 要激活的验证组
          void validateForCreate(@Valid InputWithGroups input){
            // do something
          }
      
          @Validated(OnUpdate.class) // // 要激活的验证组
          void validateForUpdate(@Valid InputWithGroups input){
            // do something
          }
      
      }
      
  • 注意事项:使用验证组混合了关注点,需要了解更多信息,因此有必要控制使用这种方式。

    如:需要了解到id的新增和更新情况。

自定义验证错误

  • 当验证时,希望返回更有意义的信息。

  • 通过自定义全局异常处理器

    @RestControllerAdvice
    class ErrorHandlingControllerAdvice {
    
      @ExceptionHandler(ConstraintViolationException.class)
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      ValidationErrorResponse onConstraintValidationException(
          ConstraintViolationException e) {
       	// 省略无数...
      }
    
      @ExceptionHandler(MethodArgumentNotValidException.class)
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      ValidationErrorResponse onMethodArgumentNotValidException(
          MethodArgumentNotValidException e) {
      // 省略无数...
      }
    }
    

自定义验证器

  • 用于扩展验证

  • 步骤:

    1. 自定义约束注解IpAddress

      • message:当条件不符合时,抛出异常的默认消息

      • groups:验证组

      • @Constraint指向接口实现的注解ConstraintValidator

      @Target({ FIELD })
      @Retention(RUNTIME)
      @Constraint(validatedBy = IpAddressValidator.class)
      @Documented
      public @interface IpAddress {
      
        String message() default "{IpAddress.invalid}";
      
        Class<?>[] groups() default { };
      
        Class<? extends Payload>[] payload() default { };
      
      }
      
    2. 验证器实现

      class IpAddressValidator implements ConstraintValidator<IpAddress, String> {
      
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
          Pattern pattern = 
            Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
          Matcher matcher = pattern.matcher(value);
          try {
            if (!matcher.matches()) {
              return false;
            } else {
              for (int i = 1; i <= 4; i++) {
                int octet = Integer.valueOf(matcher.group(i));
                if (octet > 255) {
                  return false;
                }
              }
              return true;
            }
          } catch (Exception e) {
            return false;
          }
        }
      }
      
    3. 使用

      @IpAddress像使用任何其他约束注解一样使用注解

      class InputWithCustomValidator {
      
        @IpAddress
        private String ipAddress;
        
        // ...
      
      }
      

以编程方式验证

  • 不依赖其他第三方验证支持。

    class ProgrammaticallyValidatingService {
      void validateInput(Input input) {
        // 获取工厂
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        // 获取验证器
        Validator validator = factory.getValidator();
        // 进行验证
        Set<ConstraintViolation<Input>> violations = validator.validate(input);
        // 不为空则意味着有验证失败。
        if (!violations.isEmpty()) {
          throw new ConstraintViolationException(violations);
        }
      }
    }
    // 或者
    
    // springboot提供一个预先配置Validator好的实例, 
    // 当实例时,spring会自动将一个Validator实例注入到构造函数中
    @Service
    class ProgrammaticallyValidatingService {
    
      private Validator validator;
    
      ProgrammaticallyValidatingService(Validator validator) {
        this.validator = validator;
      }
    
      void validateInputWithInjectedValidator(Input input) {
        Set<ConstraintViolation<Input>> violations = validator.validate(input);
        if (!violations.isEmpty()) {
          throw new ConstraintViolationException(violations);
        }
      }
    }
    

验证反模式(使用问题)

仅在持久层中进行验证

  • 持久层为最底层

    如:mvc架构,web层(controller) -> 服务层(service) -> 持久层(mapper/dao)

  • 只在持久层中进行验证,会导致一些无效的数据传入到了web层和业务层(服务层),因为这两层并没有进行校检。

    从而在业务层产生一些错误,因此应当在业务层进行校检。如果需要在持久层进行进一步校检,充当安全网

使用霰弹式验证

  • 散弹式验证:到处添加@Validation验证,不管他会不会得到验证。

  • 这种方式的验证会导致减低开发速度、减低可读性

    • 如:当别人看到这段代码时,会思考为什么这么设置?他一定是有原因的。但是其实他并没有用处,只是习惯性的添加验证。

    因此在使用@Validation验证时,需要思考这是不是必要的?

  • 当到处都是这种验证时,如果遇到意外的验证错误,不会那么容易找到触发验证的位置。这时就会导致浪费大量的时间

使用验证组进行用例验证

  • 验证组违反了单一职责原则,模型类需要知道所有验证规则。如果用于特定用例的验证发生更改,则模型类必须更改。
  • 当验证组越来越多时,会很难阅读,因为需要了解他相关的逻辑
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Validation校检使用及验证反模式问题,详细 的相关文章

  • 我需要在 header("Location: http://localhost/..."); 之后使用 exit 吗?

    我正在创建一个脚本来验证表单 并且我问自己一个问题 当我使用标头 参见下面的示例 时 我需要立即使用 exit 吗 我的意思是 使用 header 是否也意味着它默认退出 因此我不需要使用命令 exit cancel button clic
  • 如何编写返回 Validation 的函数?

    这是我之前的后续question https stackoverflow com questions 30527740 composing validating functions in scala 假设我有两个验证函数 如果输入有效则返回
  • 两个 primefaces 日历组件验证

    我有一个表格JSF 2我使用双字段来指定日期范围 这样做的目的是不让用户选择第二个日期之前的第一个日期 所以我想在发送表单之前执行验证 使用p calendar成分 我所做的是将验证器绑定到第二个日历输入 以便在内部访问第一个组件并比较日期
  • 如何为 HTML 验证提供自定义验证错误消息?

    当我使用默认 HTML 验证时 它会显示默认错误消息 这不是我想向客户显示的 我需要自定义消息并为每个验证提供不同的信息 例如最小 最大 类型和要求 例如 该字段为必填项 值不匹配 参考传统的HTML代码
  • 语义 UI 表单验证 - 仅当值不为空时验证某些表单字段

    我有一个表单 其中包含必填字段和可选字段 我正在使用语义 UI 的表单验证行为来验证字段 然而 我想要实现的是 表单验证行为仅在可选字段具有值时才验证可选字段 可选字段示例 div class field div
  • ASP.NET 的电子邮件地址验证

    使用什么来验证 ASP NET 表单上的电子邮件地址 我想确保它不包含 XSS 漏洞 这是 ASP NET 1 1 ASP NET Web 表单上发布的任何脚本标记都会导致您的网站抛出未处理的异常 您可以使用 asp 正则表达式验证器来确认
  • 验证仅适用于数组的第一项

    给定这个模型代码 Required Display Name Name public string Name get set 以下查看代码有效 Html LabelFor model gt model Name Html TextBoxFo
  • 属性列表后缺少 jquery 验证 }

    我这里有这个代码 order validate rules name required true lastname required true address required true telephone required true di
  • Android 手机号码验证

    如何检查电话号码是否有效 长度最大为13 包括字符 在前 我怎么做 我试过这个 String regexStr 0 9 String number entered number getText toString if entered num
  • EditText.setError 不显示错误文本,仅显示图标

    我的应用程序中有一个简单的验证 这里我使用了四个EdtiText 我显示错误时EditTex不会失去焦点 但问题在于失去焦点EditText只显示图标 没有错误消息 我尝试过使用requestFocus 方法 现在可以看到错误 但问题是 现
  • 如何使用 MVC 属性验证列表是否具有大于零的非空元素?

    我正在尝试实现一个可以获取不同数量的文件的文件上传器 文件输入元素的名称都相同 因此会生成 MVC3 乐意绑定的文件列表 所以在我的控制器中我有 public virtual ViewResult UploadReceive IEnumer
  • Android - 从服务器获取响应时验证 JSON 以避免 JSONException

    在我的一些与服务器通信并使用 http 获取响应的应用程序中 我使用 json 来格式化数据服务器端 当它到达设备时 我使用类似于我在 stackoverflow 上找到的代码 private class LoadData extends
  • Angular 2:使用正则表达式进行数字验证

    我正在尝试验证 IE 11 中的数字字段
  • JSF 中基于两个组件的组合的验证/转换

    我正在开发一个 JSF Web 应用程序 我需要使用周期性作为数据结构 以下是我使用的 Java 类 public class Periodicity implements Serializable private Integer valu
  • 验证美国的电话号码是否存在

    我想用 C 验证美国的给定电话号码 是否有任何API或其他东西可以帮助程序员验证电话号码存在 我不仅想验证它们是否与美国电话号码的格式匹配 还想验证它们的存在 提前致谢 您可以使用正则表达式验证电话号码格式 例如 但为了验证该电话号码是否确
  • jquery.validate:多个远程规则

    我计划使用两个远程规则验证单个文本字段 更多这样的 form validate rules remote url1 php remote url2 php messages remote Error1 remote Error2 这可能吗
  • 将自定义参数传递给 Symfony2 中的自定义 ValidationConstraint

    我正在 Symfony2 中创建一个表单 表格只包含一个book字段允许用户在列表中进行选择Books实体 我需要检查是否选择了Book属于Author我的控制器里有 public class MyFormType extends Abst
  • RegularExpressionValidator.ValidationExpression 强制长度为 10 或 12 个符号

    RegularExpressionValidator ValidationExpression d 10 仅表示数字 最多 10 位 RegularExpressionValidator ValidationExpression d 10
  • 自定义 jQuery 验证 .addMethod

    我有一个表单 可以根据最小 最大长度验证邮政编码 我需要将所有国家 地区的邮政编码最小设置为 5 位数字 澳大利亚除外 澳大利亚需要为 4 位数字 这是我遇到的问题 validator addMethod AusZip function v
  • 如何在 CakePHP 中“验证”人名?

    我有一个 PHP 脚本 应该检查 有效 的人名 但最近破解了带有空格的名称 因此我们向验证器添加了空格 除了这样做之外 有没有办法向 CakePHP 的验证器添加黑名单以阻止所有 无效 字符 而不是允许 有效 字符 注意 我 通常 知道如何

随机推荐

  • 使用@Autowired注解警告Field injection is not recommended

    问题 xff1a 在使用变量方式依赖注入时 xff0c 提示Field injection is not recommended 64 Autowired LogService logService 虽然变量方式注入非常简洁 xff0c 但
  • mybatis动态数据源,分页插件失效

    mybatis动态数据源 xff0c 分页插件失效 发表于 xff1a 2020 08 18 20 42 47 阅读量 xff1a 9 作者 xff1a 黄叶 原因 xff1a 使用动态数据源 xff1a 数据正常但是total为0 解决
  • mybatis动态数据源配置使用事务不生效

    原因 xff1a 因为我使用的是配置的方式来加载数据源 xff0c 因此我们还需要对事务管理器进行一个配置 解决 xff1a 在代码中添加 配置事物 64 param dataSource 64 return 64 Bean public
  • Caffeine cache实现本地缓存(简单又清楚)

    Caffeine cache实现本地缓存题 缓存填充策略 手动加载 介绍 xff1a 使用方式 xff1a 同步加载 介绍 xff1a 使用方式 xff1a 异步加载 介绍 xff1a 注意 xff1a 异步和同步使用方式相似 这里的话主要
  • 商城后台系统 — 新手练习 —毕业设计

    商城后台系统 新手练习 毕业设计 业务功能介绍项目地址 xff1a 一 商品管理1 商品列表 描述 效果 2 添加商品 描述 效果 3 商品分类 描述 效果 4 商品类型 描述 效果 二 订单管理1 订单列表 描述 效果 2 订单设置 描述
  • CASE WHEN函数@sql学习

    mysql中可以使用CASE WHEN函数完成数据分组 CASE WHEN函数用来对数据进行判断和分组 来自MySQL触发器里的流程控制语句 知识 CASE WHEN是SQL编程中常用的条件控制语句 CASE WHEN的功能 xff1a 新
  • @Autowired注入为null — 4种常见情况

    64 Autowired注入为null 情况一 使用过滤器 原因解决 情况二 没有添加注解 原因解决 情况三 xff08 没有被扫描到 xff09 原因解决 情况四 xff08 手动new xff09 原因解决 情况一 使用过滤器 原因 因
  • TDD项目实战-命令行参数解析

    认识1 基本规则2 三步骤3 任务分解法总结 项目1命令行参数解析01 任务分解法与整体工作流程1 API 构思与组件划分2 功能分解与任务列表3 红绿灯循环 02 识别坏味道与代码重构1 引入多态接口2 使用 抽象工厂 模式的变体来替换分
  • mapper扫描问题(Invalid bound statement (not found))

    分析 xff1a 通常来说这种情况是mybatis没有配置好 但是还有一种可能是你的mapperscan扫描问题 解决 xff1a 使用这个的时候应该扫描的是mapper层 如果我们用成全局的扫描 xff08 根目录 xff09 xff0c
  • 找不到org.springframework.cloud.client.loadbalancer.reactive.OnNoRibbonDefaultCondition

    原因 xff1a 该类存在于spring cloud commons jar 引用的jar包存在冲突 新版本的spring cloud commons中取消了OnNoRibbonDefaultCondition类 解决 xff1a 引入依赖
  • Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springfra..问题

    原因 xff1a 依赖冲突 解决 xff1a 例如我的是spring cloud starter gateway和spring boot starter web和spring boot starter webflux依赖冲突排除 sprin
  • 重构 - 消除重复的new创建

    如下 xff1a 有时会遇到这种重复的new创建 span class token keyword public span span class token keyword class span span class token class
  • IDEA快捷键-重构

    文章目录 重构项目案例参考重构技巧1 消除重复new创建重构技巧2 提炼函数 xff0c 消除重复计算 提炼函数提炼变量搬移函数inlIne使用 xff08 内联 xff09 inLine重构局部变量inLine重构方法 重构重构菜单栏ID
  • 重构 - 提炼函数,消除重复代码

    一 参考资料二 重构步骤 以提炼重复计算函数为例子演示代码具体步骤1 提取重复new创建2 提取会变化的信息3 使用抽取的共有信息 xff0c 并删除原有信息4 提取计算函数5 使用卫语句 xff0c 简化代码逻辑 一 参考资料 重构 2
  • 模板方法 + 工厂变体消除重复if else

    模板方法 43 工厂消除重复if else 1 将重复代码 xff0c 抽取到抽象类中2 子类实现抽象类3 使用工厂获取对象 思维导图 xff1a 示例代码 xff1a 1 将重复代码 xff0c 抽取到抽象类中 span class to
  • mapstruct学习及使用详解

    映射器定义基本映射自定义映射方法从多个源对象映射映射嵌套对象更新现有实例继承配置逆映射映射期间的异常处理 数据类型转换隐式类型转换映射集合映射策略映射流映射枚举定义默认值或常量定义默认表达式 映射器检索策略映射定制装饰器 64 Before
  • 开窗函数@sql学习

    参考链接 https zhuanlan zhihu com p 98655285 mysql8 0 43 开窗函数 开窗函数又称OLAP函数 xff08 Online Analytical Processing xff09 1 开窗函数的语
  • LWIP学习系列(一):OSI模型以及TCP/IP模型的整理

    一 OSI模型与TCP IP模型的对比图 这张图是从网上搜来的 xff0c 我认为能够比较好的对应其中的两种模型的差别 学习lwip对其中部分协议有个大致的了解就行了 xff0c 具体需要的时候 xff0c 在按需求进行学习
  • win10 ubuntu16 双系统共用蓝牙鼠标

    最近给新笔记本电脑装了win10 43 ubuntu16双系统 xff0c 发现原来在win10下已经配对的蓝牙鼠标 xff0c 在ubuntu下配对后 xff0c win10就不能用了 xff0c 需要重新配对才行 xff0c 反之亦然
  • Validation校检使用及验证反模式问题,详细

    Validation校检认识基本使用验证 Spring MVC 控制器的输入验证 service层输入验证持久化层 实体输入使用验证组为不同的用例验证不同的对象自定义验证错误自定义验证器以编程方式验证 验证反模式 使用问题 xff09 仅在