Dlaczego brakująca adnotacja nie powoduje wyjątku ClassNotFoundException w czasie wykonywania?

91

Rozważ następujący kod:

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()));
        }
}

Kompilowanie i uruchamianie działa zgodnie z oczekiwaniami:

$ javac *.java
$ java -cp . C
[@A()]

Ale rozważ to:

$ rm A.class
$ java -cp . C
[]

Spodziewałbym się, że wyrzuci ClassNotFoundException, ponieważ @Abrakuje. Ale zamiast tego po cichu odrzuca adnotację.

Czy to zachowanie zostało gdzieś udokumentowane w JLS, czy jest to dziwactwo JVM firmy Sun? Jakie jest tego uzasadnienie?

Wydaje się to wygodne w przypadku takich rzeczy javax.annotation.Nonnull(co wydaje się, że i tak powinno być @Retention(CLASS)), ale w przypadku wielu innych adnotacji wydaje się, że może powodować różne złe rzeczy w czasie wykonywania.

Matt McHenry
źródło

Odpowiedzi:

90

We wcześniejszych publicznych wersjach roboczych JSR-175 (adnotacje) omówiono, czy kompilator i środowisko wykonawcze powinny ignorować nieznane adnotacje, aby zapewnić luźniejsze sprzężenie między użyciem a deklaracją adnotacji. Konkretnym przykładem było użycie adnotacji specyficznych dla serwera aplikacji w komponencie EJB do kontrolowania konfiguracji wdrażania. Gdyby ten sam komponent bean został wdrożony na innym serwerze aplikacji, byłoby wygodne, gdyby środowisko wykonawcze po prostu zignorowało nieznane adnotacje zamiast zgłaszać NoClassDefFoundError.

Nawet jeśli sformułowanie jest trochę niejasne, zakładam, że zachowanie, które widzisz, jest określone w JLS 13.5.7: "... usunięcie adnotacji nie ma wpływu na prawidłowe powiązanie binarnych reprezentacji programów w języku programowania Java ”. Interpretuję to tak, jakby adnotacje zostały usunięte (niedostępne w czasie wykonywania), program powinien nadal łączyć się i działać, co oznacza, że ​​nieznane adnotacje są po prostu ignorowane, gdy uzyskuje się do nich dostęp poprzez odbicie.

Pierwsze wydanie JDK 5 firmy Sun nie zaimplementowało tego poprawnie, ale zostało to naprawione w wersji 1.5.0_06. Możesz znaleźć odpowiedni błąd 6322301 w bazie danych błędów, ale nie wskazuje on żadnych specyfikacji poza twierdzeniem, że „zgodnie ze specyfikacją JSR-175, nieznane adnotacje muszą być ignorowane przez getAnnotations”.

jarnbjo
źródło
35

Cytując JLS:

9.6.1.2 Adnotacje retencyjne mogą występować tylko w kodzie źródłowym lub mogą występować w postaci binarnej klasy lub interfejsu. Adnotacja obecna w pliku binarnym może, ale nie musi, być dostępna w czasie wykonywania za pośrednictwem bibliotek refleksyjnych platformy Java.

Adnotacja typu adnotacja Retention służy do wyboru spośród powyższych możliwości. Jeśli adnotacja a odpowiada typowi T, a T ma (meta) adnotację m, która odpowiada adnotacji.

  • Jeśli m ma element, którego wartością jest adnotacja.RetentionPolicy.SOURCE, kompilator języka Java musi upewnić się, że w binarnej reprezentacji klasy lub interfejsu, w którym występuje a, nie występuje a.
  • Jeśli m ma element, którego wartością jest adnotacja.RetentionPolicy.CLASS lub annotation.RetentionPolicy.RUNTIME, kompilator Java musi upewnić się, że a jest reprezentowane w binarnej reprezentacji klasy lub interfejsu, w którym występuje a, chyba że m zawiera adnotacje w deklaracji zmiennej lokalnej . Adnotacja w deklaracji zmiennej lokalnej nigdy nie jest zachowywana w reprezentacji binarnej.

Jeśli T nie ma (meta) adnotacji m, która odpowiada adnotacji.Retention, wówczas kompilator Java musi traktować T tak, jakby miał taką meta-adnotację m z elementem, którego wartością jest adnotacja.RetentionPolicy.CLASS.

Dlatego RetentionPolicy.RUNTIME zapewnia, że ​​adnotacja jest wkompilowana do pliku binarnego, ale adnotacja obecna w pliku binarnym nie musi być dostępna w czasie wykonywania

Guillaume
źródło
9

jeśli faktycznie masz kod, który czyta @A i coś z nim robi, kod jest zależny od klasy A i wyrzuci ClassNotFoundException.

jeśli nie, tj. żaden kod nie zajmuje się konkretnie @A, można argumentować, że @A tak naprawdę nie ma znaczenia.

nieoceniony
źródło