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

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

目录

Validation自定义实现方式

Spring Boot Validation定制

使用自定义的注解

自定义执行Validator

自定义Validation注解

场景说明

源码

总结

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; } 总结

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

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持易知道(ezd.cc)。

推荐阅读