为什么缺少的注释在运行时不会导致 ClassNotFoundException?

2022-08-31 10:29:41

请考虑以下代码:

答.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)


答案 1

在早期的 JSR-175(注释)公共草案中,讨论了编译器和运行时是否应该忽略未知注释,以便在注释的使用和声明之间提供更宽松的耦合。一个具体的例子是在 EJB 上使用特定于应用程序服务器的注释来控制部署配置。如果同一个 Bean 应该部署在不同的应用程序服务器上,那么如果运行时只是忽略未知的注释,而不是引发 NoClassDefFoundError,那将很方便。

即使措辞有点模糊,我假设你看到的行为在JLS 13.5.7中指定:“......删除注释对Java编程语言中程序的二进制表示的正确链接没有影响。我将此解释为如果删除了注释(在运行时不可用),程序仍应链接并运行,这意味着通过反射访问时会忽略未知注释。

Sun 的 JDK 5 的第一个版本没有正确实现这一点,但在 1.5.0_06 中修复了这个问题。您可以在bug数据库中找到相关的bug 6322301,但它没有指向任何规范,除了声称“根据JSR-175规范线索,未知注释必须由getAnnotations忽略”。


答案 2

引用JLS:

9.6.1.2 保留注释可能仅存在于源代码中,也可能以类或接口的二进制形式存在。二进制文件中存在的注释在运行时可能通过 Java 平台的反射库提供,也可能不可用。

批注类型批注。保留用于在上述可能性中进行选择。如果注释 a 对应于类型 T,并且 T 具有与注释对应的(元)注释 m。保留,然后:

  • 如果 m 有一个元素,其值为注释。RetentionPolicy.SOURCE,则 Java 编译器必须确保 在出现 a 的类或接口的二进制表示形式中不存在 。
  • 如果 m 有一个元素,其值为注释。保留策略.CLASS或注释。RetainPolicy.RUNTIME Java 编译器必须确保 在出现 a 的类或接口的二进制表示形式中表示 a,除非 m 对局部变量声明进行注释。局部变量声明上的批注永远不会保留在二进制表示中。

如果 T 没有对应于注释的(元)注释 m。保留,那么Java编译器必须将T视为它确实具有这样的元注释m,其元素的值为注释。保留策略.CLASS。

因此,RetentionPolicy.RUNTIME 确保将注释编译到二进制文件中,但二进制文件中存在的注释不必在运行时可用。


推荐