Spring Boot之Validation自定义实现的方法

2023-05-19,

这篇“Spring Boot之Validation自定义实现的方法”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Spring Boot之Validation自定义实现的方法”文章吧。

    Validation自定义实现方式

    Spring Boot Validation定制

    虽然在Spring Boot中已经提供了非常多的预置注解,用以解决在日常开发工作中的各类内容,但是在特定情况仍然存在某些场景,无法满足需求,需要自行定义相关的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;
    }

    以上就是关于“Spring Boot之Validation自定义实现的方法”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注本站行业资讯频道。

    《Spring Boot之Validation自定义实现的方法.doc》

    下载本文的Word格式文档,以方便收藏与打印。