重新认识Java注解

今天Debug看源码的时候,无意间看到这么个东西

首先承认我的无知,看到这个我很惊诧。

也勾起了我的好奇心,于是有了这篇认知记录。

下面就来重新认识下注解吧!

注解的本质

关于运行时注解的信息,会在.class文件中,并且最终以运行时数据结构存储在方法区,也知道我们是可以通过Class对象或者Method对象,来获取其相应的注解信息的。

不过确实没有意识到,或者说根本就没有去猜想其背后的实现,也许是直接使用来解析注解的机会比较少吧。

现在才认识到,原来我们定义的注解, 最终使用的时候,都是以一个代理类的方式与相应的Class或者Method对象绑定到一起。

所有的注解,其实都是接口Annotation子接口,而每一个@interface的声明,最后其实就是一个普通的interface罢了!下面请看

public @interface AnnotationDemo {

    int value();

    int name ();

}

public interface com.example.demo.anno.AnnotationDemo extends java.lang.annotation.Annotation {

  	public abstract int value();

 	public abstract int name();
}

从上面对一个注解类的反编译结果就能看出来,它其实就是一个普通的接口类

从接口到实例

我们是如何查找到一个类定义的那些注解然后去使用呢?

答案是:从Class对象中,我们可以获取所有的信息

一个Class 的所有Annotation代理类被封装到一个私有静态类AnnotationData

private static class AnnotationData {
// 一个Map 映射 具体的Annotation Class 和其代理类对象
final Map<Class<? extends Annotation>, Annotation> annotations;
final Map<Class<? extends Annotation>, Annotation> declaredAnnotations; // Value of classRedefinedCount when we created this AnnotationData instance
final int redefinedCount; AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
int redefinedCount) {
this.annotations = annotations;
this.declaredAnnotations = declaredAnnotations;
this.redefinedCount = redefinedCount;
}
}

Class类中有一个成员变量

    private volatile transient AnnotationData annotationData;

而具体的创建动态代理对象的操作,则是懒加载的方式

  public Annotation[] getAnnotations() {
// 调用 Class#annotationData方法
return AnnotationParser.toArray(annotationData().annotations);
}
private AnnotationData annotationData() {
while (true) { // retry loop
AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
// 如果已经初始化,并且这个类的redefinedCount和创建此AnnotationData对象时一致
// 则无需重新创建AnnotationData对象
// java.lang.instrument.Instrumentation#redefineClasses允许在运行时,重新定义类
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
// null or stale annotationData -> optimistically create new instance
// 为null 或者已经过时了,创建一个新的实例
AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// try to install it
// 使用Unsafe CAS去更新字段 annotationData,直至成功
if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
// successfully installed new AnnotationData
return newAnnotationData;
}
}
}

创建AnnotationData时,是通过一些native方法,获取类相关的annotation元信息的byte[]数组表示,然后解析出注解接口对应的Class对象,最后去通过Jdk Dynamic Proxy动态代理来创建对象

public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
// JDK动态代理
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
}
});
}

这样最终就创建了一个注解接口的代理类

总结

注解类就是一个普通的接口类,最终在使用时,会创建相应的代理对象,用来获取定义在注解上的一些元数据信息。

为什么要用接口?

我的理解是,接口简单、简洁,所有的方法都是抽象方法,属性都是静态常量,而我们的添加在注解上的一些信息,通常都是一些值,并不需要方法体来去做些什么。

不过使用接口来实现注解,就会有个问题,接口的字段都是静态常量,不能修改,所以注解里定义的都是方法,而动态代理类就是为了能在运行时,调用注解定义的方法,就能获取我们定义在注解上的值。

到这里,对注解的实现已经有了一个大概的认识,不过一些细节,并没有深究,能力有限,待需要时,有机会和能力再去深究。

