自定义注解@UniqueProperty

UniqueProperty 注解允许你在集合中的元素上指定某个属性,并使用 UniquePropertyValidator 类来确保该属性的值在集合中是唯一的。你可以通过注解的属性来自定义校验的行为,包括校验失败时的错误消息、属性是否可以为 null 等。

代码如下:

  1. @Target(FIELD):这个注解告诉编译器,UniqueProperty 注解应该只能用在类的字段(属性)上。

  2. @Retention(RUNTIME):这个注解指定了 UniqueProperty 的保留策略,即该注解会保留在编译后的类文件中,并在运行时可以通过反射来访问。

  3. @Documented:这个注解用于指示注解将包含在 Javadoc 文档中。

  4. @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注解!

posted @   xiaogh  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示