经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
Spring?Boot之Validation自定义实现方式的总结
来源:jb51  时间:2022/7/4 14:10:02  对本文有异议

Validation自定义实现方式

Spring Boot Validation定制

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

自定义的注解

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

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

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

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

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

  1. public class IPAddressValidator implements ConstraintValidator<IPAddress, String> {
  2. ? ? @Override
  3. ? ? public boolean isValid(String value, ConstraintValidatorContext context) {
  4. ? ? ? ? Pattern pattern = compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
  5. ? ? ? ? Matcher matcher = pattern.matcher(value);
  6. ? ? ? ? try {
  7. ? ? ? ? ? ? if (!matcher.matches()) {
  8. ? ? ? ? ? ? ? ? return false;
  9. ? ? ? ? ? ? } else {
  10. ? ? ? ? ? ? ? ? for (int i = 1; i <= 4; i++) {
  11. ? ? ? ? ? ? ? ? ? ? int octet = Integer.valueOf(matcher.group(i));
  12. ? ? ? ? ? ? ? ? ? ? if (octet > 255) {
  13. ? ? ? ? ? ? ? ? ? ? ? ? return false;
  14. ? ? ? ? ? ? ? ? ? ? }
  15. ? ? ? ? ? ? ? ? }
  16. ? ? ? ? ? ? ? ? return true;
  17. ? ? ? ? ? ? }
  18. ? ? ? ? } catch (Exception e) {
  19. ? ? ? ? ? ? return false;
  20. ? ? ? ? }
  21. ? ? }
  22. }

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

使用自定义的注解

定义了实体类CustomFieldBean.java

  1. @Data
  2. public class CustomFieldBean {
  3. ? ? @IPAddress
  4. ? ? private String ipAddr;
  5. }

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

单元测试用例

测试代码:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class CustomFieldValidatorTest {
  4. ? ? @Autowired
  5. ? ? private ProductService productService;
  6. ? ? @Test(expected = ConstraintViolationException.class)
  7. ? ? public void testInvalid() {
  8. ? ? ? ? CustomFieldBean customFieldBean = new CustomFieldBean();
  9. ? ? ? ? customFieldBean.setIpAddr("1.2.33");
  10. ? ? ? ? this.productService.doCustomField(customFieldBean);
  11. ? ? }
  12. ? ? @Test
  13. ? ? public void testValid() {
  14. ? ? ? ? CustomFieldBean customFieldBean = new CustomFieldBean();
  15. ? ? ? ? customFieldBean.setIpAddr("1.2.33.123");
  16. ? ? ? ? this.productService.doCustomField(customFieldBean);
  17. ? ? }
  18. }

自定义执行Validator

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

使用示例如下:

  1. @Autowired
  2. private Validator validator;

自定义执行的单元测试

测试代码如下:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class CodeValidationTest {
  4. ? ? @Autowired
  5. ? ? private Validator validator;
  6. ? ? @Test(expected = ConstraintViolationException.class)
  7. ? ? public void testValidator() {
  8. ? ? ? ? CustomFieldBean input = new CustomFieldBean();
  9. ? ? ? ? input.setIpAddr("123.3.1");
  10. ? ? ? ? Set<ConstraintViolation<CustomFieldBean>> violations = validator.validate(input);
  11. ? ? ? ? if (!violations.isEmpty()) {
  12. ? ? ? ? ? ? throw new ConstraintViolationException(violations);
  13. ? ? ? ? }
  14. ? ? }
  15. }

自定义Validation注解

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

场景说明

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

源码

1.类注解

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

  1. @Target( { TYPE })
  2. @Retention(RUNTIME)
  3. @Constraint(validatedBy = RelateOtherValidator.class)
  4. @Documented
  5. public @interface RelateOther {
  6. ? ? String message() default "";
  7. ? ? /**
  8. ? ? ?* 校验数量
  9. ? ? ?*/
  10. ? ? int num() default 2;
  11. ? ? Class<?>[] groups() default {};
  12. ? ? Class<? extends Payload>[] payload() default {};
  13. }