重新认识Java注解的更多相关文章

  1. Java注解

    Java注解其实是代码里的特殊标记,使用其他工具可以对其进行处理.注解是一种元数据,起到了描述.配置的作用,生成文档,所有的注解都隐式地扩展自java.lang.annotation.Annotati ...

  2. 19.Java 注解

    19.Java注解 1.Java内置注解----注解代码 @Deprecated                                    //不推荐使用的过时方法 @Deprecated ...

  3. Java注解入门

    注解的分类   按运行机制分:   源码注解:只在源码中存在,编译后不存在 编译时注解:源码和编译后的class文件都存在(如@Override,@Deprecated,@SuppressWarnin ...

  4. java注解(Annotation)解析

    注解(Annotation)在java中应用非常广泛.它既能帮助我们在编码中减少错误,(比如最常见的Override注解),还可以帮助我们减少各种xml文件的配置,比如定义AOP切面用@AspectJ ...

  5. JAVA 注解的几大作用及使用方法详解

    JAVA 注解的几大作用及使用方法详解 (2013-01-22 15:13:04) 转载▼ 标签: java 注解 杂谈 分类: Java java 注解,从名字上看是注释,解释.但功能却不仅仅是注释 ...

  6. attilax.java 注解的本质and 使用最佳实践(3)O7

    attilax.java 注解的本质and 使用最佳实践(3)O7 1. 定义pojo 1 2. 建立注解By eclipse tps 1 3. 注解参数的可支持数据类型: 2 4. 注解处理器 2 ...

  7. paip.java 注解的详细使用代码

    paip.java 注解的详细使用代码 作者Attilax 艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/att ...

  8. JAVA 注解的几大作用及使用方法详解【转】

    java 注解,从名字上看是注释,解释.但功能却不仅仅是注释那么简单.注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解 ...

  9. 框架基础——全面解析Java注解

    为什么学习注解? 学习注解有什么好处? 学完能做什么? 答:1. 能够读懂别人写的代码,特别是框架相关的代码: 2. 让编程更加简洁,代码更加清晰: 3. 让别人高看一眼. spring.mybati ...

  10. Java注解配置

    Java注解是附加在代码中的一些元信息,用于一些工具在编译.运行时进行解析和使用,起到说明.配置的功能.注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用.包含在 java.lang.annota ...

随机推荐

  1. CSS 布局水平 & 垂直对齐

    元素居中对齐 margin: auto; 文本居中对齐 text-align: center; 图片居中对齐 要让图片居中对齐, 可以使用 margin: auto; 并将它放到 块 元素中 左右对齐 ...

  2. matplotlib Transform

    2020-04-09 15:09:02 -- Edit by yangray Transform 类是TransformNode的子类,它是所有执行变换的TransformNode的实例的基类.所有非 ...

  3. spark error Caused by: java.io.NotSerializableException: org.apache.hadoop.hdfs.DistributedFileSystem

    序列化问题多事rdd遍历过程中使用了没有序列化的对象. 1.将未序列化的变量定义到rdd遍历内部.如定义入数据库连接池. 2.常量定义里包含了未序列化对象 ,提出去吧 如下常量要放到main里,不能放 ...

  4. Powershell如何制定属性并输出

    这个标题看着有些云里雾里.... 前一阵,群里有个朋友问博主“我想把所有用户的SMTP地址全部输出到CSV文件中进行统计,但是SMTP地址似乎输出的是错误的,可在shell里看输出的内容是正确的阿” ...

  5. vue(element)中使用codemirror实现代码高亮,代码补全,版本差异对比

    vue(element)中使用codemirror实现代码高亮,代码补全,版本差异对比 使用的是vue语言,用element的组件,要做一个在线编辑代码,要求输入代码内容,可以进行高亮展示,可以切换各 ...

  6. tf.nn.relu6 激活函数

    tf.nn.relu6(features,name=None) 计算校正线性6:min(max(features, 0), 6) 参数: features:一个Tensor,类型为float,doub ...

  7. tf.nn.conv2d 卷积

    tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) 第一个参数input:指需要做卷积的输入 ...

  8. 008-进制-C语言笔记

    008-进制-C语言笔记 学习目标 1.[掌握]include预处理指令 2.[掌握]多文件开发 3.[了解]认识进制 4.[掌握]进制之间的互相转换 5.[掌握]原码,反码,补码 6.[掌握]位运算 ...

  9. 泛微ecology OA系统在数据库配置信息泄露

    漏洞描述 攻击者可通过该漏洞页面直接获取到数据库配置信息,攻击者可通过访问存在漏洞的页面并解密从而获取数据库配置信息,如攻击者可直接访问数据库,则可直接获取用户数据,由于泛微e-cology默认数据库 ...

  10. 磁盘性能测试工具之fio

    fio是测试磁盘性能的一个非常好的工具,用来对硬件进行压力测试和验证. 注意事项 CentOS 6.5等较老版本的操作系统用fdisk创建分区时,默认为非4KB对齐选择初始磁柱编号,对性能有较大的影响 ...