1. Overview
1.概述
In this tutorial, we’re going to get familiar with a seemingly bizarre feature in the Java Programming language: Missing annotations won’t cause any exceptions at runtime.
在本教程中,我们将熟悉Java编程语言中一个看似奇怪的特性。缺少注解不会在运行时引起任何异常。
Then, we’ll dig deeper to see what reasons and rules govern this behavior and what are the exceptions to such rules.
然后,我们将深入挖掘,看看这种行为的原因和规则是什么,以及这种规则的例外情况是什么。
2. A Quick Refresher
2.快速复习
Let’s start with a familiar Java example. There is class A, and then there’s class B, which depends on A:
让我们从一个熟悉的Java例子开始。有一个A类,然后有一个B类,它依赖于A。
public class A {
}
public class B {
public static void main(String[] args) {
System.out.println(new A());
}
}
Now, if we compile these classes and run the compiled B, it’ll print a message on the console for us:
现在,如果我们编译这些类并运行编译后的B,它将在控制台为我们打印一条信息。
>> javac A.java
>> javac B.java
>> java B
A@d716361
However, if we remove the compiled A.class file and re-run class B, we will see a NoClassDefFoundError caused by a ClassNotFoundException:
然而,如果我们删除已编译的A.class文件并重新运行类B,我们会看到NoClassDefFoundError引起的ClassNotFoundException。
>> rm A.class
>> java B
Exception in thread "main" java.lang.NoClassDefFoundError: A
at B.main(B.java:3)
Caused by: java.lang.ClassNotFoundException: A
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 1 more
This happens because the classloader couldn’t find the class file at runtime, even though it was there during compilation. That’s the normal behavior many Java developers expect.
发生这种情况是因为classloader在运行时找不到该类文件,尽管它在编译时就在那里。这是许多Java开发者期望的正常行为。
3. Missing Annotations
3.缺少注解
Now, let’s see what happens with annotations under the same circumstances. In order to do that, we’re going to change the A class to be an annotation:
现在,让我们看看在同样的情况下,注解会发生什么。为了做到这一点,我们要把A类改成一个注释。
@Retention(RetentionPolicy.RUNTIME)
public @interface A {
}
As shown above, Java will retain the annotation information at runtime. After that, it’s time to annotate the class B with A:
如上所示,Java会在运行时保留注释信息。之后,就该用A来注释类B了。
@A
public class B {
public static void main(String[] args) {
System.out.println("It worked!");
}
}
Next, let’s compile and run these classes:
接下来,让我们编译并运行这些类。
>> javac A.java
>> javac B.java
>> java B
It worked!
So, we see that B successfully prints its message on the console, which makes sense, as everything is compiled and wired together so nicely.
因此,我们看到B成功地在控制台打印了它的信息,这是有道理的,因为一切都被编译和连接得很好。
Now, let’s delete the class file for A:
现在,让我们删除A的类文件。
>> rm A.class
>> java B
It worked!
As shown above, even though the annotation class file is missing, the annotated class runs without any exceptions.
如上所示,即使缺少注释类文件,注释类的运行也没有任何异常。
3.1. Annotation with Class Tokens
3.1.用类标记进行注解
To make it even more interesting, let’s introduce another annotation that has a Class<?> attribute:
为了让它更有趣,让我们引入另一个注释,它有一个Class<?>属性。
@Retention(RetentionPolicy.RUNTIME)
public @interface C {
Class<?> value();
}
As shown above, this annotation has an attribute named value with the return type of Class<?>. As an argument for that attribute, let’s add another empty class named D:
如上所示,这个注解有一个名为value的属性,其返回类型为Class<?>。作为该属性的一个参数,让我们添加另一个名为D的空类。
public class D {
}
Now, we’re going to annotate the B class with this new annotation:
现在,我们要用这个新注解来注解B类。
@A
@C(D.class)
public class B {
public static void main(String[] args) {
System.out.println("It worked!");
}
}
When all class files are present, everything should work fine. However, what happens when we delete only the D class file and don’t touch the others? Let’s find out:
当所有的类文件都存在时,一切都应该工作正常。然而,当我们只删除D类文件而不碰其他文件时会发生什么?让我们来了解一下。
>> rm D.class
>> java B
It worked!
As shown above, despite the absence of D at runtime, everything is still working! Therefore, in addition to annotations, the referenced class tokens from attributes are not required to be present at runtime either.
如上所示,尽管在运行时没有D,但一切仍在正常运行!D在运行时没有出现。因此,除了注解之外,来自属性的引用类标记也不需要在运行时出现。
3.2. The Java Language Specification
3.2.Java语言规范
So, we saw that some annotations with runtime retention were missing at runtime but the annotated class was running perfectly. As unexpected as it might sound, this behavior is actually completely fine according to the Java Language Specification, §9.6.4.2:
因此,我们看到一些带有运行时保留的注解在运行时丢失了,但被注解的类却运行得很好。虽然听起来很意外,但根据《Java语言规范》第9.6.4.2节,这种行为实际上是完全没有问题的。
Annotations may be present only in source code, or they may be present in the binary form of a class or interface. An annotation that is present in the binary form may or may not be available at run time via the reflection libraries of the Java SE Platform.
注释可能只存在于源代码中,也可能存在于类或接口的二进制形式中。以二进制形式存在的注解在运行时可能会或可能不会通过Java SE平台的反射库来使用。
Moreover, the JLS §13.5.7 entry also states:
此外,JLS §13.5.7条目还指出。
Adding or removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language.
添加或删除注释对Java编程语言中程序的二进制表示的正确链接没有影响。
The bottom line is, the runtime does not throw exceptions for missing annotations, because the JLS allows it.
底线是,运行时不会因为缺少注解而抛出异常,因为JLS允许这样做。
3.3. Accessing the Missing Annotation
3.3.访问缺失的注释
Let’s change the B class in a way that it retrieves the A information reflectively:
让我们改变B类的方式,让它以反射方式检索A的信息。
@A
public class B {
public static void main(String[] args) {
System.out.println(A.class.getSimpleName());
}
}
If we compile and run them, everything would fine:
如果我们编译并运行它们,一切都会很好。
>> javac A.java
>> javac B.java
>> java B
A
Now, if we remove the A class file and run B, we’ll see the same NoClassDefFoundError caused by a ClassNotFoundException:
现在,如果我们删除A类文件并运行B,我们会看到同样的NoClassDefFoundError,由ClassNotFoundException引起。
Exception in thread "main" java.lang.NoClassDefFoundError: A
at B.main(B.java:5)
Caused by: java.lang.ClassNotFoundException: A
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 1 more
According to JLS, the annotation does not have to be available at runtime. However, when some other code reads that annotation and does something about it (like what we did), the annotation must be present at runtime. Otherwise, we would see a ClassNotFoundException.
根据JLS,注释不一定要在运行时可用。然而,当其他代码读取该注释并对其进行处理时(比如我们所做的),该注释必须在运行时存在。否则,我们会看到一个ClassNotFoundException。
4. Conclusion
4.总结
In this article, we saw how some annotations can be absent at runtime, even though they’re part of the binary representation of a class.
在这篇文章中,我们看到一些注解在运行时是如何缺席的,尽管它们是类的二进制表示的一部分。
As usual, all the examples are available over on GitHub.
像往常一样,所有的例子都可以在GitHub上找到。