java contrain_Java通过ConstrainValidator实现自定义注解@UniqueCollection校验请求集合元素唯一...

通过Set对基本类型进行去重

在API开发过程中,我们经常会遇到需要对接口参数中的集合进行重复校验的场景,这些集合可能是基本数据类型,也可能是Java Bean对象。如果是基本类型,我们可以单纯通过将接收集合设置为Set来实现去重,下面我来展示一下如何通过Set的形式实现最简单的请求参数去重。

首先在接收参数的Bean中声明待去重集合testSet:

public class uniqueCollectionRequest { private Set testSet; }1

2

3

4

5

然后在Controller中声明一个接口去接受这个Request为Body的请求:

@PostMapping

public Integer uniqueCollectionTest(@RequestBody uniqueCollectionRequest request) throws HttpException{

log.info("log for set size:{}", request.getTestSet().size());

return request.getTestSet().size();

}1

2

3

4

5

6

接下来构造请求:

curl --location --request POST 'http://localhost:8080/api/uniqueCollectionTest' \

--header 'Content-Type: application/json;charset=utf-8' \

--header 'Content-Type: text/plain' \

--data-raw '{

"testSet": [

"a","b","c","c"

]

}'1

2

3

4

5

6

7

8

可以看到最后返回的结果是3,也就是说我们所使用的Set实现了我们需要的去重功能。

通过重写Equals和hashCode来实现Bean去重

我们知道java中的HashSet,HashMap等结构都是通过equals方法来实现,这里有一篇很好的文章阐述了这个问题:hashcode()和equals()及HashSet判断对象相等。

为了实现这个目的,我们定义对象Student并重写它的hashCode及equals方法:

public class Student {

private int studentNumber;

private String studentName; @Override

public int hashCode() { return studentNumber * studentName.hashCode();

} @Override

public boolean equals(Object obj) { Student s = (Student) obj; return this.studentName.equals(s.studentName) && this.studentNumber == s.studentNumber;

} @Override

public String toString() { return studentNumber + ":" + studentName;

}

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

然后构造包含学生Set的Request来实现,我们重新定义UniqueCollectionRequst:

public class UniqueCollectionRequest { private Set studentSet;

}1

2

3

4

同理,在controller中接收这个request并打印出studentSet的大小:

@PostMapping

public Integer uniqueCollectionTest(@RequestBody uniqueCollectionRequest request) throws HttpException{

log.info("log for set size:{}", request.getStudentSet().size());

return request.getStudentSet().size();

}1

2

3

4

5

6

接下来构造请求:

curl --location --request POST 'http://localhost:8080/api/uniqueCollectionTest' \

--header 'Content-Type: application/json;charset=utf-8' \

--header 'Content-Type: text/plain' \

--data-raw '{

"studentSet": [

{"studentNumber":0,"studentName":"jerry"},

{"studentNumber":1,"studentName":"tom"},

{"studentNumber":1,"studentName":"tom"},

]

}'1

2

3

4

5

6

7

8

9

10

可以看到输出的结果为2。

自定义ConstrainValidator来实现利用注解完成重复校验操作

通过上面的两个例子我们可以看到,使用Set的确可以实现针对基础类型或者Bean的去重操作,但是当我们想要对输入集合进行重复性校验,违反校验规则时抛出异常或错误码的时候,就显得无能为力了,这时候我会推荐大家使用ConstrainValidator来优雅的处理参数校验问题。这里有一篇很好的文章向你介绍ConstrainValidator的机制和例子,来自baeldung。推荐感兴趣的同学阅读。Spring MVC Custom Validation

接下来的两个例子与bealdung的文章有些许类似,如果你英文足够好已经看过了上面链接中的文章,那么大可以愉快的忽略掉接下来我要讲的内容。

首先自定义去重操作的注解将会发生在针对于class对象的指定域(feild)进行去重操作,同时支持注解的方式指定联合域来进行去重操作。

第一步,定义你需要的注解类:

@Target({ FIELD

})

@Retention(RUNTIME)

@Documented

@Constraint(validatedBy = {UniqueCollectionValidator.class})

