感谢作者:本文转自:https://www.cnblogs.com/yangming1996/p/9295168.html
以前,xml是各大框架的青睐者,他以松耦合的方式玩是完成了框架中几乎所有的配置,但是随着项目越来越庞大,xml的内容也越来越复杂,维护成本也越来越高,于是人们提出一种高耦合的配置方式 注解,方法上可以注解,类上可以注解,字段属性上也可以注解。反正几乎配置的地方都可以进行注解。
注解的本质
java.lang.annotation.Annotation接口中有这么一句话,用来描述注解
所有的注解都继承自于Annotation
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
这是注解Override的定义
public interface Override extends Annotation{ }
没错注解的本质就是一个继承了Annotation接口的接口。有管这一点,你可以反编译任意一个注解类,你会得到结果,一个注解准确的含义就是一个特殊的注释而已,如果没有解析他的代码,他可能连注解都不如
而解析一个类或者方法的注解往往两种形式,一种是编译器直接扫描,一种是运行期间反射。反射的事情我们待会说,而编译器的扫描指得是编译器对java代码编译字节码的过程中会检测到某个类或者方法的注解修饰。这是他就睡对这些注解进行某些特殊的处理,典型的注解就是@Override,一旦编译器检测到某个方法修饰了@Override注解,编译器就会检查当前方法的方法的方法名是否真正重写了父类的某个方法。也就是比较父类中是否有一个同样的方法签名
这一种情况仅仅适用于编译器已经成熟的注解类,比如JDK内置的几个注解类,而你自定义的的注解,编译器是不知道你这个主机的作用的。当然不知道该如何处理。往往只会根据该注解的作用范围来选择是否编译进行字节码文件,仅此而已。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
这是我们@Override注解的定义,你可以看到其中的@Target,@Retention两个注解就是我们所谓的元注解,元注解一般用于指定某个注解的生命周期以及作用目标等信息。
java中一下元注解
. @Target:注解的作用目标
.@Retrntion:注解的生命周期
.@Documented:注解是否应该被包含在Javadoc中
.@Iherited:是否允许子类继承该注解
@Target的定义如下 @Documented @Retention(RetentionPolilcy.RUNNING) @Target(ElementType.ANNOTATION_TYPE) public @interface Target{ ElementTypep[] value(); }
我们可以通过如下的方式传值
@Target(value = {ElementType.FIELD})
这个Target注解修饰的注解将只能作用在成员字段上,不能用英语修饰方法或者类。其中,ElementType是枚举类型,有一下一些值:
- ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
- ElementType.FIELD:允许作用在属性字段上
- ElementType.METHOD:允许作用在方法上
- ElementType.PARAMETER:允许作用在方法参数上
- ElementType.CONSTRUCTOR:允许作用在构造器上
- ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
- ElementType.ANNOTATION_TYPE:允许作用在注解上
- ElementType.PACKAGE:允许作用在包上
@Retention用于指明当前注解的生命周期,他的定义如下:
@Documented @Retention(RententionPolicy.RUNNINT) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention{ RetentionPolicy value(); }
同样的他又一个value属性
@Retention(value = RetentionPolicy.RUNTIME)
注解与反射
上诉内容我们介绍了注解使用上的细节,简单提到,注解的本质是继承Annotation接口的接口。那么从虚拟机层面看注解是个什么东西
我们自己定义一个注解类。
@Target(value = {ElementType.FIELD,ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNNINT) public @interface Hello{ String value(); }
这里我们指定了Hello这个注解只能修饰字段和方法,并且修饰永远存货,以便我们反射获取
之前我们说过,虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
- RuntimeVisibleAnnotations:运行时可见的注解
- RuntimeInVisibleAnnotations:运行时不可见的注解
- RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
- RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
- AnnotationDefault:注解类元素的默认值
给大家看虚拟机的这几个注解相关的属性表的目的在于,让大家从整体上构建一个基本的影响,注解在字节码文件中式如何存储的。所以,对于一个类或者接口来说,class 类中提供了一下一些方法用于发射注解
- getAnnotation:返回指定的注解
- isAnnotationPresent:判定当前元素是否被指定注解修饰
- getAnnotations:返回所有的注解
- getDeclaredAnnotation:返回本元素的指定注解
- getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
方法、字段中相关反射的方法基本式类似的,这里不做过多陈述,下面我们看一个完整的例子。
public class Test{ @Hello("hello") public static void main(String []args) throw NOSuchMethodException{ Class cls = Test.class; Method method = clas.getMethod("main",String[].class); Hello hello = method.getAnootation(Hello.class); } }
我们说过,注解本质上式继承了Annotation 接口的接口,而当你通过反射,而你通过反射获取getAnnotation方法中获取一个注解的实例的时候,其实JDK是通过动态代理机制生成一个实现我们注解接口的代理类。我们运行程序后,会看到输出目录中又这么一个代理类,反编译之后是这样的
代理类实现接口Hello并冲洗所有的方法。包括Hello从Annotation接口继承而来的方法。而这个InvocationHandler实例是谁?
AnnotationInvocationHandler是JAVA中专门用于吃醋里注解Handler,而这个类的涉及也非常由意思。
这里有一个memberVallues,他是一个Map键值对,键是我们注解属性名称,值就是该属性当初被赋予上的值。
而这个invoke方法就很有意思了,大家注意看,我们的代理类代理了Hello接口中所有的方法,所以对于代理类中的任何方法的调用都会被传递到这里来。
var2指向被调用方法实例,而治理首先用变量var4获取该方法的简明名称,接着switch结构判断当前的调用方法时谁,如果时Annotation中
,将 var7 赋上特定的值。
如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。
那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,例如我们 Hello 注解的 value 方法。这种情况下,将从我们的注解 map 中获取这个注解属性对应的值
接下来我们看一个注解类的简单实现以及琢磨他的原理时怎么回事。