Java实现自定义验证注解

什么是注解

从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注释)。

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。 通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。

Annotation 可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在 Annotation 的“name=value” 对中。

例如avatar

Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据。

基本的Annotation

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。 用于修饰它支持的程序元素。

三个基本的 Annotation:

  • @Override: 限定重写父类方法, 该注释只能用于方法
  • @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
  • @SuppressWarnings: 抑制编译器警告。

自定义Annotation

定义新的 Annotation 类型使用 @interface 关键字

Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。 其方法名和返回值定义了该成员的名字和类型。

可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字

没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation

下面是java验证的NotNull注解的源码:

avatar

其中所有的成员变量都是以无参方法定义的,default定义了某个成员变量的默认值,这是一个包含成员变量的 Annotation 所以我们也可以称为 元数据 Annotation。

下面是Override注解的源码:

avatar

这个注解没有成员变量,所以我们可以称为标记。而且可以注意的是这个注解的注解(元注解) @Retention是SOURCE类型的,编译器会直接丢弃这种策略的注释。也就是我们编译成class文件的时候是看不见@Override注解的,这个我们后面会讲。

提取 Annotation 信息

JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注释的程序元素。

当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取。

程序可以调用 AnnotatedElement 对象的如下方法来访问 Annotation 信息

avatar

JDK 的元 Annotation

JDK 的元 Annotation 用于修饰其他 Annotation 定义(注解的注解)

  • @Retention注解

    只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值

    • RetentionPolicy.CLASS: 编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注释. 这是默认值
    • RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注释. 程序可以通过反射获取该注释
    • RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
  • @Target注解

    用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。可以参考本文第一张图片。

  • @Documented注解

    用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档

  • @Inherited注解

    被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注释

实现自定义验证注解@IsMobile

参考于实现自定义验证注解

@IsMobile是一个验证是否为11位手机号码的验证注解。

API开发中经常会遇到一些对请求数据进行验证的情况,这时候如果使用注解就有两个好处,一是验证逻辑和业务逻辑分离,代码清晰,二是验证逻辑可以轻松复用,只需要在要验证的地方加上注解就可以。

Java提供了一些基本的验证注解,比如@NotNull、@Size,但是更多情况下需要自定义验证逻辑,这时候就可以自己实现一个验证注解,方法很简单,仅需要两个东西:

  • 一个自定义的注解,并且指定验证器
  • 一个验证器的实现

这个时候我们就可以结合刚刚所学的知识就可以根据前面两部来实现验证注解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
//这个表示该注解能对于类型注解
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
//这个表示在运行时起作用
@Retention(RetentionPolicy.RUNTIME)
//@Constraint是最关键的,它表示这个注解是一个验证注解,并且指定了一个实现验证逻辑的验证器
@Constraint(validatedBy = IsMobileValidator.class)
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
//groups()和payload()也为@Constraint要求,可默认为空
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
  • @Target

    @Target指明这个注解要作用在什么地方,可以是对象、域、构造器等,因为要作用在域上,因此这里可以选择FIELD(这里我是直接所有的可以)。参考了NotNull注解

  • @Retention

    指明生命周期,这里选择RUNTIME

  • @Constraint

    实现验证器最重要的注解,它表示这个注解是一个验证注解,并且指定了一个实现验证逻辑的验证器。

  • message()

    验证失败之后返回的消息。此方法为@Constraint要求

  • groups()和payload()

    groups()和payload()也为@Constraint要求,可默认为空,详细用途可以查看@Constraint文档

下面是验证器类:

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
/**
* @author Lin
* @date 2019-04-20 10:55
**/

public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {

//这里实现ConstraintValidator接口需要重写initialize和isValid方法
/**
* required指这个数据是否必须
*/
private boolean required = false;
@Override
public void initialize(IsMobile constraintAnnotation) {
required=constraintAnnotation.required();
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//如果是必须的,那么就验证是否为手机号
if (required){
return ValidatorUtil.isMobile(value);
}else {
//如果非必须,那么空值和手机号都可以通过验证
if (StringUtils.isEmpty(value)){
return true;
}else {
return ValidatorUtil.isMobile(value);
}
}
}
}

其细节说明在注释中。

这样我们就实现了一个自定义验证注解,我们只需要在要验证的元素的前面加上@IsMobile就行了。

-------------本文结束感谢阅读-------------