[Java] 浅析可重复注解(Repeatable Annotation) 是如何实现的

166 阅读4分钟

浅析 java 中的可重复注解(Repeatable Annotation)是如何实现的

要点

使用可重复注解时,总是需要定义对应的 containing annotation

假设我们定义了可重复注解 A(而且它的 @RentetionvalueRetentionPolicy.RUNTIME),它的 containing annotationCA

  • 如果在类 C 上使用了可重复注解 A 恰好 1 次,那么
    1. C.class 中会有 RuntimeVisibleAnnotations 属性
    2. A 会出现在这个 RuntimeVisibleAnnotations 属性中
  • 如果在类 C 上使用了可重复注解 A 不止 1 次,那么
    1. C.class 中会有 RuntimeVisibleAnnotations 属性
    2. CA 会出现在这个 RuntimeVisibleAnnotations 属性中

正文

JDK 8 支持可重复注解(Repeatable Annotation)。在 Java Language Specification 中的 9.6.3. Repeatable Annotation Interfaces 小节 提到 ⬇️

An annotation interface A is repeatable if its declaration is (meta-)annotated with an @Repeatable annotation (§9.6.4.8) whose value element indicates a containing annotation interface of A.

大意是说,如果注解 A 上有 @Repeatable 元注解且 @Repeatablevalue 的值是 A 的一个 containing annotation,则 A 是可重复注解。

Java Language Specification 中的 9.7.5. Multiple Annotations of the Same Interface 小节 提到 ⬇️

If a declaration context or type context has multiple annotations of a repeatable annotation interface A, then it is as if the context has no explicitly declared annotations of interface A and one implicitly declared annotation of the containing annotation interface of A.

image.png

大意是说,现在有可重复注解 A,如果我们使用了 多个 A 注解,那么实际的效果就像是

  • 没有显式使用 A 注解
  • 隐式使用 A 注解的 containing annotation

这里的措辞很严谨,用了 multiple,所以对于 A 注解只出现一次的情况并不适用。 我们可以分别写点代码来验证。

代码

情形 1: 可重复注解出现次数超过 1 的情况

我写了一个简单的例子,请将以下代码保存为 PrimeNumber.java ⬇️

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Sample(value = 2)
@Sample(value = 3)
@Sample(value = 5)
@Sample(value = 7)
public class PrimeNumber {

    public static void main(String[] args) throws NoSuchMethodException {
        // 下面这行的 sample 会是 null
        Sample sample = PrimeNumber.class.getAnnotation(Sample.class);
        System.out.println(sample == null);

        // 下面这行的 samples 中会有 4 个元素
        Sample[] samples = PrimeNumber.class.getAnnotationsByType(Sample.class);
        System.out.println(samples.length);
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Samples.class)
@interface Sample {
    int value();
}

@Retention(RetentionPolicy.RUNTIME)
@interface Samples {
    Sample[] value();
}

这里的 @Sample 注解是可重复注解,而 Samples 是对应的 containing annotation

以下命令可以编译 PrimeNumber.java 并运行其中的 main(...) 方法。

javac PrimeNumber.java
java PrimeNumber

运行结果如下 ⬇️

true
4

在编译之后,我们会得到 PrimeNumber.class 文件,用以下命令可以查看 PrimeNumber.class 的内容 ⬇️

javap -v -p PrimeNumber

完整的结果有点长,结尾部分的内容如下 ⬇️

RuntimeVisibleAnnotations:
  0: #51(#52=[@#53(#52=I#54),@#53(#52=I#55),@#53(#52=I#56),@#53(#52=I#57)])
    Samples(
      value=[@Sample(
        value=2
      ),@Sample(
        value=3
      ),@Sample(
        value=5
      ),@Sample(
        value=7
      )]
    )

从中可以看出,在 RuntimeVisibleAnnotations 这个属性中,保存的是 @Samples 注解(这个注解中包含了 4@Sample 注解)。

显式使用 containing annotation 的例子

我们也可以直接使用 @Samples 注解。 请将以下代码保存为 PrimeNumber2.java

@Samples(value = {
        @Sample(value = 2),
        @Sample(value = 3),
        @Sample(value = 5),
        @Sample(value = 7)
})
public class PrimeNumber2 {
}

用以下命令可以编译 PrimeNumber2.java

javac PrimeNumber2.java

编译后,我们会得到 PrimeNumber2.class 文件,用以下命令可以查看 PrimeNumber2.class 的内容 ⬇️

javap -v -p PrimeNumber2

完整的结果有点长,结尾部分的内容如下 ⬇️

RuntimeVisibleAnnotations:
  0: #14(#15=[@#16(#15=I#17),@#16(#15=I#18),@#16(#15=I#19),@#16(#15=I#20)])
    Samples(
      value=[@Sample(
        value=2
      ),@Sample(
        value=3
      ),@Sample(
        value=5
      ),@Sample(
        value=7
      )]
    )

这里的 RuntimeVisibleAnnotations 属性的内容,和 PrimeNumber.classRuntimeVisibleAnnotations 属性的内容是一致的。由此可见,以下两种情况在 class 中的表示方式是一致的

  • 直接使用可重复注解
  • 显式使用可重复注解的 containing annotation
情形 2: 可重复注解恰好出现一次的情况

可以在 PrimeNumber.java 的基础上略作调整,让 @Sample 注解只出现一次 ⬇️

@Sample(value = 2)
public class PrimeNumber3 {

    public static void main(String[] args) throws NoSuchMethodException {
        // 下面这行的 sample 不是 null
        Sample sample = PrimeNumber3.class.getAnnotation(Sample.class);
        System.out.println(sample == null);

        // 下面这行的 samples 中会有 1 个元素
        Sample[] samples = PrimeNumber3.class.getAnnotationsByType(Sample.class);
        System.out.println(samples.length);
    }
}

用以下命令可以编译 PrimeNumber3.java 并运行其中的 main(...) 方法 ⬇️

javac PrimeNumber3.java
java PrimeNumber3

运行结果如下

false
1

从运行结果可以看出来,这次 sample 变量的值 不是 null。我们可以用以下的命令来查看 PrimeNumber3.class 文件的详细内容。

javap -v -p PrimeNumber3

完整的结果有点长,在结果的结尾可以看到如下内容

RuntimeVisibleAnnotations:
  0: #51(#52=I#53)
    Sample(
      value=2
    )

这个结果也可以说明,当 @Sample 注解只出现一次时,class 文件中保存的是 @Sample 注解(而不是 @Samples 这个 containing annotation)。

参考资料