JVM 在运行时未正确处理可重复的注释



Windows 10平台上的JDK 14似乎无法在方法级别处理可重复的注释。此外,当可重复注释实际上存在时,API 调用 isAnnotationPresent() 返回 false。下面的示例演示了这些行为。

Color.java 包含可重复批注的声明,Shirt 类使用该批注。 颜色.java:

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Repeatable(Colors.class)
@interface Color {
String name();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Colors {
Color[] value();
}
@Color(name = "red") @Color(name = "blue") @Color(name = "green")
class Shirt {
//@Color(name = "red") @Color(name = "blue") @Color(name = "green")
public void print(){
System.out.println("The @repeatable annotation at the method level");
}
}
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class TestRepeatable {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
testTypeWithIsAnnotationPresent();
testTypeWtihoutIsAnnotationPresent();
testMethodWithIsAnnotationPresent();
testMethodWithoutIsAnnotationPresent();
}
public static void testTypeWithIsAnnotationPresent() {
boolean flag;
System.out.println("Processing testTypeWithIsAnnotationPresent() method..... ");
System.out.println();
String colorClassName = Color.class.getSimpleName();
String shirtClassName = Shirt.class.getSimpleName();
Class<Shirt> obj = Shirt.class;
flag = obj.isAnnotationPresent(Color.class);
if (flag) {
System.out.println("The " + colorClassName + " annotation is present at the TYPE level in " + shirtClassName + " class");
Color[] colorArray = Shirt.class.getAnnotationsByType(Color.class);
for (Color color : colorArray) {
System.out.println(color.name());
}
System.out.println("Test completed successfuly");
} else {
System.out.println("There are no "+colorClassName+" annotations declared at the TYPE level in "
+shirtClassName+ " class");
System.out.println();
}
}//testTypeWithIsAnnotationPresent()
public static void testTypeWtihoutIsAnnotationPresent() {
System.out.println();
System.out.println("Processing testTypeWtihoutIsAnnotationPresent().....");
System.out.println("In this scenario, the system is not calling the API call "isAnnotationPresent()"");
Color[] colorArray = Shirt.class.getAnnotationsByType(Color.class);
for (Color color : colorArray) {
System.out.println(color.name());
}
}//testTypeWtihoutIsAnnotationPresent()
public static void testMethodWithIsAnnotationPresent() {
System.out.println("Processing testMethodWithIsAnnotationPresent() method..... ");
System.out.println();
String colorClassName = Color.class.getSimpleName();
String shirtClassName = Shirt.class.getSimpleName();
Class<Shirt> obj = Shirt.class;
Method[] method = obj.getDeclaredMethods();
int length = method.length;
if (length < 1) {
System.out.println("The are not methods declared in " + shirtClassName + " class");
}//outter if
else {
for (int i = 0; i < length; i++) {
if (method[i].isAnnotationPresent(Color.class)) {
Annotation annotation = method[i].getAnnotation(Color.class);
Color c = (Color) annotation;
//additional code needed here to process the annotations
}// inner if
else {
System.out.println("The " + method[i].getName() + " method in " + shirtClassName
+ " class does not have any annotation of type " + colorClassName);
continue;
}// inner else
}//for
}// outter else
}//testMethodWithIsAnnotationPresent()
public static void testMethodWithoutIsAnnotationPresent() {
System.out.println();
System.out.println("In testMethodWithoutIsAnnotationPresent()");
Class<Shirt> obj = Shirt.class;
Method[] method = obj.getDeclaredMethods();
int length = method.length;
if (length < 1) {
System.out.println("The are no methods declared in " + Shirt.class.getSimpleName() + " class");
}//outter if
else {
System.out.println("There are " + length + " methods present in class " + Shirt.class.getSimpleName());
for (int i = 0; i < length; i++) {
Annotation annotation = method[i].getAnnotation(Color.class);
Color c = (Color) annotation;
if (c == null) {
System.out.println("No annotations at the method level");
} else {
System.out.print("The annotation for " + method[i].getName() + "is " + Color.class.getSimpleName());
System.out.println("With name " + c.name());
}
}//for
}
}//testMethodWithoutIsAnnotationPresent()
}// end

