【问题标题】:Why doesn't a missing annotation cause a ClassNotFoundException at runtime?为什么缺少注释不会在运行时导致 ClassNotFoundException?
【发布时间】:2011-04-03 19:16:54
【问题描述】:

考虑以下代码:

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)),但对于许多其他注释来说,它似乎可能会导致运行时发生各种不好的事情。

【问题讨论】:

    标签: java annotations


    【解决方案1】:

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

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

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

    【讨论】:

    • 我怀疑 JLS 13.5.7 正在处理其他问题——例如,您可以针对 foo 未注释的库编译代码,如果 @同时注释987654328@。也就是说,我不是 100% 确定,并且感谢您提供指向该链接和 JDK-6322301 的链接! Java 开发人员似乎还打算在反射注解查找过程中针对缺失的类提供一些弹性,即使某些边缘情况可能仍然存在(例如,JDK-8247797)。
    • 更多历史:在 Java 引入类型注释之后,如果在运行时缺少 type 注释,JDK-8152174 可以生成 NullPointerException(在 TypeAnnotationParser.mapTypeAnnotations 中)。这已在 JDK9 更新中得到修复,但在较旧的 JDK 下,这是一个需要注意的额外错误。
    • (是的,JDK-8152174 说“规范要求”注释被忽略。我仍然认为 JLS 13.5.7 没有涵盖这一点。但我注意到像 Method.getAnnotatedReturnType don '不记录任何抛出的异常,而像 AnnotatedElement 这样的 API 记录失败,例如,“尝试通过调用相关的类返回方法来读取类。”)
    【解决方案2】:

    如果您确实有读取@A 并对其进行处理的代码,则该代码依赖于A 类,它会抛出ClassNotFoundException。

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

    【讨论】:

      【解决方案3】:

      引用 JLS:

      9.6.1.2 保留注解可能只存在于源代码中,或者 它们可能以二进制形式存在 一个类或接口。 注释 存在于二进制文件中的可能或 在运行时可能无法通过 Java的反射库 平台。

      注解类型 annotation.Retention 用于选择 在上述可能性中。如果 注解 a 对应一个类型 T, 并且 T 有一个(元)注释 m 对应annotation.Retention, 那么:

      • 如果m有一个值为annotation.RetentionPolicy.SOURCE的元素, 那么 Java 编译器必须确保 a 不存在于二进制文件中 班级的代表或 a出现的界面。
      • 如果 m 有一个值为 annotation.RetentionPolicy.CLASS 的元素,或者 annotation.RetentionPolicy.RUNTIME 一个 Java 编译器必须确保 a 是 以二进制表示 班级的代表或 a出现的界面,除非m 注释局部变量 宣言。本地注释 永远不会保留变量声明 在二进制表示中。

      如果 T 没有(元)注解 m 对应于 annotation.Retention,然后是 Java 编译器必须像对待 T 一样对待它 有这样一个元注释 m 值为的元素 annotation.RetentionPolicy.CLASS。

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

      【讨论】:

        猜你喜欢
        • 2014-11-26
        • 1970-01-01
        • 2015-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多