¿Por qué una anotación faltante no causa una ClassNotFoundException en tiempo de ejecución?

91

Considere el siguiente código:

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

La compilación y ejecución funciona como se esperaba:

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

Pero luego considere esto:

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

Hubiera esperado que arrojara un ClassNotFoundException, ya @Aque falta. Pero en cambio, deja caer silenciosamente la anotación.

¿Este comportamiento está documentado en alguna parte del JLS o es una peculiaridad del JVM de Sun? ¿Cuál es la razón fundamental para ello?

Parece conveniente para cosas como javax.annotation.Nonnull(que parece que debería haber sido de @Retention(CLASS)todos modos), pero para muchas otras anotaciones parece que podría causar que sucedan varias cosas malas en tiempo de ejecución.

Matt McHenry
fuente

Respuestas:

90

En los borradores públicos anteriores para JSR-175 (anotaciones), se discutió si el compilador y el tiempo de ejecución deberían ignorar las anotaciones desconocidas, para proporcionar un acoplamiento más flexible entre el uso y la declaración de anotaciones. Un ejemplo específico fue el uso de anotaciones específicas del servidor de aplicaciones en un EJB para controlar la configuración de implementación. Si el mismo bean se debe implementar en un servidor de aplicaciones diferente, hubiera sido conveniente que el tiempo de ejecución simplemente ignorara las anotaciones desconocidas en lugar de generar un NoClassDefFoundError.

Incluso si la redacción es un poco vaga, supongo que el comportamiento que está viendo está especificado en JLS 13.5.7: "... eliminar anotaciones no tiene ningún efecto en el enlace correcto de las representaciones binarias de programas en el lenguaje de programación Java . " Interpreto esto como si las anotaciones se eliminan (no están disponibles en tiempo de ejecución), el programa aún debería vincularse y ejecutarse y esto implica que las anotaciones desconocidas simplemente se ignoran cuando se accede a través de la reflexión.

La primera versión del JDK 5 de Sun no implementó esto correctamente, pero se corrigió en 1.5.0_06. Puede encontrar el error relevante 6322301 en la base de datos de errores, pero no apunta a ninguna especificación, excepto que afirma que "de acuerdo con el plomo de especificaciones JSR-175, las anotaciones desconocidas deben ser ignoradas por getAnnotations".

jarnbjo
fuente
35

Citando el JLS:

9.6.1.2 Las anotaciones de retención pueden estar presentes solo en el código fuente, o pueden estar presentes en forma binaria de una clase o interfaz. Una anotación que está presente en el binario puede o no estar disponible en tiempo de ejecución a través de las bibliotecas reflectantes de la plataforma Java.

El tipo de anotación annotation.Retention se utiliza para elegir entre las posibilidades anteriores. Si una anotación a corresponde a un tipo T, y T tiene una (meta-) anotación m que corresponde a una anotación. Retención, entonces:

  • Si m tiene un elemento cuyo valor es annotation.RetentionPolicy.SOURCE, un compilador de Java debe asegurarse de que a no esté presente en la representación binaria de la clase o interfaz en la que aparece.
  • Si m tiene un elemento cuyo valor es annotation.RetentionPolicy.CLASS, o annotation.RetentionPolicy.RUNTIME, un compilador Java debe asegurarse de que a esté representado en la representación binaria de la clase o interfaz en la que aparece a, a menos que m anote una declaración de variable local . Una anotación en una declaración de variable local nunca se retiene en la representación binaria.

Si T no tiene una (meta-) anotación m que corresponda a annotation.Retention, entonces un compilador Java debe tratar a T como si tuviera una meta-anotación m con un elemento cuyo valor es annotation.RetentionPolicy.CLASS.

Entonces, RetentionPolicy.RUNTIME asegura que la anotación se compila en el binario, pero una anotación presente en el binario no tiene que estar disponible en tiempo de ejecución.

Guillaume
fuente
9

Si realmente tiene un código que lee @A y hace algo con él, el código tiene una dependencia de la clase A y arrojará ClassNotFoundException.

si no, es decir, ningún código se preocupa específicamente por @A, entonces es discutible que @A realmente no importe.

irrefutable
fuente