@NotNull 非 null 验证

用于标记字段必须不能为 null。注解定义:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
	// 错误提示信息,验证失败时使用
	String message() default "{javax.validation.constraints.NotNull.message}";
	// 用于指定在进行验证时所属的验证组
	Class<?>[] groups() default { };
	// 用来指定关于验证约束的元数据信息的
	Class<? extends Payload>[] payload() default { };
	// 内部注解,用于存放多个 @NotNull 自身
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
	@Retention(RUNTIME)
	@Documented
	@interface List {
		NotNull[] value();
	}
}

示例:使用 @NotNull 校验 name 字段,如下:

package com.hxstrive.validation;

import lombok.Builder;
import lombok.Data;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import java.util.Set;

/**
 * 注解元素必须不为null
 * @author hxstrive.com
 **/
@Data
@Builder
public class NotNullDemo {

    @NotNull(message = "名称不能为空")
    private String name;

    public static void main(String[] args) {
        NotNullDemo.builder().name("Tom").build().validator("case1"); // OK
        NotNullDemo.builder().name("").build().validator("case2"); // OK
        NotNullDemo.builder().name(null).build().validator("case3"); // Fail
    }

    private void validator(String caseName) {
        validator(caseName, this);
    }

    private <T> void validator(String caseName, T obj) {
        // 手动调用 API 对定义了注解的字段进行校验
        Validator validator = Validation.byProvider(HibernateValidator.class).configure()
                .failFast(true).buildValidatorFactory().getValidator();
        Set<ConstraintViolation<T>> set = validator.validate(obj);
        if (set.size() > 0) {
            // 校验失败
            System.out.println(caseName + " :: Fail :: " + set.iterator().next().getMessage());
        } else {
            // 校验通过
            System.out.println(caseName + " :: Succeed");
        }
    }

}

运行结果:

case1 :: Fail :: 名称必须为空
case2 :: Fail :: 名称必须为空
case3 :: Succeed

group 属性

@NotNull 注解中的 group 属性则用于指定在进行验证时所属的验证组。验证组是用于对实体类的字段进行分组验证的机制,可以根据不同的场景指定不同的验证组进行验证。

在实际使用中,可以定义不同的验证组,例如 GroupA、GroupB 等,然后在实体类的字段上使用@NotNull 注解时,通过 group 属性指定所属的验证组。例如:

public class User {
    @NotNull(groups = {GroupA.class})
    private String username;

    @NotNull(groups = {GroupB.class})
    private String password;
}

在上面的例子中,username 字段属于 GroupA 验证组,password 字段属于 GroupB 验证组。在进行验证时,可以根据需要指定使用哪个验证组进行验证。例如,可以使用 GroupA 验证组对 username 字段进行验证,而不对 password 字段进行验证。

这样的设计可以很好地满足不同场景下的验证需求,使得验证逻辑更加灵活和精确。

示例:创建 GroupAdd 和 GroupUpdate 接口用来指定 @NotNull 的 group 属性,然后使用不同的验证组进行验证,代码如下:

package com.hxstrive.validation;

import lombok.Builder;
import lombok.Data;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import java.util.Set;

/**
 * 注解元素必须不为null
 * @author hxstrive.com
 **/
@Data
@Builder
public class NotNullDemo2 {

    @NotNull(message = "ID不能为空", groups = { GroupUpdate.class })
    private String id;

    @NotNull(message = "名称不能为空", groups = { GroupAdd.class, GroupUpdate.class })
    private String name;

    interface GroupAdd {
        // 新增验证组
    }

    interface GroupUpdate {
        // 更新验证组
    }

    public static void main(String[] args) {
        // GroupAdd 验证组
        NotNullDemo2.builder().id("100").name("Tom").build().validator("GroupAdd_case1", GroupAdd.class);
        NotNullDemo2.builder().id(null).name("Tom").build().validator("GroupAdd_case2", GroupAdd.class);

        // GroupUpdate 验证组
        NotNullDemo2.builder().id("100").name("Tom").build().validator("GroupUpdate_case1", GroupUpdate.class);
        NotNullDemo2.builder().id(null).name("Tom").build().validator("GroupUpdate_case2", GroupUpdate.class);
    }

    private void validator(String caseName, Class<?>...clazzs) {
        validator(caseName, this, clazzs);
    }

    private <T> void validator(String caseName, T obj, Class<?>...clazzs) {
        // 手动调用 API 对定义了注解的字段进行校验
        Validator validator = Validation.byProvider(HibernateValidator.class).configure()
                .failFast(true).buildValidatorFactory().getValidator();
        Set<ConstraintViolation<T>> set = validator.validate(obj, clazzs);
        if (set.size() > 0) {
            // 校验失败
            System.out.println(caseName + " :: Fail :: " + set.iterator().next().getMessage());
        } else {
            // 校验通过
            System.out.println(caseName + " :: Succeed");
        }
    }

}

