考虑以下代码:
A.java:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface A{}
C.java:
import java.util.*;
@A public class C {
public static void main(String[] args){
System.out.println(Arrays.toString(C.class.getAnnotations()));
}
}
编译和运行按预期工作:
$ javac *.java
$ java -cp . C
[@A()]
但再考虑一下:
$ rm A.class
$ java -cp . C
[]
我预计它会抛出 ClassNotFoundException
,因为缺少 @A
。但相反,它会默默地删除注释。
这种行为是在某处的 JLS 中记录的,还是 Sun 的 JVM 的怪癖?它的理由是什么?
对于 javax.annotation.Nonnull
之类的东西似乎很方便(无论如何它似乎应该是 @Retention(CLASS)
),但对于许多其他注释来说,它似乎可能会导致在运行时发生各种不好的事情。
在 JSR-175(注解)的早期公共草案中,讨论了编译器和运行时是否应该忽略未知注解,以便在注解的使用和声明之间提供更松散的耦合。一个具体的例子是在 EJB 上使用应用程序服务器特定的注释来控制部署配置。如果同一个 bean 应该部署在不同的应用程序服务器上,如果运行时简单地忽略未知注释而不是引发 NoClassDefFoundError 会很方便。
即使措辞有点模糊,我假设您看到的行为在 JLS 13.5.7 中指定:“...删除注释对 Java 编程语言中程序的二进制表示的正确链接没有影响。 "我将此解释为好像注释已删除(在运行时不可用),程序仍应链接并运行,这意味着在通过反射访问时会简单地忽略未知注释。
Sun 的 JDK 5 的第一个版本没有正确实现这一点,但在 1.5.0_06 中已修复。您可以在 bug 数据库中找到相关的 bug 6322301,但它没有指向任何规范,只是声称“根据 JSR-175 规范导引,getAnnotations 必须忽略未知注释”。
引用 JLS:
9.6.1.2 保留 注解可能只存在于源代码中,或者它们可能以类或接口的二进制形式存在。通过 Java 平台的反射库,二进制文件中存在的注释可能在运行时可用,也可能不可用。注释类型 annotation.Retention 用于在上述可能性中进行选择。如果注解 a 对应于类型 T,并且 T 具有对应于 annotation.Retention 的(元)注解 m,则:如果 m 具有值为 annotation.RetentionPolicy.SOURCE 的元素,则 Java 编译器必须确保a 不存在于出现 a 的类或接口的二进制表示中。如果 m 具有值为 annotation.RetentionPolicy.CLASS 或 annotation.RetentionPolicy.RUNTIME 的元素,Java 编译器必须确保 a 以出现 a 的类或接口的二进制表示形式表示,除非 m 注释局部变量声明.局部变量声明上的注释永远不会保留在二进制表示中。如果 T 没有对应于 annotation.Retention 的(元)注释 m,那么 Java 编译器必须将 T 视为它确实具有这样的元注释 m,其元素的值为 annotation.RetentionPolicy.CLASS。
因此 RetentionPolicy.RUNTIME 确保注释被编译到二进制文件中,但二进制文件中存在的注释不必在运行时可用
如果您实际上有读取@A 并对其执行某些操作的代码,则该代码依赖于类A,它将抛出ClassNotFoundException。
如果不是,即没有代码特别关心@A,那么@A 并不重要。
foo
的库编译代码,并且如果同时注释foo
,则以后仍然可以依赖它工作。也就是说,我不是 100% 确定,并且感谢您提供指向该链接和 JDK-6322301 的链接! Java 开发人员似乎还打算在反射注解查找过程中针对丢失的类提供一些弹性,即使某些边缘情况可能会到处存在(例如,JDK-8247797)。NullPointerException
(在TypeAnnotationParser.mapTypeAnnotations
中)。这在 JDK9 更新中得到了修复,但在较旧的 JDK 下,这是一个需要注意的额外错误。Method.getAnnotatedReturnType
这样的 API 不会记录任何抛出的异常,而像AnnotatedElement
这样的 API 会记录失败,例如,“尝试通过调用相关的类返回方法来读取类。”)