首先,运行 TestRepeatable 并观察输出。第一种方法"testTypeWithIsAnnotationPresent()"使用API调用"isAnnotationPresent()"来确定API调用在Shirt类(它们是)中在TYPE级别使用的颜色注释是否返回false。我不知道为什么? 第二种方法"testTypeWtihoutIsAnnotationPresent()"不使用API调用"isAnnotationPresent()",而只是使用"getAnnotationsByType()"并打印按预期工作的详细信息。 第二次运行 TestRepeatable,但修改颜色.java如下所示: 这是修改后的颜色.java:

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Repeatable(Colors.class)
@interface Color {
String name();
}
@Retention(RetentionPolicy.RUNTIME)
//@Target(ElementType.TYPE)
@Target(ElementType.METHOD)
@interface Colors {
Color[] value();
}
//@Color(name = "red") @Color(name = "blue") @Color(name = "green")
class Shirt {
@Color(name = "Cyan") @Color(name = "Pink") @Color(name = "Yellow")
public void print(){
System.out.println("The @repeatable annotation at the method level");
}
}

在这种情况下,TestRepeatable 类"testMethodWithIsAnnotationPresent()"中的第三个方法通过使用 API 调用 isAnnotationPresent() 查询 "print()" 方法声明是否存在@Color注释(其中有 3 个),但是,调用返回 false,我又不知道为什么?

在最后一个场景中,方法testMethodWithoutIsAnnotationPresent()不使用API调用isAnnotationPresent(),而是尝试从Method对象获取颜色注释(其中有3个),但没有找到任何注释。

总之,也许代码的行为正确,我对@Repeatable注释的理解是错误的,或者可能是相反的。任何解释都会很棒。

谢谢

可重复的注释

以下是 §9.7.5 相同类型的Java 语言规范(JLS) 的多个注释的摘录:

如果声明上下文或类型上下文具有可重复注释类型的多个注释T,则就好像上下文没有显式声明的类型为T的注释,并且有一个隐式声明的包含注释类型的注释为T

隐式声明的注释称为容器注释,上下文中出现的类型T的多个注释称为基本注释。容器注释的(数组类型)value元素的元素是它们在上下文中出现的从左到右顺序的所有基本注释。

这意味着,如果您有以下注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FooContainer.class)
public @interface Foo {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FooContainer {
Foo[] value();
}

然后这个:

// Important: There's more than one of the repeatable annotation
@Foo("baz")
@Foo("qux")
public class Bar {}

实际上编译为好像编写了以下内容:

@FooContainer({@Foo("baz"), @Foo("qux")})
public class Bar {}

换句话说,Bar类不存在任何@Foo注释。当前注释@FooContainer具有保存@Foo注释的数组元素 - 使@Foo注释"间接存在"。您可以通过检查#getAnnotations()返回的数组来查看这一点。在查看javap的输出时,您也可以看到这一点:

SourceFile: "Bar.java"
RuntimeVisibleAnnotations:
0: #17(#18=[@#19(#18=s#20),@#19(#18=s#21)])
@FooContainer(
value=[@Foo(
value="baz"
),@Foo(
value="qux"
)]
)

请注意,尽管此答案侧重于类注释,但方法注释也可以看到相同的行为。


反射和可重复注释

这是AnnotatedElement#isAnnotationPresent(Class)的Javadoc:

如果此元素上存在指定类型的批注,则返回 true,否则返回 false。此方法主要用于方便访问标记注释。

此方法返回的真值等效于:getAnnotation(annotationClass) != null

如前所述,当可重复批注包装在其容器批注中时,它不存在。AnnotatedElement#getAnnotations()方法具有相同的行为;它返回当前批注,但不返回间接存在关联的批注。AnnotatedElement类 Javadoc 解释了注解的存在关联等意味着什么。

AnnotatedElement#getAnnotationsByType(Class)的Javadoc说:

返回与此元素关联的批注。如果没有与此元素关联的注释,则返回值是长度为 0 [零] 的数组。此方法与getAnnotation(Class)的区别在于,此方法检测其参数是否为可重复的注释类型(JLS 9.6),如果是,则尝试通过"查看"容器注释来查找该类型的一个或多个注释[强调添加]。此方法的调用方可以自由修改返回的数组;它对返回给其他调用方的数组没有影响。

强调的部分解释了为什么此方法可以找到您的重复注释。

因此,如果您想知道的是是否存在/关联一个或多个可重复注释,那么我相信这是最简洁的方法:

boolean hasFoo = Bar.class.getAnnotationsByType(Foo.class).length != 0;
<小时 />

您的代码

关于代码的小说明:@Color批注的保留策略为CLASS(默认值),而@Colors批注的保留策略为RUNTIME。虽然这种设置是完全合法的,但我认为这很奇怪。它也可能令人困惑。如果只有一个@Color则无法使用反射进行查询,但如果有多个@Color,则可以通过隐式@Colors使用反射查询它们。在我看来,最好对可重复注释和容器注释使用相同的保留策略。

最新更新