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校检使用及验证反模式问题,详细 的相关文章

  • laravel 5.4 在请求验证之前修改数据[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我有我的自定义请求 它扩展了 Backpack CrudController 现在我想重写 ValidatesWhenResolv
  • 关闭框触发非模态表单验证

    我有两个继承自共同基础的表单类 其中一种形式称为模态形式 另一种形式称为非模态形式 焦点更改时需要验证 但表单取消时则不需要验证 当在模态表单上选择 关闭框 时 它会正确关闭 而不会在其控件上触发任何验证 当在非模态表单上选择关闭框时 将触
  • CakePHP3 中令人困惑的验证与应用程序规则

    有关验证的多个问题可能属于同一类 因为它们都在解决 CakePHP 3 中的新验证概念 我已阅读章节 1 http book cakephp org 3 0 en orm validation html 2 http book cakeph
  • CakePHP Unfilled 单选按钮在提交时更改为不需要的值

    我有这个表单元素 form gt input ChecklistResponseGovernmentInfo driversLicenseIsOnline array type gt radio empty gt true options
  • 在单个 WPF 控件中列出所有 Validation.Error?

    我试图找到一种简单的方法来绑定单个控件 例如 TextBlock 或 ListBox 以列出 WPF 表单上的所有验证错误 我能找到的大多数源代码示例只是将一个控件绑定到 Validation Errors 0 ErrorContent 它
  • JQuery 验证表单数组中的重复项

    我想显示重复或不唯一的值的错误 但我的表单接受输入数组 我已经在 jsfiddle 上检查了这些问题 name week 失败但是name week 工作正常 问题1 https stackoverflow com questions 24
  • 如何在 JavaScript 中验证包含“00:07 PM”的日期?

    有人验证了这种格式的日期吗 2010 年 2 月 9 日 12 07 以前在 javascript 中 javascript Date 对象存在问题 它接受 Feb 9 2010 00 07 PM 作为有效日期 如果你喜欢使用图书馆 dat
  • YAML 中的映射键标识符允许使用字符吗?

    键中允许和不允许使用哪些字符 即example in example Value 在 YAML 中 根据 YAML 1 2 规范 仅建议使用可打印字符 并排除显式控制字符 see here http www yaml org spec 1
  • 如何根据多个条件创建所需的属性?

    我有一对单选按钮的列表 是 否 Q1 Y N Q2 Y N Q3 Y N Q4 Y N 我的模型中有一个属性public string MedicalExplanation get set 我的目标是 如果任何单选按钮已设置为 true 则
  • 以编程方式创建验证列表

    我有一组从外部源进入 VBA 代码的数据 我希望能够分配该数据以用作此工作簿中一张工作表的单元格下拉框中的验证 但是 我不想将该数据复制到工作表中 然后使用命名范围 可能有相当多的数据 而且这感觉不是很有效 我确信一定有办法 但我还没有找到
  • Angular JS - 如何验证数字输入中的位数

    我们想要做的是 有一个仅接受 0 24 的输入 对于时间输入应用程序 这些是用户应该能够输入到输入中的值 0 1 2 3 4 5 6 7 8 9 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
  • Codeigniter 使用表单验证函数“匹配”子数组 POST

    上周刚开始使用 CI 并遇到了这个问题 里面要放什么matches如果我将表单数据作为数组传递 函数会怎样 我使用 html 表单中的数组来定位单个数组内的所有输入字段 以防我想传递用户生成的输入 例如多个电话号码或电子邮件 所以所有东西都
  • 如何禁用单个视图的客户端验证?

    我需要在单个视图上禁用表单的客户端验证 我该怎么做呢 我不想只禁用以下 JS 文件 Scripts jquery jquery validate min js type text javascript gt Scripts jquery j
  • Javascript onchange 事件阻止 HTML 表单中的 onsubmit 事件?

    考虑一个 HTML 表单
  • Java,根据 WSDL 中的嵌入模式验证 XML

    我有一个 wsdl具有嵌入架构的文件 我想使用它来验证 XML 文件 字符串 wsdl文件 与验证的方式相同 xsd 该架构位于
  • Codeigniter:重置表单值

    在我看来 我想要做的是在用户成功注册后清除表单字段 这里一切正常 即用户正在注册 成功消息正在向用户显示 除了我想要做的是清除表单字段的值 我正在使用这个 Clear the form validation field data so th
  • 如何验证magento中的单选按钮?

    在我的自定义模块中 我有一个带有单选按钮的表单 当我单击提交按钮时 它不会验证单选按钮 如何使用 Magento 默认验证类进行验证 Thanks 我得到了答案 use validate one required by name在单选按钮的
  • Laravel 5 中的自定义验证器

    我正在将 Laravel 应用程序从 4 升级到 5 但是 我有一个自定义验证器 但无法运行 在L4中 我做了一个验证器 php文件并将其包含在全局 php using require app path validators php 我尝试
  • 角度 2 通道数组到自定义验证器(模板驱动形式)

    我需要将一个对象数组传递给 a2 自定义验证器 然后我想根据该数组中的记录验证模板驱动表单字段的值 但是我无法检索验证器内的对象 我唯一能看到的是它的名称作为字符串 如有任何帮助 我们将不胜感激
  • 使用 php/regex 验证美国电话号码

    EDIT 我混合并修改了下面给出的两个答案 以形成完整的功能 现在它可以完成我想要的功能 然后是一些 所以我想我会将其发布在这里 以防其他人来寻找同样的东西 Function to analyze string against many p

随机推荐

  • 使用@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 仅在