public @interface UniqueCollection { String[] uniqueKeys(); String message() default "object in input collection should be unique."; Class>[] groups() default {}; Class extends Payload>[] payload() default {};

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

其中元注解@Targe注解指明该注解@UniqueCollection是作用于域之上的;

@Retention(RUNTIME)表明这种类型的Annotations将会被JVM保留,所以他们能在运行时被JVM或者其他反射机制的代码所读取和使用;

@Documented表明该注解将会被包含在javadoc中;

@Constraint表明该注解的作用是表明该注解是用于校验,同时该注解使用的是UniqueCollectionValidator类实现的具体校验逻辑。

第二步,实现UniqueCollectionValidator进行参数校验逻辑:

public class UniqueCollectionValidator implements ConstraintValidator> { private UniqueCollection uniqueCollection; @Override public void initialize(UniqueCollection constraintAnnotation) { this.uniqueCollection = constraintAnnotation; } @Override public boolean isValid(Collection> target, ConstraintValidatorContext constraintValidatorContext) { if (CollectionUtils.isEmpty(target)) { return true; } List uniqueKeys = Arrays.asList(uniqueCollection.uniqueKeys()); if (uniqueKeys.isEmpty()) { return true; } int targetSize = target.size(); int targetAfterDistinctSize = (int) target.stream() .filter(distinctByKey( i -> uniqueKeys.stream() .map(key -> buildDistinctKey(i, key)) .collect(Collectors.toList()))) .count(); return targetSize == targetAfterDistinctSize; } private String buildDistinctKey(Object o, String fieldName) { try { Field field = o.getClass().getDeclaredField(fieldName); field.setAccessible(true); return (String) field.get(o); } catch (Exception e) { throw new IllegalArgumentException("can not find field named:" + fieldName); } } private static Predicate distinctByKey(Function super T, ?> keyExtractor) { Set seen = ConcurrentHashMap.newKeySet(); return t -> seen.add(keyExtractor.apply(t)); }

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

首先我们看到,参数校验逻辑实现类实现了ConstraintValidator接口,同时该接口两个泛型分别是这个Validator工作的注解类或其子类,这个Validator所去进行校验的对象。

该Validator持有注解类对象并在initialize的同时将该注解类对象初始化。

覆盖该类的isValida方法,当满足我们需要的校验条件的时候该方法返回true,否则返回false,返回false的同时,在注解接口中我们定义了错误信息:message()。

使用该注解的方式也非常简单:

@UniqueCollection(uniqueKeys = {"key1", "key2"}, message = "collection items not unique.")

List toBeValidateCollection;1

2

我们针对SomeClass里面的field key1和key2进行校验,并且在校验失败时会抛出collection items not unique的异常信息。

总结

以上就是自定义参数校验注解的全部流程,在这里我们用到了Spring Boot注解,反射,元注解等知识,对相关知识仍旧存在欠缺的同学我推荐你们阅读其他文章进行了解,真正能够做到知其然,知其所以然。

文章来源: zclhit.blog.csdn.net,作者:zclhit_,版权归原作者所有,如需转载,请联系作者。

原文链接:zclhit.blog.csdn.net/article/details/106490470

Java中,可以通过自定义注解来进行数据验证。下面是一个简单的例子: 1. 定义注解 ```java @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value() default ""; int minLength() default 0; int maxLength() default Integer.MAX_VALUE; String regex() default ""; } ``` 这个注解可以用在类的字段上,可以指定字段的值、最小长度、最大长度和正则表达式。 2. 使用注解 ```java public class User { @MyAnnotation(minLength = 3, maxLength = 10, regex = "[a-zA-Z0-9_]+") private String username; // getter and setter } ``` 在这个例子中,我们给User类的username字段加上了MyAnnotation注解,并指定了最小长度为3,最大长度为10,只能包含字母、数字和下划线。 3. 验证数据 ```java public class Validator { public static boolean validate(Object obj) throws IllegalAccessException { Class<?> clazz = obj.getClass(); for (Field field : clazz.getDeclaredFields()) { MyAnnotation annotation = field.getAnnotation(MyAnnotation.class); if (annotation != null) { field.setAccessible(true); String value = (String) field.get(obj); if (value == null || value.length() < annotation.minLength() || value.length() > annotation.maxLength() || !value.matches(annotation.regex())) { return false; } } } return true; } } ``` 这个Validator类可以用来验证任意对象的字段是否符合注解的要求。它通过反射获取对象的所有字段,并检查是否有MyAnnotation注解,然后根据注解的要求验证字段的值。 使用方法: ```java public static void main(String[] args) throws IllegalAccessException { User user = new User(); user.setUsername("abc_123"); boolean isValid = Validator.validate(user); System.out.println(isValid); // true } ``` 在这个例子中,我们创建了一个User对象,并将username设置为"abc_123",然后使用Validator类来验证这个对象的所有字段是否符合注解的要求。由于username符合要求,所以验证结果为true。 这样,我们就可以通过自定义注解来进行数据验证了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值