运行示例,输出如下:

GroupAdd_case1 :: Succeed
GroupAdd_case2 :: Succeed
GroupUpdate_case1 :: Succeed
GroupUpdate_case2 :: Fail :: ID不能为空

payload 属性

用来指定验证错误的有效负载信息的。有效负载信息可以用于传递额外的验证错误信息或者错误级别。

当使用 @NotNull 注解进行验证时,如果字段的值为空,验证框架会生成一个默认的错误信息。但是通过指定 payload 属性,可以自定义验证错误的有效负载信息,使得错误信息更加具体和有用。

payload 属性的类型通常是一个空接口,用来充当标记接口。开发人员可以定义自己的有效负载接口,以便在验证失败时能够根据不同的有效负载类来区分不同的错误级别或提供额外的错误信息。例如:

import javax.validation.constraints.NotNull;

public class User {
    @NotNull(message = "用户名不能为空", payload = Severity.Error.class)
    private String username;

    // 其他字段和方法
}

public interface Severity {
    public class Info {}
    public class Error {}
}

在上面的示例中,@NotNull 注解用于验证 User 类中的 username 字段是否为空。通过指定 payload 属性为不同的有效负载类(Info 和 Error),可以自定义验证错误的有效负载信息。这样在验证失败时,可以根据有效负载类来区分不同的错误级别,从而更好地处理验证错误。

示例:

package com.hxstrive.validation;

import lombok.Builder;
import lombok.Data;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import javax.validation.metadata.ConstraintDescriptor;
import java.util.Set;

/**
 * 注解元素必须不为null
 * @author hxstrive.com
 **/
@Data
@Builder
public class NotNullDemo3 {

    @NotNull(message = "名称不能为空", payload = { ErrorTemplate.Error.class })
    private String name;

    interface ErrorTemplate {
        // 错误级别
        public class Error implements Payload {}
        // 信息级别
        public class Info implements Payload {}
    }

    public static void main(String[] args) {
        NotNullDemo3.builder().name("Tom").build().validator("case1"); // OK
        NotNullDemo3.builder().name("").build().validator("case2"); // OK
        NotNullDemo3.builder().name(null).build().validator("case3"); // Fail
    }

    private void validator(String caseName) {
        validator(caseName, this);
    }

    private <T> void validator(String caseName, T obj) {
        // 手动调用 API 对定义了注解的字段进行校验
        Validator validator = Validation.byProvider(HibernateValidator.class).configure()
                .failFast(true).buildValidatorFactory().getValidator();
        Set<ConstraintViolation<T>> set = validator.validate(obj);
        if (set.size() > 0) {
            // 校验失败
            ConstraintViolation<T> constraintViolation = set.iterator().next();
            ConstraintDescriptor<?> constraintDescriptor = constraintViolation.getConstraintDescriptor();
            // 获取 @NotNull 注解上 payload 属性的值
            constraintDescriptor.getPayload().forEach(payload -> {
                if(payload.equals(ErrorTemplate.Error.class)) {
                    System.out.println(caseName + " :: Error :: " + constraintViolation.getMessage());
                } else if(payload.equals(ErrorTemplate.Info.class)) {
                    System.out.println(caseName + " :: Info :: " + constraintViolation.getMessage());
                } else {
                    System.out.println(caseName + " :: Debug :: " + constraintViolation.getMessage());
                }
            });
        } else {
            // 校验通过
            System.out.println(caseName + " :: Succeed");
        }
    }

}

运行示例,输出如下:

case1 :: Succeed
case2 :: Succeed
case3 :: Error :: 名称不能为空

嵌套注解 @NotNull.List

仔细查看源码,你会发现有这样一段代码:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
	...
	// 内部注解,用于存放多个 @NotNull 自身
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
	@Retention(RUNTIME)
	@Documented
	@interface List {
		NotNull[] value();
	}
}

上面代码中,在 @NotNull 注解中声明了一个内部注解 @List。注意,@NotNull.List 是一个特殊的注解,它用于将多个 @NotNull 注解组合在一起,以便在一个元素上执行多个非空验证。这样可以在一个字段或方法参数上应用多个 @NotNull 注解,以便进行多个非空验证。

示例:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotNull.List;

public class User {
    @NotNull.List({
        @NotNull(message = "用户名不能为空"),
        @NotNull(message = "用户名不能为空", payload = Severity.Error.class)
    })
    private String username;

    // 其他字段和方法
}

在上面的示例中,@NotNull.List 注解用于将两个 @NotNull 注解组合在一起,分别指定了不同的错误信息和有效负载信息。这样在验证时,会同时执行这两个非空验证,并根据各自的条件进行处理。

注意:@NotNull.List 是在 Bean Validation 2.0 中引入的,需要确保项目中使用的 Bean Validation 版本支持 @NotNull.List 注解。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号