Dans le passé, "XML" était favorisé par les principaux frameworks. Il complétait presque toutes les configurations du framework de manière faiblement couplée. Cependant, à mesure que les projets deviennent de plus en plus grands, le contenu de "XML" devient de plus en plus complexe. . , les coûts de maintenance deviennent plus élevés.
Quelqu'un a donc proposé une méthode de configuration marquée et hautement couplée, "l'annotation". Vous pouvez annoter des méthodes, vous pouvez annoter des classes, vous pouvez également annoter des attributs de champ. Quoi qu'il en soit, vous pouvez annoter presque partout où cela doit être configuré.
Il y a un débat depuis de nombreuses années sur les deux modes de configuration différents "annotations" et "XML". Chacun a ses propres avantages et inconvénients. Les annotations peuvent offrir une plus grande commodité et sont faciles à maintenir et à modifier, mais. le degré de couplage Élevé, alors que l'inverse est vrai pour XML par rapport aux annotations.
La recherche d'un faible couplage signifie abandonner le haut rendement. La recherche de l'efficacité rencontrera inévitablement le couplage. Le but de cet article n’est pas de faire la différence entre les deux, mais de présenter le contenu de base lié à l’annotation dans le langage le plus simple.
L'essence de l'annotation
Il existe une telle phrase dans l'interface "java.lang.annotation.Annotation" pour décrire "l'annotation".
The common interface extended by all annotation types 所有的注解类型都继承自这个普通的接口(Annotation)
Cette phrase est un peu abstraite, mais elle exprime l'essence de l'annotation. Regardons la définition d'une annotation intégrée au JDK :
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
Voici la définition de l'annotation @Override En fait, c'est essentiellement :
public interface Override extends Annotation{ }
Oui, l'essence de l'annotation est un héritage. Une interface vers l'interface Annotation. À ce propos, vous pouvez décompiler n’importe quelle classe d’annotation et vous obtiendrez le résultat.
Dans un sens précis, une annotation n'est qu'un commentaire spécial s'il n'y a pas de code pour l'analyser, elle peut même ne pas être aussi bonne qu'un commentaire.
Il existe souvent deux formes d'analyse des annotations d'une classe ou d'une méthode, l'une est l'analyse directe au moment de la compilation et l'autre est la réflexion à l'exécution. Nous parlerons de réflexion plus tard, et l'analyse du compilateur signifie que le compilateur détectera qu'une certaine classe ou méthode est modifiée par certaines annotations pendant le processus de compilation du bytecode pour le code Java. À ce stade, il détectera ces annotations. .
L'annotation typique est l'annotation @Override. Une fois que le compilateur détecte qu'une méthode a été modifiée avec l'annotation @Override, le compilateur vérifiera si la signature de la méthode actuelle remplace réellement une méthode du parent. classe. Il s'agit de comparer si la classe parent a la même signature de méthode.
Cette situation s'applique uniquement aux classes d'annotations que le compilateur connaît déjà, comme plusieurs annotations intégrées dans le JDK. Quant à vos annotations personnalisées, le compilateur ne connaît pas la fonction de votre annotation. Bien sûr, je ne sais pas comment gérer cela, je choisis souvent de le compiler dans un fichier de bytecode en fonction de la portée de l'annotation, et c'est tout.
Méta-annotation
"Méta-annotation" est une annotation utilisée pour modifier des annotations, généralement utilisée dans la définition des annotations, par exemple :
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
Ceci est notre @Override Quant à la définition des annotations, vous pouvez voir que les deux annotations @Target et @Retention sont ce que nous appelons des « méta-annotations ». Les « méta-annotations » sont généralement utilisées pour spécifier des informations telles que le cycle de vie et la cible. d'une annotation. . Je viens de compiler un ensemble des derniers didacticiels d'introduction et avancés de base 0 en 2018. Je les partage de manière désintéressée. Vous pouvez les obtenir en ajoutant Java pour apprendre les q-u-n : 678, 241 et 563. Sont inclus : les outils de développement et les packages d'installation, et System Learning Roadmap
Il existe les "méta-annotations" suivantes en JAVA :
@Target:注解的作用目标 @Retention:注解的生命周期 @Documented:注解是否应当被包含在 JavaDoc 文档中 @Inherited:是否允许子类继承该注解
Parmi elles, @Target est utilisé pour indiquer qui l'annotation modifiée peut finalement cibler, et également Cela signifie simplement que votre annotation est utilisée pour modifier la méthode ? Type de modification ? Il est également utilisé pour modifier les attributs des champs.
@Target est défini comme suit :
On peut passer la valeur à cette valeur de la manière suivante :
@Target(value = {ElementType.FIELD})
is L'annotation modifiée par cette annotation @Target n'agira que sur les champs membres et ne pourra pas être utilisée pour modifier des méthodes ou des classes. Parmi eux, ElementType est un type d'énumération avec les valeurs suivantes :
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上 ElementType.FIELD:允许作用在属性字段上 ElementType.METHOD:允许作用在方法上 ElementType.PARAMETER:允许作用在方法参数上 ElementType.CONSTRUCTOR:允许作用在构造器上 ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上 ElementType.ANNOTATION_TYPE:允许作用在注解上 ElementType.PACKAGE:允许作用在包上
@Retention est utilisé pour indiquer le cycle de vie de l'annotation actuelle. Sa définition de base est la suivante :
<. 🎜>
De même, il a également un attribut value :@Retention(value = RetentionPolicy.RUNTIME
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件 RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件 RetentionPolicy.RUNTIME:永久保存,可以反射获取
剩下两种类型的注解我们日常用的不多,也比较简单,这里不再详细的进行介绍了,你只需要知道他们各自的作用即可。@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 是通过动态代理机制生成一个实现我们注解(接口)的代理类。
Après avoir exécuté le programme, nous verrons une telle classe proxy dans le répertoire de sortie. Après décompilation, elle ressemble à ceci :
.
La classe proxy implémente l'interface Hello et remplace toutes ses méthodes, y compris la méthode valeur et les méthodes dont l'interface Hello hérite de l'interface Annotation .
Et qui est cette instance clé d'InvocationHandler ?
AnnotationInvocationHandler est un Handler spécifiquement utilisé pour traiter les annotations en JAVA. La conception de cette classe est également très intéressante.
Il y a ici un memberValues, qui est une paire clé-valeur Map. La clé est le nom de notre attribut d'annotation et la valeur est la valeur attribuée à. l'attribut.
Et cette méthode d'invocation est très intéressante. Veuillez noter que notre classe proxy proxy toutes les méthodes de l'interface Hello, donc tout appel de méthode. dans la classe proxy sera transféré ici.
var2 pointe vers l'instance de méthode appelée, et ici nous utilisons d'abord la variable var4 pour obtenir le nom concis de la méthode, puis utilisons la structure switch pour déterminer qui est la méthode appelante actuelle. parmi les quatre méthodes principales d'Annotation, attribuez var7 à une valeur spécifique.
Si la méthode actuellement appelée est toString, equals, hashCode, annotationType, l'implémentation de ces méthodes a été prédéfinie dans l'instance AnnotationInvocationHandler, et vous pouvez l'appeler directement.
Ensuite, si var7 ne correspond pas à ces quatre méthodes, cela signifie que la méthode actuelle appelle la méthode déclarée par l'octet d'annotation personnalisé, comme la méthode value de notre annotation Hello. Dans ce cas, la valeur correspondant à cet attribut d'annotation sera obtenue à partir de notre carte d'annotation.
En fait, je pense personnellement que la conception des annotations en JAVA est un peu anti-humaine. Il s'agit évidemment d'une opération d'attribut et doit être implémentée à l'aide de méthodes. Bien sûr, si vous avez des opinions différentes, veuillez laisser un message pour en discuter.
Enfin, résumons le principe de fonctionnement de l'ensemble de l'annotation de réflexion :
Tout d'abord, nous pouvons attribuer des valeurs aux attributs d'annotation sous la forme de paires clé-valeur , comme ceci : @Bonjour (valeur = "bonjour").
Ensuite, si vous modifiez un élément avec une annotation, le compilateur analysera les annotations sur chaque classe ou méthode lors de la compilation et effectuera une vérification de base pour voir si votre annotation est autorisée à agir à l'emplacement actuel. , les informations d'annotation sont écrites dans la table attributaire de l'élément.
Ensuite, lorsque vous effectuez une réflexion, la machine virtuelle supprime toutes les annotations du cycle de vie RUNTIME et les place dans une carte, crée une instance AnnotationInvocationHandler et lui transmet la carte.
Enfin, la machine virtuelle utilisera le mécanisme de proxy dynamique JDK pour générer une classe proxy annotée par la cible et initialiser le processeur.
De cette façon, une instance d'annotation est créée, qui est essentiellement une classe proxy. Vous devez comprendre la logique d'implémentation de la méthode Invocation dans AnnotationInvocationHandler, qui est le noyau. En une phrase, renvoie la valeur de l'attribut d'annotation via le nom de la méthode.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!