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

Mat*_*nry 82 java annotations

请考虑以下代码:

A.java:

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

@Retention(RetentionPolicy.RUNTIME)
@interface A{}
Run Code Online (Sandbox Code Playgroud)

C.java:

import java.util.*;

@A public class C {
        public static void main(String[] args){
                System.out.println(Arrays.toString(C.class.getAnnotations()));
        }
}
Run Code Online (Sandbox Code Playgroud)

编译和运行按预期工作:

$ javac *.java
$ java -cp . C
[@A()]
Run Code Online (Sandbox Code Playgroud)

但是考虑一下:

$ rm A.class
$ java -cp . C
[]
Run Code Online (Sandbox Code Playgroud)

我会期望它抛出一个ClassNotFoundException,因为@A缺少.但相反,它会默默地删除注释.

这种行为是在JLS的某个地方记录的,还是Sun的JVM的怪癖?它的基本原理是什么?

这样的事情似乎很方便javax.annotation.Nonnull(看起来应该是这样@Retention(CLASS)的)但是对于许多其他注释来说,似乎它可能会导致在运行时发生各种不好的事情.

jar*_*bjo 85

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

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

Sun的JDK 5的第一个版本没有正确实现,但它已在1.5.0_06中修复.您可以在错误数据库中找到相关的错误6322301,但它没有指出任何规范,只是声称"根据JSR-175规范的引导,getAnnotations必须忽略未知的注释".


Gui*_*ume 32

引用JLS:

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

注释类型注释.注释用于在上述可能性中进行选择.如果注释a对应于类型T,并且T具有对应于注释的(元)注释m,则:

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

如果T没有对应于annotation.Retention的(meta-)注释m,则Java编译器必须将T视为具有这样的元注释m,其元素的值为annotation.RetentionPolicy.CLASS.

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


irr*_*ble 7

如果您实际上有代码读取@A并对其执行某些操作,则代码依赖于类A,并且它将抛出ClassNotFoundException.

如果没有,即没有代码特别关注@A,那么可以说@A并不重要.