Spring Boot之Validation自定义实现方式的总结

 更新时间:2022年7月4日 15:09  点击:228 作者:bladestone

Validation自定义实现方式

Spring Boot Validation定制

虽然在Spring Boot中已经提供了非常多的预置注解,用以解决在日常开发工作中的各类内容,但是在特定情况仍然存在某些场景,无法满足需求,需要自行定义相关的validator。本节将针对自定义的validator进行介绍。

自定义的注解

这里的场景设置为进行IP地址的验证,通过注解的方式,让用户使用验证规则。注解定义如下:

@Target({ElementType.FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = IPAddressValidator.class)
public @interface IPAddress {
    String message() default "{ipaddress.invalid}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

这个注解是作用在Field字段上,运行时生效,触发的是IPAddressValidator这个验证类。

  • message
  • 定制化的提示信息,主要是从ValidationMessages.properties里提取,也可以依据实际情况进行定制
  • groups
  • 这里主要进行将validator进行分类,不同的类group中会执行不同的validator操作
  • payload
  • 主要是针对bean的,使用不多。

然后自定义Validator,这个是真正进行验证的逻辑代码:

public class IPAddressValidator implements ConstraintValidator<IPAddress, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        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;
        }
    }
}

关于IP地址的验证规则是通用的,具体逻辑不用太在意,主要是需要这里Validator这个接口,以及其中的两个泛型参数,第一个为注解名称,第二个为实际字段的数据类型。

使用自定义的注解

定义了实体类CustomFieldBean.java

@Data
public class CustomFieldBean {
    @IPAddress
    private String ipAddr;
}

使用方法非常简约,基于注解,无侵入逻辑。

单元测试用例

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomFieldValidatorTest {
    @Autowired
    private ProductService productService;
    @Test(expected = ConstraintViolationException.class)
    public void testInvalid() {
        CustomFieldBean customFieldBean = new CustomFieldBean();
        customFieldBean.setIpAddr("1.2.33");
        this.productService.doCustomField(customFieldBean);
    }
    @Test
    public void testValid() {
        CustomFieldBean customFieldBean = new CustomFieldBean();
        customFieldBean.setIpAddr("1.2.33.123");
        this.productService.doCustomField(customFieldBean);
    }
}

自定义执行Validator

如果不希望由系统自行触发Validator的验证逻辑,则可以由开发者自行进行验证。在Spring Boot已经内置了Validator实例,直接将其加载进来即可。

使用示例如下:

@Autowired
private Validator validator;

自定义执行的单元测试

测试代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CodeValidationTest {
    @Autowired
    private Validator validator;
    @Test(expected = ConstraintViolationException.class)
    public void testValidator() {
        CustomFieldBean input = new CustomFieldBean();
        input.setIpAddr("123.3.1");
        Set<ConstraintViolation<CustomFieldBean>> violations = validator.validate(input);
        if (!violations.isEmpty()) {
            throw new ConstraintViolationException(violations);
        }
    }
}

自定义Validation注解

最近新开了一个项目,虽然hibernate-validator很好用,但是有时不能满足稍微复杂一些的业务校验。为了不在业务代码中写校验逻辑,以及让代码更优雅,故而采用了自定义校验注解的方式。

场景说明

本例注解应用场景: 填写表单时,某一项数据存在时,对应的一类数据都应存在,一同提交。

源码

1.类注解

主注解用于标记要在校验的实体类