2.辅助注解

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

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

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

  1. @Target( { FIELD })
  2. @Retention(RUNTIME)
  3. @Documented
  4. public @interface RelateOtherItem {
  5. ? ? /**
  6. ? ? ?* 是否为主字段,主字段存在才进行校验
  7. ? ? ?*/
  8. ? ? boolean isMaster() default false;
  9. ? ? /**
  10. ? ? ?* 用于开启对指定值校验判断,master字段有效
  11. ? ? ?* 当前为master且value与标注字段值相等才进行校验,
  12. ? ? ?*/
  13. ? ? String value() default "";
  14. }

3.校验类

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

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

  1. @Slf4j ?// @Slf4j是lombok的注解
  2. public class RelateOtherValidator implements ConstraintValidator<RelateOther, Object> {
  3. ?? ?// 要校验的个数
  4. ? ? private int validateNum;
  5. ? ? @Override
  6. ? ? public void initialize(RelateOther constraintAnnotation) {
  7. ? ? ? ? validateNum = constraintAnnotation.num();
  8. ? ? }
  9. ? ? @Override
  10. ? ? public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
  11. ? ? ? ? if (o == null) {
  12. ? ? ? ? ? ? return true;
  13. ? ? ? ? }
  14. ? ? ? ? Field[] declaredFields = o.getClass().getDeclaredFields();
  15. ? ? ? ? boolean mater = false;
  16. ? ? ? ? int emptyNum = 0;
  17. ? ? ? ? try {
  18. ? ? ? ? ? ? // 总共需要校验的字段数
  19. ? ? ? ? ? ? int totalValidateNum = validateNum;
  20. ? ? ? ? ? ? for (Field field : declaredFields) {
  21. ? ? ? ? ? ? ? ? // 校验是否进行过标注
  22. ? ? ? ? ? ? ? ? if (!field.isAnnotationPresent(RelateOtherItem.class)) {
  23. ? ? ? ? ? ? ? ? ? ? continue;
  24. ? ? ? ? ? ? ? ? }
  25. ? ? ? ? ? ? ? ? if (validateNum > 0 && totalValidateNum-- < 0) {
  26. ? ? ? ? ? ? ? ? ? ? return false;
  27. ? ? ? ? ? ? ? ? }
  28. ? ? ? ? ? ? ? ? field.setAccessible(true);
  29. ? ? ? ? ? ? ? ? Object property = field.get(o);
  30. ? ? ? ? ? ? ? ? RelateOtherItem relateOtherItem = field.getAnnotation(RelateOtherItem.class);
  31. ? ? ? ? ? ? ? ? // 主字段不存在,则校验通过
  32. ? ? ? ? ? ? ? ? if (relateOtherItem.isMaster()) {
  33. ? ? ? ? ? ? ? ? ? ? if (property==null) {
  34. ? ? ? ? ? ? ? ? ? ? ? ? return true;
  35. ? ? ? ? ? ? ? ? ? ? }
  36. ? ? ? ? ? ? ? ? ? ? // 与指定值不一致,校验通过
  37. ? ? ? ? ? ? ? ? ? ? if (!StringUtils.isEmpty(relateOtherItem.value()) && !relateOtherItem.value().equals(property)) {
  38. ? ? ? ? ? ? ? ? ? ? ? ? return true;
  39. ? ? ? ? ? ? ? ? ? ? }
  40. ? ? ? ? ? ? ? ? ? ? mater = true;
  41. ? ? ? ? ? ? ? ? ? ? continue;
  42. ? ? ? ? ? ? ? ? }
  43. ? ? ? ? ? ? ? ? if (null == property) {
  44. ? ? ? ? ? ? ? ? ? ? emptyNum++;
  45. ? ? ? ? ? ? ? ? }
  46. ? ? ? ? ? ? }
  47. ? ? ? ? ? ? // 主字段不存在,则校验通过
  48. ? ? ? ? ? ? if (!mater) {
  49. ? ? ? ? ? ? ? ? log.info("RelateOther注解主字段不存在");
  50. ? ? ? ? ? ? ? ? return true;
  51. ? ? ? ? ? ? }
  52. ? ? ? ? ? ? return emptyNum==0;
  53. ? ? ? ? } catch (Exception e) {
  54. ? ? ? ? ? ? log.info("RelateOther注解,解析异常 {}", e.getMessage());
  55. ? ? ? ? ? ? return false;
  56. ? ? ? ? }
  57. ? ? }
  58. }

4.校验失败

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

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

5.使用demo

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

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

总结

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号