Spring Boot提供了强大的校验框架,但有时我们需要根据自己的业务需求创建自定义的校验规则
。本文将介绍如何使用Spring Boot自定义注解、校验器以及反射
来检查集合中每个对象的某一属性的值是否唯一。
一、自定义注解
-
创建注解:
@UniqueProperty
用于集合中每一个对象元素的某个相同字段进行值的唯一性校验import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 用于集合中每一个对象元素的某个相同字段进行值的唯一性校验 */ @Target({PARAMETER, FIELD}) // 指定适用对象 @Retention(RUNTIME) @Documented @Constraint(validatedBy = UniquePropertyValidator.class) // 指定校验器类 public @interface UniqueProperty { // 默认0: 集合中的对象的第一个字段 int index() default 0; // 提示信息 String message() default "value is notUnique"; // 继续定义其他... //Boolean canNull(); Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
这个
注解
可以适合用在类的集合类型的字段
或方法的集合参数
上,用来标识集合中的对象的第index个位置的字段
需要做唯一性校验
。
二、定义校验类
- 创建校验器类
NotNullFieldValidator
,实现ConstraintValidator
接口,并重写initialize
和isValid
方法。import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.lang.reflect.Field; import java.util.Collection; import java.util.HashSet; import java.util.Set; @Slf4j public class UniquePropertyValidator implements ConstraintValidator<UniqueProperty , Collection<?>> { private int index; // 初始化方法 @Override public void initialize(UniqueProperty constraintAnnotation) { // 获取注解的 index 字段的值 index = constraintAnnotation.index(); } // 校验逻辑 @Override public boolean isValid(Collection<?> objects, ConstraintValidatorContext context) { if (CollectionUtils.isEmpty(objects)) { return false; } // 存放集合的每个对象的index处所获取的字段的值 Set<Object> uniqueValues = new HashSet<>(); /* * 遍历对象数组,即注解作用的对象 * 例: * @UniqueProperty(index = 2) * private List<User> list; * isValid方法中的参数 objects 指的就是 list * */ for (Object obj : objects) { //获取指定的 index处的属性值 Object param = getFieldValue(obj, index); if (param == null || uniqueValues.contains(param)) { return false; } uniqueValues.add(param); } return true; } //获取指定的 index处的字段值 public static Object getFieldValue(Object object, int index) { // 反射 获取对象的字段(Field)集合 Field[] fields = object.getClass().getDeclaredFields(); if (fields.length == 0) { throw new IllegalArgumentException("Object has no fields."); } try { // 获取字段名 String fieldName = fields[index].getName(); // 获取具有指定名称 fieldName 的字段对象 Field field = object.getClass().getDeclaredField(fieldName); // 暴力破解(包括私有字段) field.setAccessible(true); Object fieldValue = field.get(object); log.info("属性值唯一性校验: {}的值为{}", fieldName, fieldValue); return fieldValue; } catch (NoSuchFieldException | IllegalAccessException e) { log.warn("属性值唯一性校验异常: {}", e.getMessage()); return null; } } }
三、测试我们的注解
1. 注意:在pom.xml
文件中添加下面的依赖
- 用于
集成和自动配置
Java Bean Validation(JSR 380)框架。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
2. 测试-作用在方法参数
-
在未使用
javax.validation.Validator
来执行校验的情况下,校验并不会自动触发。 -
这里我们可以在不显式调用它的情况下手动触发校验:在
MyService
的testUniqueAnnotation
方法参数上使用@UniqueProperty
注解,并通过@Validated
注解告诉Spring Boot执行校验。如果collection
中的对象的字段不满足@UniqueProperty
的条件,将抛出ConstraintViolationException
异常。import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.Collection; @Data @Service @Slf4j @Validated // 通过@Validated注解告诉Spring Boot执行校验 public class MyService { public Boolean testUniqueAnnotation(@UniqueProperty(index = 2, message = "idNumber is not unique") Collection<?> collection) { // 在方法参数上使用 @UniqueProperty 注解 // 如果参数 collection中的对象的字段不满足 @UniqueProperty 的条件,将会抛出 ConstraintViolationException 异常 log.info("Collection content: {}", collection); return true; } }
-
编写
Schoolfellow类
import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class Schoolfellow { private String name; private Integer age; private String idNumber; }
-
编写
测试单元
,调用MyService
中的testUniqueAnnotation
方法。import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Collection; @SpringBootTest public class UniquePropertyTest { @Resource private MyService myService; @Test public void annotationTest1() { Collection<Schoolfellow> userList = new ArrayList<>(); userList.add(new Schoolfellow("zhangsan",18,"123456789")); userList.add(new Schoolfellow("lisi",21,"123456")); userList.add(new Schoolfellow("zhangsan",23,"123456")); myService.testUniqueAnnotation(userList); } }
-
测试不通过的异常日志
3. 测试-作用在类的字段上,
-
编写
School类
,在School
的List
字段上使用@UniqueProperty
注解。import lombok.Data; import org.springframework.stereotype.Component; import java.util.Collection; @Data @Component public class School { @UniqueProperty(index = 0, message = "name is not unique") private Collection<Schoolfellow> List; }
-
在
MyService
中添加testUniqueAnnotationByValid
方法,使用@Valid
注解来标记需要校验的类,然后在方法参数中使用这个类(School)。public Boolean testUniqueAnnotationByValid(@Valid School school) { // 在方法参数上使用 @Valid 注解 log.info("Collection content: {}", school.getList()); return true; }
- 编写
测试单元
,调用MyService
中的testUniqueAnnotationByValid
方法。
@Resource private School school; @Test public void annotationTest2() { Collection<Schoolfellow> userList = new ArrayList<>(); userList.add(new Schoolfellow("zhangsan",18,"123456789")); userList.add(new Schoolfellow("lisi",21,"123456")); userList.add(new Schoolfellow("zhangsan",23,"123456")); school.setList(userList); myService.testUniqueAnnotationByValid(school); }
- 编写
-
测试不通过的异常日志
四、特别说明
-
@Valid
是一个Java注解,通常用于标记在方法参数、方法返回值、字段、方法、构造函数等位置,它的主要作用是告诉Bean Validation(JSR 380规范中定义的Java Bean验证框架)在执行验证时,应该递归验证标记为@Valid
的对象。具体来说,
@Valid
的作用如下:- 方法参数上使用
@Valid
:在方法参数上使用@Valid
注解时,Bean Validation会自动递归验证参数中的嵌套对象。这对于验证复杂对象的属性非常有用,以确保嵌套对象中的属性也受到验证。
public void createUser(@Valid User user) { // 验证User对象及其属性 }
- 方法返回值上使用
@Valid
:还可以在方法返回值上使用@Valid
注解,以确保返回的对象经过验证。这对于确保服务方法返回的对象是有效的非常有用。
@Valid public User getUser() { // 返回User对象 }
- 字段上使用
@Valid
:虽然不太常见,但也可以在类的字段上使用@Valid
注解,通常在嵌套对象的情况下。这将告诉Bean Validation验证字段的值。
public class Order { @Valid private ShippingAddress shippingAddress; // Getters and setters }
总之,
@Valid
注解主要用于在Bean Validation框架中执行嵌套验证,以确保验证递归到标记为@Valid
的对象的属性,以及在返回值上使用它来确保方法返回的对象经过验证。这有助于确保应用程序中的数据完整性和一致性。 - 方法参数上使用
除此之外:还可以在注解中继续添加一些字段,增加校验规则
,比如:在注解内添加一个Boolean canNull()
用以规定字段的值是否能为空,然后在校验类获取canNull
进行判断。确保在实际应用中适当地处理异常和错误情况,以满足我们的实际开发需求。