@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = RelateOtherValidator.class)
@Documented
public @interface RelateOther {
    String message() default "";
    /**
     * 校验数量
     */
    int num() default 2;
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2.辅助注解

辅助注解用于标注于要校验的字段,isMaster区分为主注解和从注解。

主注解是关键字段,存在才进行校验从注解对应字段的有效性;主注解的value()属性可以设置默认值,当字段对应值对应value()时才开启校验。

从注解为等待校验的值,默认为从注解。

@Target( { FIELD })
@Retention(RUNTIME)
@Documented
public @interface RelateOtherItem {
    /**
     * 是否为主字段,主字段存在才进行校验
     */
    boolean isMaster() default false;
    /**
     * 用于开启对指定值校验判断,master字段有效
     * 当前为master且value与标注字段值相等才进行校验,
     */
    String value() default "";
}

3.校验类

校验类为实际执行校验逻辑的类,在类注解的@Constraint的validatedBy属性上设置。

要设置为校验类,首先要实现ConstraintValidator类的isValid方法。

@Slf4j  // @Slf4j是lombok的注解
public class RelateOtherValidator implements ConstraintValidator<RelateOther, Object> {
    // 要校验的个数
    private int validateNum;
    @Override
    public void initialize(RelateOther constraintAnnotation) {
        validateNum = constraintAnnotation.num();
    }
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        if (o == null) {
            return true;
        }
        Field[] declaredFields = o.getClass().getDeclaredFields();
        boolean mater = false;
        int emptyNum = 0;
        try {
            // 总共需要校验的字段数
            int totalValidateNum = validateNum;
            for (Field field : declaredFields) {
                // 校验是否进行过标注
                if (!field.isAnnotationPresent(RelateOtherItem.class)) {
                    continue;
                }
                if (validateNum > 0 && totalValidateNum-- < 0) {
                    return false;
                }
                field.setAccessible(true);
                Object property = field.get(o);
                RelateOtherItem relateOtherItem = field.getAnnotation(RelateOtherItem.class);
                // 主字段不存在,则校验通过
                if (relateOtherItem.isMaster()) {
                    if (property==null) {
                        return true;
                    }
                    // 与指定值不一致,校验通过
                    if (!StringUtils.isEmpty(relateOtherItem.value()) && !relateOtherItem.value().equals(property)) {
                        return true;
                    }
                    mater = true;
                    continue;
                }
                if (null == property) {
                    emptyNum++;
                }
            }
            // 主字段不存在,则校验通过
            if (!mater) {
                log.info("RelateOther注解主字段不存在");
                return true;
            }
            return emptyNum==0;
        } catch (Exception e) {
            log.info("RelateOther注解,解析异常 {}", e.getMessage());
            return false;
        }
    }
}

4.校验失败

注解校验不同时会抛出一个MethodArgumentNotValidException异常。这里可以采用全局异常处理的方法,进行捕获处理。捕获之后的异常可以获取BindingResult 对象,后面就跟hibernate-validator处理方式一致了。

BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();

5.使用demo

注解的使用类似下面,首先在请求实体类上标注类注解,再在对应的字段上标注辅助注解。

@RelateOther(message = "xx必须存在!",num=2)
public class MarkReq  {
    @RelateOtherItem (isMaster= true,value="1")
    private Integer  girl;
    @RelateOtherItem 
    private Integer sunscreen;
    private String remarks;
}

总结

自定义注解在开发中还是很好用的,本文主要起到抛砖引玉的作用。对于首次使用的朋友应该还是有些用处的。

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。

原文出处:https://blade.blog.csdn.net/article/details/88817334

[!--infotagslink--]

相关文章

  • Spring AOP 对象内部方法间的嵌套调用方式

    这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
  • 解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题

    这篇文章主要介绍了解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-28
  • Spring Cloud 中@FeignClient注解中的contextId属性详解

    这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25
  • Springboot如何实现Web系统License授权认证

    这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28
  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • SpringBoot实现excel文件生成和下载

    这篇文章主要为大家详细介绍了SpringBoot实现excel文件生成和下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-09
  • JS实现自定义简单网页软键盘效果代码

    本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • 详解springBoot启动时找不到或无法加载主类解决办法

    这篇文章主要介绍了详解springBoot启动时找不到或无法加载主类解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-16
  • 详解SpringCloudGateway内存泄漏问题

    这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
  • SpringBoot集成Redis实现消息队列的方法

    这篇文章主要介绍了SpringBoot集成Redis实现消息队列的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-10
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • 自定义jquery模态窗口插件无法在顶层窗口显示问题

    自定义一个jquery模态窗口插件,将它集成到现有平台框架中时,它只能在mainFrame窗口中显示,无法在顶层窗口显示. 解决这个问题的办法: 通过以下代码就可能实现在顶层窗口弹窗 复制代码 代码如下: $(window.top.documen...2014-05-31
  • 解决Springboot get请求是参数过长的情况

    这篇文章主要介绍了解决Springboot get请求是参数过长的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-17
  • 自定义feignClient的常见坑及解决

    这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-20
  • Spring Boot项目@RestController使用重定向redirect方式

    这篇文章主要介绍了Spring Boot项目@RestController使用重定向redirect方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-02
  • Springboot+TCP监听服务器搭建过程图解

    这篇文章主要介绍了Springboot+TCP监听服务器搭建过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-28
  • pytorch 自定义卷积核进行卷积操作方式

    今天小编就为大家分享一篇pytorch 自定义卷积核进行卷积操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-06
  • springBoot 项目排除数据库启动方式

    这篇文章主要介绍了springBoot 项目排除数据库启动方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-10