注解的本质(yet)
所有的注解类型都继承自这个普通的接口(Annotation),有关这一点,你可以去反编译任意一个注解类,你会得到结果的。
一个注解准确意义上来说,只不过是一种特殊的注释而已,如果没有解析它的代码,它可能连注释都不如。
而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。
典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名。
这一种情况只适用于那些编译器已经熟知的注解类,比如 JDK 内置的几个注解,而你自定义的注解,编译器是不知道你这个注解的作用的,当然也不知道该如何处理,往往只是会根据该注解的作用范围来选择是否编译进字节码文件,仅此而已。
一 元注解
『元注解』是用于修饰注解的注解,通常用在注解的定义上
JAVA 中有以下几个『元注解』:
-
@Target:注解的作用目标
-
@Retention:注解的生命周期
-
@Documented:注解是否应当被包含在 JavaDoc 文档中
-
@Inherited:是否允许子类继承该注解 元注解Inherited声明出的注解,在使用时用在类上,可以被子类所继承,对属性或方法无效
@Target 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。
-
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
-
ElementType.FIELD:允许作用在属性字段上
-
ElementType.METHOD:允许作用在方法上
-
ElementType.PARAMETER:允许作用在方法参数上
-
ElementType.CONSTRUCTOR:允许作用在构造器上
-
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
-
ElementType.ANNOTATION_TYPE:允许作用在注解上
-
ElementType.PACKAGE:允许作用在包上
@Retention 用于指明当前注解的生命周期
-
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
-
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
-
RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
二 除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
-
@Override
-
@Deprecated
-
@SuppressWarnings
@Override
它没有任何的属性,所以并不能存储任何其他信息。它只能作用于方法之上,编译结束后将被丢弃。
所以你看,它就是一种典型的『标记式注解』,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译。
@Deprecated
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
@SuppressWarnings主要用来压制 java 的警告
三 自定义注解的时候也可以选择性的使用元注解进行修饰,这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。
编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表。被写进字节码文件
四 反射
所以,对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。
-
getAnnotation:返回指定的注解
-
isAnnotationPresent:判定当前元素是否被指定注解修饰
-
getAnnotations:返回所有的注解
-
getDeclaredAnnotation:返回本元素的指定注解
-
getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的
方法、字段中相关反射注解的方法基本是类似的,这里不再赘述
五
当你进行反射的时候,虚拟机将类(方法)所有生命周期在 RUNTIME 的注解取出来放到一个 map 中(键是我们注解属性名称,值就是该属性当初被赋上的值。),并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。
最后,虚拟机将采用 JDK 动态代理机制通过AnnotationInvocationHandler生成一个目标注解的代理类,因为代理的是没有实体的接口,故并初始化好处理器。
那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值。
https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247485067&idx=2&sn=52499b25de7274ff195abb7502fa3f15&chksm=fa49793acd3ef02c37fc4d6c3df29948093d28b30e6a33825c40422eace6eabcb8d8c4e500ae&mpshare=1&scene=1&srcid=0914HYqCAT43APIejUmWAKhY&key=aabb2ccd6a655235c0e92affd604bc04affb0edebd7a2c97eef504c0b58dc3f38953df6b88e79de853aa6db1e342df2c1733e36d8166f8a61c8bbbba9cd78bd07bf8c094a3486552e2de76136bd171e4&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=pPNKGlObNJE8D1Cy5YHUbptxjVEzzwJnsSwaAJ67uUGXcPrFGJzurwfZ6aX8xWBU
六 使用:
ServiceRequestMapping an = method.getAnnotation(ServiceRequestMapping.class); if (null != an) { String anName = an.value();
getAnnotion
取得字节码常量池,<name, value>.put
new AnnotationInvocationHandler implements InvocationHandler , 取得注解ServiceRequestMapping的代理对象
an.value 调用代理方法,<name, value>.get
该实例更多内容在:netty httpserver url映射:https://www.cnblogs.com/silyvin/articles/9700586.html
七 另一片比较详细的文章:
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。
通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。
该方法会从memberValues这个Map中索引出对应的值。
而memberValues的来源是Java 类(方法)常量池。
https://blog.csdn.net/lylwo317/article/details/52163304?utm_source=copy
73: public AnnotationInvocationHandler(Class type, Map memberValues) 74: { 75: this.type = type; 76: this.memberValues = memberValues; 77: }
http://www.javadocexamples.com/java_source/sun/reflect/annotation/AnnotationInvocationHandler.java.html
79: public static Annotation create(Class type, Map memberValues)
80: {
81: for (Method m : type.getDeclaredMethods())
82: {
83: String name = m.getName();
84: if (! memberValues.containsKey(name))
85: {
86: // FIXME: what to do about exceptions here?
87: memberValues.put(name, m.getDefaultValue());
88: }
89: }
90: AnnotationInvocationHandler handler
91: = new AnnotationInvocationHandler(type, memberValues);
92: return (Annotation) Proxy.newProxyInstance(type.getClassLoader(),
93: new Class[] { type },
94: handler);
95: }