In the past, "XML" was favored by major frameworks. It completed almost all configurations in the framework in a loosely coupled manner. However, as projects become larger and larger, the content of "XML" becomes more and more complex. , maintenance costs become higher.
So someone proposed a marked and highly coupled configuration method, "annotation". You can annotate methods, you can annotate classes, you can also annotate field attributes. Anyway, you can annotate almost anywhere that needs to be configured.
There has been debate for many years about the two different configuration modes of "annotations" and "XML". Each has its own advantages and disadvantages. Annotations can provide greater convenience and are easy to maintain and modify, but the degree of coupling High, while the opposite is true for XML relative to annotations.
The pursuit of low coupling means abandoning high efficiency, and the pursuit of efficiency will inevitably encounter coupling. The purpose of this article is not to differentiate between the two, but to introduce the basic content related to annotation in the simplest language.
The essence of annotation
There is such a sentence in the "java.lang.annotation.Annotation" interface to describe "annotation".
The common interface extended by all annotation types 所有的注解类型都继承自这个普通的接口(Annotation)
This sentence is a bit abstract, but it tells the essence of annotation. Let’s look at the definition of a JDK built-in annotation:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
This is the definition of the annotation @Override. In fact, it is essentially:
public interface Override extends Annotation{ }
Yes, the essence of an annotation is one that inherits the Annotation interface. interface. Regarding this, you can decompile any annotation class and you will get the result.
In a precise sense, an annotation is nothing more than a special annotation. If there is no code to parse it, it may not even be as good as an annotation.
There are often two forms of parsing annotations of a class or method, one is direct scanning at compile time, and the other is runtime reflection. We will talk about reflection later, and compiler scanning means that the compiler will detect that a certain class or method is modified by some annotations during the process of compiling bytecode for Java code, and then it will detect these annotations. perform some processing.
The typical one is the annotation @Override. Once the compiler detects that a method has been modified with the @Override annotation, the compiler will check whether the method signature of the current method actually overrides a method of the parent class. That is to compare whether the parent class has the same method signature.
This situation only applies to those annotation classes that are already familiar to the compiler, such as several built-in annotations in the JDK. As for your custom annotations, the compiler does not know the function of your annotations. Of course, I don’t know how to deal with it. I often just choose whether to compile it into a bytecode file based on the scope of the annotation, and that’s it.
Meta-annotation
"Meta-annotation" is an annotation used to modify annotations, usually used in the definition of annotations, for example:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
This is our @Override annotation Definition, you can see that the two annotations @Target and @Retention are what we call "meta-annotations". "Meta-annotations" are generally used to specify information such as the life cycle and target of an annotation. . I just compiled a set of the latest 0 basic introductory and advanced tutorials in 2018. I share them selflessly. You can get them by adding Java to learn q-u-n: 678, 241, 563. Included are: development tools and installation packages, and System learning roadmap
There are the following "meta-annotations" in JAVA:
@Target:注解的作用目标 @Retention:注解的生命周期 @Documented:注解是否应当被包含在 JavaDoc 文档中 @Inherited:是否允许子类继承该注解
Among them, @Target is used to indicate who the modified annotation can ultimately act on, that is, to specify , are your annotations used to modify methods? Modification type? It is also used to modify field attributes.
@Target is defined as follows:
We can pass the value to this value in the following ways:
@Target(value = {ElementType.FIELD})
is @ by this The annotations modified by Target annotations will only work on member fields and cannot be used to modify methods or classes. Among them, ElementType is an enumeration type with the following values:
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上 ElementType.FIELD:允许作用在属性字段上 ElementType.METHOD:允许作用在方法上 ElementType.PARAMETER:允许作用在方法参数上 ElementType.CONSTRUCTOR:允许作用在构造器上 ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上 ElementType.ANNOTATION_TYPE:允许作用在注解上 ElementType.PACKAGE:允许作用在包上
@Retention is used to indicate the life cycle of the current annotation. Its basic definition is as follows:
Similarly, it also has a value attribute:
@Retention(value = RetentionPolicy.RUNTIME
The RetentionPolicy here is still an enumeration type, and it has the following enumeration values:
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件 RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention annotation designation The life cycle of the modified annotations, one is only visible during compilation and will be discarded after compilation, the other will be compiled into the class file by the compiler, whether it is a class, method, or even field, they are all There are attribute tables, and the JAVA virtual machine also defines several annotation attribute tables for storing annotation information. However, this visibility cannot be brought to the method area and will be discarded when the class is loaded. The last one is permanently visible. sex.
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
JAVA 的内置三大注解
除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
@Override @Deprecated @SuppressWarnings @Override 注解想必是大家很熟悉的了,它的定义如下: @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。
所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated 的基本定义如下:
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
当然,编译器并不会强制要求你做什么,只是告诉你 JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者。
@SuppressWarnings 主要用来压制 java 的警告,它的基本定义如下:
它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:
public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
这么一段代码,程序启动时编译器会报一个警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
@SuppressWarning(value = "deprecated") public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查。
当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。
自定义注解的相关内容就不再赘述了,比较简单,通过类似以下的语法即可自定义一个注解。
public @interface InnotationName{ }
当然,自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。
注解与反射
上述内容我们介绍了注解使用上的细节,也简单提到,「注解的本质就是一个继承了 Annotation 接口的接口」,现在我们就来从虚拟机的层面看看,注解的本质到底是什么。
首先,我们自定义一个注解类型:
这里我们指定了 Hello 这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。
之前我们说过,虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
RuntimeVisibleAnnotations:运行时可见的注解 RuntimeInVisibleAnnotations:运行时不可见的注解 RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解 RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解 AnnotationDefault:注解类元素的默认值
给大家看虚拟机的这几个注解相关的属性表的目的在于,让大家从整体上构建一个基本的印象,注解在字节码文件中是如何存储的。
所以,对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。
getAnnotation:返回指定的注解 isAnnotationPresent:判定当前元素是否被指定注解修饰 getAnnotations:返回所有的注解 getDeclaredAnnotation:返回本元素的指定注解 getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
方法、字段中相关反射注解的方法基本是类似的,这里不再赘述,我们下面看一个完整的例子。
首先,设置一个虚拟机启动参数,用于捕获 JDK 动态代理类。
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
然后 main 函数。
我们说过,注解本质上是继承了 Annotation 接口的接口,而当你通过反射,也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。
After we run the program, we will see such a proxy class in the output directory. After decompilation, it looks like this:
The proxy class implements the interface Hello and overrides all its methods, including the value method and the methods that the interface Hello inherits from the Annotation interface.
And who is this key InvocationHandler instance?
AnnotationInvocationHandler is a Handler specifically used to process annotations in JAVA. The design of this class is also very interesting.
There is a memberValues here, which is a Map key-value pair. The key is the name of our annotation attribute, and the value is the value assigned to the attribute.
This invoke method is very interesting. Please note that our proxy class proxies all methods in the Hello interface. So any method call in the proxy class will be transferred here.
var2 points to the called method instance, and here we first use the variable var4 to obtain the concise name of the method, and then use the switch structure to determine who is the current calling method. If it is one of the four major methods in Annotation, assign var7 to on a specific value.
If the currently called method is toString, equals, hashCode, annotationType, the implementation of these methods has been predefined in the AnnotationInvocationHandler instance, and you can call it directly.
Then if var7 does not match these four methods, it means that the current method calls the method declared by the custom annotation byte, such as the value method of our Hello annotation. In this case, the value corresponding to this annotation attribute will be obtained from our annotation map.
In fact, I personally feel that the annotation design in JAVA is a bit anti-human. It is obviously an attribute operation and must be implemented using methods. Of course, if you have different opinions, please leave a message to discuss.
Finally, let’s summarize the working principle of the entire reflection annotation:
First, we can assign values to the annotation attributes in the form of key-value pairs, like this: @Hello (value = "hello").
Next, if you modify an element with an annotation, the compiler will scan the annotations on each class or method during compilation and do a basic check to see if your annotation is allowed to act at the current location. Finally, the annotation information is written to the attribute table of the element.
Then, when you perform reflection, the virtual machine takes out all the annotations in the RUNTIME life cycle and puts them into a map, creates an AnnotationInvocationHandler instance, and passes the map to it.
Finally, the virtual machine will use the JDK dynamic proxy mechanism to generate a target annotated proxy class and initialize the processor.
In this way, an annotation instance is created, which is essentially a proxy class. You should understand the implementation logic of the invoke method in AnnotationInvocationHandler, which is the core. In one sentence, returns the annotation attribute value through the method name.
The above is the detailed content of An in-depth analysis of the basic principles of annotations in JAVA development. For more information, please follow other related articles on the PHP Chinese website!