自定义注解@UniqueProperty
UniqueProperty
注解允许你在集合中的元素上指定某个属性,并使用 UniquePropertyValidator
类来确保该属性的值在集合中是唯一的。你可以通过注解的属性来自定义校验的行为,包括校验失败时的错误消息、属性是否可以为 null 等。
代码如下:
-
@Target(FIELD)
:这个注解告诉编译器,UniqueProperty
注解应该只能用在类的字段(属性)上。 -
@Retention(RUNTIME)
:这个注解指定了UniqueProperty
的保留策略,即该注解会保留在编译后的类文件中,并在运行时可以通过反射来访问。 -
@Documented
:这个注解用于指示注解将包含在 Javadoc 文档中。 -
@Constraint(validatedBy = UniquePropertyValidator.class)
:这是一个约束注解,它告诉验证器要使用UniquePropertyValidator
类来执行实际的校验逻辑。
/**
* 用于集合中每一个元素的某个相同属性进行值的唯一性校验
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = UniquePropertyValidator.class)
public @interface UniqueProperty {
/**
* 默认0为类中的第一个属性,若类中出现序列版本号,则0指向序列版本号。
* @return
*/
int index() default 0;
String message() default "invalid value";
boolean canNull() default false;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
验证器的主要功能包括:
-
通过
initialize
方法来获取UniqueProperty
注解上的配置信息,包括要校验的属性位置(index
)和是否允许属性为 null(canNull
)。 -
通过
isValid
方法来执行实际的校验逻辑。它首先检查集合是否为空,如果为空并且允许属性为 null,则认为校验通过。如果为空但不允许属性为 null,则认为校验失败。接下来,它遍历集合中的元素,获取指定位置的属性值,并将这些属性值添加到一个集合中进行唯一性校验。如果发现重复的属性值,或者某个属性值为 null(如果不允许属性为 null),则认为校验失败。 -
getFieldValue
方法用于通过反射获取对象中指定位置属性的值。它首先获取对象的所有字段,然后根据指定的索引位置获取字段名称,进而获取属性值。这个方法的目的是根据index
属性指定的位置获取属性值。
@Slf4j
public class UniquePropertyValidator implements ConstraintValidator<UniqueProperty ,Collection<?>> {
private int index;
private boolean canNull;
@Override
public void initialize(UniqueProperty constraintAnnotation) {
index = constraintAnnotation.index();
canNull = constraintAnnotation.canNull();
}
@Override
public boolean isValid(Collection<?> objects, ConstraintValidatorContext context) {
if(CollectionUtil.isEmpty(objects)){
if(canNull) {
return true;
} else {
return false;
}
}
Set<Object> uniqueValues = new HashSet<>();
for (Object obj : objects) {
//获取指定属性值
Object param = getFieldValue(obj, index);
if (param == null || uniqueValues.contains(param)) {
return false;
}
uniqueValues.add(param);
}
return true;
}
public static Object getFieldValue(Object object, int index) {
// 使用 Java 反射获取对象的所有字段
Field[] fields = object.getClass().getDeclaredFields();
if (fields.length == 0) {
throw new IllegalArgumentException("Object has no fields.");
}
try {
String fieldName = fields[index].getName();
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object fieldValue = field.get(object);
log.warn("属性值唯一性校验:{}的值为{}", fieldName, fieldValue);
return fieldValue;
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
示例:
@UniqueProperty(message = "XX出现重复!", index = 1)
private List<User> users;
注意:1、通过若对象中存在SerialVersionUID字段,那么index = 0会指向SerialVersionUID!
2、要在Controller加上@Valid注解!
3、多层对象嵌套也需要逐层添加@Valid注解!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现