Почему отсутствие аннотации не вызывает исключение ClassNotFoundException во время выполнения?

91

Рассмотрим следующий код:

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)любом случае), но для многих других аннотаций кажется, что это могло вызвать различные плохие вещи во время выполнения.

Мэтт МакГенри
источник

Ответы:

90

В более ранних общедоступных проектах JSR-175 (аннотации) обсуждалось, должны ли компилятор и среда выполнения игнорировать неизвестные аннотации, чтобы обеспечить более слабую связь между использованием и объявлением аннотаций. Конкретным примером было использование аннотаций для сервера приложений в EJB для управления конфигурацией развертывания. Если тот же bean-компонент должен быть развернут на другом сервере приложений, было бы удобно, если бы среда выполнения просто игнорировала неизвестные аннотации вместо того, чтобы вызывать NoClassDefFoundError.

Даже если формулировка немного расплывчата, я предполагаю, что поведение, которое вы видите, определено в JLS 13.5.7: «... удаление аннотаций не влияет на правильную компоновку двоичных представлений программ на языке программирования Java. . " Я интерпретирую это так, как если бы аннотации были удалены (недоступны во время выполнения), программа по-прежнему должна связываться и запускаться, и это означает, что неизвестные аннотации просто игнорируются при доступе через отражение.

В первом выпуске Sun JDK 5 это не реализовывалось правильно, но в 1.5.0_06 это было исправлено. Вы можете найти соответствующую ошибку 6322301 в базе данных ошибок, но она не указывает на какие-либо спецификации, кроме утверждения, что «согласно руководству спецификации JSR-175, неизвестные аннотации должны игнорироваться getAnnotations».

Jarnbjo
источник
35

Цитата из 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 гарантирует, что аннотация скомпилирована в двоичный файл, но аннотация, присутствующая в двоичном файле, не обязательно должна быть доступна во время выполнения.

Гийом
источник
9

если у вас действительно есть код, который читает @A и что-то с ним делает, этот код зависит от класса A, и он вызовет ClassNotFoundException.

если нет, т.е. никакой код не заботится конкретно о @A, то можно утверждать, что @A на самом деле не имеет значения.

бесспорный
источник