码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 优雅的springboot参数校验(二)


    7. 集合校验

            有这样一种场景,前端请求后端接口时,需要传递的是一个数组,数组的元素是一个对象,并且希望后台收到参数后可以对数组集合中的元素元素对象的属性进行校验,如果后台直接以List的来接收参数,约束注解的校验规则并不会触发,类似这样:

    1. @PostMapping("/add")
    2. public String add(@Validated(AddStuAndTeach.class) @RequestBody List teachers) {
    3. System.out.println("添加老师:" + teachers.size());
    4. return "success";
    5. }
    1. @Data
    2. public class Teacher {
    3. @NotNull(message = "学生的老师姓名不能为空",groups = AddStuAndTeach.class)
    4. private String tecName;
    5. @NotNull(message = "教授科目不能为空",groups = AddStuAndTeach.class)
    6. private String subject;
    7. }

        那么应该怎么办呢?

            1.需要重新实现List接口,并且在实现类里声明一个List类型变量,并且用@Valid声明;@Delegate是lombok的注解,其作用就是为变量生成一些常用方法,和@Data比较类似,具体可以自行检索lombok相关用法;(当然也可以不用这个注解,但是需要自行实现List接口的相关方法);

            2.集合内元素对象上的约束注解的用法和参数基础校验一样;

            3.controller层方法内要用重新实现List接口的类来接收前台传过来的参数,并且添加@Validated @RequestBody注解;

            总结:这种用法感觉有些奇怪,但是很有效,也算是解决了集合类参数校验的问题了。

    1. @Data
    2. public class ValidationList implements List {
    3. @Valid
    4. @Delegate
    5. private List list = new ArrayList<>();
    6. }
    1. @Data
    2. public class Teacher {
    3. @NotNull(message = "学生的老师姓名不能为空",groups = AddStuAndTeach.class)
    4. private String tecName;
    5. @NotNull(message = "教授科目不能为空",groups = AddStuAndTeach.class)
    6. private String subject;
    7. }
    1. @RestController
    2. @RequestMapping("/teacher")
    3. public class TeacherController {
    4. @PostMapping("/add")
    5. public String add(@Validated(AddStuAndTeach.class) @RequestBody ValidationList teachers) {
    6. System.out.println("添加老师:" + teachers.size());
    7. return "success";
    8. }
    9. }

    8. 自定义校验

            上面分享了参数的基础校验以及一些特殊场景下的参数校验,比如嵌套校验、分组校验、集合校验,但是如果和业务相关的,需要查询数据库才能进行校验的业务参数校验是不是还得像开头说的,用大量的if进行啰嗦的判断,答案是否定的,java API除了定义了一些标准的用法,同是也对外暴露了校验验证器接口(ConstraintValidator),让用户自己实现一些自定义的校验逻辑。具体怎么用呢?下面让慢慢道来:

            1.需要声明一个自定义约束注解,如@SexValid(校验性别格式是否正确)、@StuCodeValid(校验学生是否重复),@NotNull是java API已经定义好的,可以参考一下看看人家是怎么定义的;

            2.实现校验验证器接口(ConstraintValidator),并且重写有效性校验逻辑;(这里需要特别注意一下,如果校验通过,返回true; 如果校验校验不通过就返回false,剩下抛出异常、捕获异常就不管了);

            3.把我自定义的好的约束注解应用到controller层方法参数对象的属性上;

            我用两个例子来说明一下

            第一个:假如在添加学生的时候需要校验学号是否已经分配给其他学生了

    1. @Documented
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target({ElementType.FIELD,ElementType.PARAMETER})
    4. @Constraint(validatedBy = StuCodeValidator.class)
    5. public @interface StuCodeValid {
    6. String[] value() default {};
    7. String message() default "";
    8. Class[] groups() default {};
    9. Classextends Payload>[] payload() default {};
    10. }
    1. @Component
    2. public class StuCodeValidator implements ConstraintValidator {
    3. @Autowired
    4. private StudentService studentService;
    5. /**
    6. * 参数有效性校验
    7. * @param value
    8. * @param context
    9. * 校验规则:
    10. * 如果学生学号发生重复为无效返回false;
    11. * 如果学生学号不重复会有效,则返回true;
    12. * @return
    13. */
    14. @Override
    15. public boolean isValid(String value, ConstraintValidatorContext context) {
    16. if (value==null) {
    17. return false;
    18. }
    19. //查询学号是否重复,如果重复返回true,否则近回false;
    20. boolean flag = studentService.queryStuCodeRepeat(value);
    21. return !flag;
    22. }
    23. }
    1. @Data
    2. public class Student implements Serializable {
    3. @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    4. private Integer id;
    5. @NotNull(message = "学号不能为空",groups = AddStudent.class)
    6. @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    7. @StuCodeValid(groups = AddStudent.class,message = "学生的学号不能重复")
    8. private String stuCode;
    9. @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    10. @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    11. private String stuName;
    12. }
    1. @PostMapping("/add")
    2. public Student add(@Validated(AddStudent.class) @RequestBody Student student) {
    3. System.out.println(student.getStuName());
    4. return student;
    5. }

    第二个:假如在添加学生的时候需要校验学生的性别必须是“男”或“女”

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.FIELD,ElementType.PARAMETER})
    3. @Documented
    4. @Constraint(validatedBy = SexValidator.class)
    5. public @interface SexValid {
    6. //定义注解的里值
    7. String[] value() default {"男","女"};
    8. //定义异常信息
    9. String message() default "性别格式错误,请更正";
    10. //如果是需要分组校验,这个属性就用得上了
    11. Class[] groups() default {};
    12. //这个可以携带无
    13. Classextends Payload>[] payload() default {};
    14. }
    1. public class SexValidator implements ConstraintValidator {
    2. private String[] values;
    3. @Override
    4. public void initialize(SexValid constraintAnnotation) {
    5. this.values=constraintAnnotation.value();
    6. }
    7. /**
    8. * 参数有效性校验
    9. * @param value
    10. * @param context
    11. * @return 如果参数有效,返回true;否则false
    12. */
    13. @Override
    14. public boolean isValid(String value, ConstraintValidatorContext context) {
    15. List list = Arrays.asList(values);
    16. if (value==null) {
    17. return false;
    18. }
    19. if (list.contains(value)) {
    20. return true;
    21. }
    22. return false;
    23. }
    24. }
    1. @Data
    2. public class Student implements Serializable {
    3. @NotNull(message = "学生id不能为空",groups = QueryDetail.class)
    4. private Integer id;
    5. @NotNull(message = "学号不能为空",groups = AddStudent.class)
    6. @Length(min = 2, max = 4, message = "学号的长度范围是(2,4)")
    7. private String stuCode;
    8. @NotNull(message = "姓名不能为空",groups = AddStudent.class)
    9. @Length(min = 2, max = 3, message = "姓名的长度范围是(2,3)",groups = AddStudent.class)
    10. private String stuName;
    11. @SexValid(groups = AddStudent.class)
    12. private String sex;
    13. }
    1. @PostMapping("/add")
    2. public Student add(@Validated(AddStudent.class) @RequestBody Student student) {
    3. System.out.println(student.getStuName());
    4. return student;
    5. }

            总结:通过自己实现校验验证器,弥补了一些特殊场景下的校验需求,再也不用if esle了,代码复用性、可阅读性都大大提高了,整个controller看起来都无比清爽了。当然,方法虽妙,也要特别注意一下ConstraintValidator#isValid()方法的校验逻辑是:只有校验通过才返回true,false表示触发校验规则了,校验不通过,后面要抛出异常提示了。

            自定义校验除了通过注解这种声明式的实现外,还有一种编程式的实现,就好像spring的事务管理有两种:一种是声明式的,通过注解实现;另外一种是编程式,就硬编码来管理事务;事实上,如果非要硬编码,不如还用if else更简单直观,所以通常不管是事务管理、还是参数校验,建议还是用声明式的这种,比较优雅。

    9. 快速失败

            通常情况下,Spring Validation默认为校验完所有的字段,然后才抛出异常;当然,如果你希望一旦校验失败就马上返回,不等校验完所有字段,那么就需要手动开启快速失败的模式:

    1. @Configuration
    2. public class ValidCofing {
    3. @Bean
    4. public Validator validator(AutowireCapableBeanFactory springFactory) {
    5. try (ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
    6. .configure()
    7. // 快速失败
    8. .failFast(true)
    9. // 解决 SpringBoot 依赖注入问题
    10. .constraintValidatorFactory(new SpringConstraintValidatorFactory(springFactory))
    11. .buildValidatorFactory()) {
    12. return factory.getValidator();
    13. }
    14. }
    15. }

    总结

    1. Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。
    2. hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。
    3. Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验
    4. @Validated是spring validation提供的能力,支持分组校验,不支持嵌套校验;
    5. @Valid是hibernate validation提供的能力,支持嵌套校验,不支持分组校验;
    6. 最后一点很坑,困惑我很久,@Validated:可以用在类型、方法和方法参数上,@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上,事实上是这样不?当然是否定的,从注解上看,确实是这样,但是如果没有对应的具体实现,可以也只是“可以”而已。

            以上hibernate-validator和spring validaton提供的关于参数校验的实战应用。

    Springboot扩展点系列实现方式、工作原理集合:

    Springboot扩展点之ApplicationContextInitializer

    Springboot扩展点之BeanDefinitionRegistryPostProcessor

    Springboot扩展点之BeanFactoryPostProcessor

    Springboot扩展点之BeanPostProcessor

    Springboot扩展点之InstantiationAwareBeanPostProcessor

    Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

    Springboot扩展点之ApplicationContextAwareProcessor

    Springboot扩展点之@PostConstruct

    Springboot扩展点之InitializingBean

    Springboot扩展点之DisposableBean

    Springboot扩展点之SmartInitializingSingleton

    Springboot核心功能工作原理:

    Springboot实现调度任务的工作原理

    Springboot事件监听机制的工作原理

    优雅的springboot参数校验(二)_凡夫贩夫的博客-CSDN博客有这样一种场景,前端请求后端接口时,需要传递的是一个数组,数组的元素是一个对象,并且希望后台收到参数后可以对数组集合中的元素元素对象的属性进行校验,如果后台直接以List的来接收参数,约束注解的校验规则并不会触发,类似这样: 那么应该怎么办呢? 1.需要重新实现List接口,并且在实现类里声明一个List类型变量,并且用@Valid声明;@Delegate是lombok的注解,其作用就是为变量生成一些常用方法,和@Data比较类似,具体可以自行检索lombok相关用法;(当然https://blog.csdn.net/fox9916/article/details/128177232?spm=1001.2014.3001.5502

    优雅的springboot参数校验(一)_凡夫贩夫的博客-CSDN博客在后端的接口开发过程,实际上每一个接口都或多或少有不同规则的参数校验,有一些是基础校验,如非空校验、长度校验、大小校验、格式校验;也有一些校验是业务校验,如学号不能重重复、手机号不能重复注册等;对于业务校验,是需要和数据库交互才能知道校验结果;对于参数的基础校验,是有一些共有特征可以抽象出来,可以做成一个通用模板(java就是一种面向对象的编程语言,还记得天天快要说烂问烂的面向对象的三大特性吗?)。基于实际场景的需要,java API中定义了一些Bean校验的规范标准(JSR303:validation-ahttps://blog.csdn.net/fox9916/article/details/128167554?spm=1001.2014.3001.5502

     

  • 相关阅读:
    伦敦金投资为什么要止损?
    宏任务和微任务、事件循环、面试题
    体重秤智能电子秤方案开发设计
    BOM系列之sessionStorage
    zabbix监控TCP连接个数
    【小程序】微信公众号模板消息跳转小程序发送失败:errcode=40013 , errmsg=invalid appid rid:...
    python接口自动化-参数关联
    手写一个PrattParser基本运算解析器2: PrattParser概述
    Java代码实现两个数据库之间的数据同步
    C++11标准模板(STL)- 算法(std::transform)
  • 原文地址:https://blog.csdn.net/fox9916/article/details/128177232
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号