In der Vergangenheit wurde „XML“ von den großen Frameworks bevorzugt. Mit zunehmender Größe der Projekte wurden jedoch auch die Inhalte von „XML“ immer komplexer . , Wartungskosten werden höher.
Also schlug jemand eine markierte und stark gekoppelte Konfigurationsmethode vor, „Annotation“. Sie können Methoden mit Anmerkungen versehen, Sie können Klassen mit Anmerkungen versehen, Sie können auch Feldattribute mit Anmerkungen versehen. Wie auch immer, Sie können fast überall Anmerkungen hinzufügen, die konfiguriert werden müssen.
Seit vielen Jahren wird über die beiden unterschiedlichen Konfigurationsmodi „Annotationen“ und „XML“ diskutiert. Jeder hat seine eigenen Vor- und Nachteile Der Grad der Kopplung ist hoch, während für XML im Verhältnis zu Annotationen das Gegenteil gilt.
Das Streben nach geringer Kopplung bedeutet, auf hohe Effizienz zu verzichten. Das Streben nach Effizienz wird unweigerlich auf Kopplung stoßen. Der Zweck dieses Artikels besteht nicht darin, zwischen den beiden zu unterscheiden, sondern die grundlegenden Inhalte im Zusammenhang mit Annotationen in der einfachsten Sprache vorzustellen.
Das Wesen der Annotation
In der Schnittstelle „java.lang.annotation.Annotation“ gibt es einen solchen Satz zur Beschreibung von „Annotation“.
The common interface extended by all annotation types 所有的注解类型都继承自这个普通的接口(Annotation)
Dieser Satz ist etwas abstrakt, drückt aber die Essenz der Annotation aus. Schauen wir uns die Definition einer in JDK integrierten Annotation an:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
Dies ist die Definition der Annotation @Override. Tatsächlich lautet sie im Wesentlichen:
public interface Override extends Annotation{ }
Ja, die Essenz einer Annotation ist eine Schnittstelle, die die Annotation-Schnittstelle erbt. Diesbezüglich können Sie jede Annotationsklasse dekompilieren und erhalten das Ergebnis.
Genau genommen ist eine Anmerkung nur ein spezieller Kommentar. Wenn es keinen Code zum Parsen gibt, ist er möglicherweise nicht einmal so gut wie ein Kommentar.
Es gibt oft zwei Formen des Parsens von Annotationen einer Klasse oder Methode: eine ist das direkte Scannen zur Kompilierungszeit und die andere ist die Laufzeitreflexion. Wir werden später über Reflektion sprechen. Compiler-Scanning bedeutet, dass der Compiler erkennt, dass eine bestimmte Klasse oder Methode während des Kompilierungsprozesses von Bytecode für Java-Code geändert wird, und diese Anmerkungen dann erkennt und eine Verarbeitung durchführt.
Die typische Annotation ist die Annotation @Override. Sobald der Compiler erkennt, dass eine Methode mit der Annotation @Override geändert wurde, prüft der Compiler, ob die Methodensignatur der aktuellen Methode tatsächlich eine Methode der übergeordneten Methode überschreibt Das heißt, es wird verglichen, ob die übergeordnete Klasse die gleiche Methodensignatur hat.
Diese Situation gilt nur für Annotationsklassen, die dem Compiler bereits bekannt sind, wie z. B. mehrere integrierte Annotationen im JDK. Was Ihre benutzerdefinierten Annotationen betrifft, kennt der Compiler die Funktion Ihrer Annotationen nicht. Natürlich weiß ich nicht, wie ich damit umgehen soll. Ich entscheide oft einfach, ob ich es in eine Bytecode-Datei kompilieren soll, basierend auf dem Umfang der Annotation, und das war's.
Meta-Annotation
„Meta-Annotation“ ist eine Annotation, die zum Ändern von Annotationen verwendet wird und normalerweise in der Definition von Annotationen verwendet wird, zum Beispiel:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
Dies wird annotiert von In unserer @Override-Definition können Sie sehen, dass die beiden Annotationen @Target und @Retention das sind, was wir „Meta-Annotationen“ nennen. „Meta-Annotationen“ werden im Allgemeinen verwendet, um Informationen wie den Lebenszyklus und das Ziel einer Annotation anzugeben. . Ich habe gerade eine Reihe der neuesten 0 grundlegenden Einführungs- und Fortgeschrittenen-Tutorials im Jahr 2018 zusammengestellt. Sie können sie durch Hinzufügen von Java to learn q-u-n erhalten: 678, 241, 563. Enthalten sind: Entwicklungstools und Installationspakete sowie System Lern-Roadmap
Es gibt die folgenden „Meta-Annotationen“ in JAVA:
@Target:注解的作用目标 @Retention:注解的生命周期 @Documented:注解是否应当被包含在 JavaDoc 文档中 @Inherited:是否允许子类继承该注解
Unter ihnen wird @Target verwendet, um anzugeben, an wen die geänderte Annotation letztendlich zielen kann, d. h. um anzugeben, Werden Ihre Anmerkungen zum Ändern von Methoden verwendet? Änderungstyp? Es wird auch zum Ändern von Feldattributen verwendet.
@Target ist wie folgt definiert:
Wir können den Wert auf folgende Weise an diesen Wert übergeben:
@Target(value = {ElementType.FIELD})
ist @ dadurch Die durch Target-Annotationen geänderten Annotationen funktionieren nur für Mitgliedsfelder und können nicht zum Ändern von Methoden oder Klassen verwendet werden. Unter diesen ist ElementType ein Aufzählungstyp mit den folgenden Werten:
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上 ElementType.FIELD:允许作用在属性字段上 ElementType.METHOD:允许作用在方法上 ElementType.PARAMETER:允许作用在方法参数上 ElementType.CONSTRUCTOR:允许作用在构造器上 ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上 ElementType.ANNOTATION_TYPE:允许作用在注解上 ElementType.PACKAGE:允许作用在包上
@Retention wird verwendet, um den Lebenszyklus der aktuellen Annotation anzugeben. Seine grundlegende Definition lautet wie folgt:
Ebenso hat es auch ein Wertattribut:
@Retention(value = RetentionPolicy.RUNTIME
Die RetentionPolicy ist hier immer noch ein Aufzählungstyp und hat die folgenden Aufzählungswerte:
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件 RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention-Annotation gibt an Der Lebenszyklus der geänderten Anmerkungen: Eine ist nur während der Kompilierung sichtbar und wird nach der Kompilierung verworfen. Die andere wird vom Compiler in die Klassendatei kompiliert. Unabhängig davon, ob es sich um eine Klasse, eine Methode oder sogar ein Feld handelt, sind sie alle vorhanden sind Attributtabellen, und die virtuelle JAVA-Maschine definiert auch mehrere Annotationsattributtabellen zum Speichern von Annotationsinformationen. Diese Sichtbarkeit kann jedoch nicht in den Methodenbereich gebracht werden und wird beim Laden der Klasse verworfen.
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@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 是通过动态代理机制生成一个实现我们注解(接口)的代理类。
Nachdem wir das Programm ausgeführt haben, sehen wir eine solche Proxy-Klasse im Ausgabeverzeichnis. Nach der Dekompilierung sieht sie so aus:
Die Proxy-Klasse implementiert die Schnittstelle Hello und überschreibt alle ihre Methoden, einschließlich der Wertmethode und der Methoden, die die Schnittstelle Hello von der Annotation-Schnittstelle erbt .
Und wer ist diese wichtige InvocationHandler-Instanz?
AnnotationInvocationHandler ist ein Handler, der speziell zum Verarbeiten von Anmerkungen in JAVA verwendet wird. Das Design dieser Klasse ist ebenfalls sehr interessant.
Hier gibt es einen MemberValues, bei dem es sich um ein Map-Schlüssel-Wert-Paar handelt. Der Schlüssel ist der Name unseres Annotationsattributs und der Wert ist der zugewiesene Wert das Attribut.
Bitte beachten Sie, dass unsere Proxy-Klasse alle Methoden in der Hello-Schnittstelle repräsentiert in der Proxy-Klasse werden hierher übertragen.
var2 zeigt auf die Instanz der aufgerufenen Methode. Hier verwenden wir zunächst die Variable var4, um den prägnanten Namen der Methode zu erhalten, und ermitteln dann mithilfe der Schalterstruktur, wer die aktuell aufrufende Methode ist Weisen Sie var7 einer der vier Hauptmethoden in Annotation einem bestimmten Wert zu.
Wenn die aktuell aufgerufene Methode toString, equal, hashCode, annotationType ist, wurde die Implementierung dieser Methoden in der AnnotationInvocationHandler-Instanz vordefiniert und Sie können sie direkt aufrufen.
Wenn var7 dann nicht mit diesen vier Methoden übereinstimmt, bedeutet dies, dass die aktuelle Methode die durch das benutzerdefinierte Annotationsbyte deklarierte Methode aufruft, z. B. die Wertmethode unserer Hello-Annotation. In diesem Fall wird der diesem Annotationsattribut entsprechende Wert aus unserer Annotationskarte abgerufen.
Tatsächlich fühlt sich das Anmerkungsdesign in JAVA persönlich etwas unmenschlich an. Es ist offensichtlich eine Operation von Attributen und muss mithilfe von Methoden implementiert werden. Wenn Sie anderer Meinung sind, hinterlassen Sie bitte eine Nachricht zur Diskussion.
Lassen Sie uns abschließend das Funktionsprinzip der gesamten Reflexionsannotation zusammenfassen:
Zuerst können wir Annotationsattributen Werte in Form von Schlüssel-Wert-Paaren zuweisen , etwa so: @Hello (value = "hello").
Wenn Sie als Nächstes ein Element mit einer Annotation ändern, scannt der Compiler die Annotationen für jede Klasse oder Methode während der Kompilierung und führt eine grundlegende Prüfung durch, um zu sehen, ob Ihre Annotation schließlich an der aktuellen Position agieren darf , werden die Anmerkungsinformationen in die Attributtabelle des Elements geschrieben.
Wenn Sie dann eine Reflektion durchführen, entnimmt die virtuelle Maschine alle Anmerkungen im RUNTIME-Lebenszyklus und fügt sie in eine Karte ein, erstellt eine AnnotationInvocationHandler-Instanz und übergibt die Karte an diese.
Schließlich verwendet die virtuelle Maschine den dynamischen JDK-Proxy-Mechanismus, um eine zielannotierte Proxy-Klasse zu generieren und den Prozessor zu initialisieren.
Auf diese Weise wird eine Annotationsinstanz erstellt, bei der es sich im Wesentlichen um eine Proxy-Klasse handelt. Sie sollten die Implementierungslogik der Aufrufmethode in AnnotationInvocationHandler verstehen, die den Kern darstellt. In einem Satz gibt den Annotationsattributwert über den Methodennamen zurück.
Das obige ist der detaillierte Inhalt vonEine eingehende Analyse der Grundprinzipien von Annotationen in der JAVA-Entwicklung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!