从字节码角度了解Java注解

注解定义

Java注解(Annotaion)又称Java标注,是JDK5.0引入的一种注释机制.注解也是元数据的一种形式,提供有关于程序,但不属于程序本身的数据. 注解对他们注解的代码的操作没有直接影响. 本文后面会从字节码文件中分析注解.

注解作用

注解本身没有任何意义,单独的注解就是一个注释, 它需要结合APT,反射,插桩技术才有意义. 在后续的文章中,会陆续给大家介绍这些技术.

元注解

在引入注解的架构之前,先引入什么是元注解.
作用在注解上面的注解叫元注解,其中包括@Retention @Target @Document @Inherited四种. 本文后面会介绍我们自己如何自定义一个元注解.

@Document:用于描述其它类型的annotation应该被作为被标注的程序成员的公共API, 因此可以被例如javadoc此类的工具文档化.它是一个标记注解,没有成员。

@Inherited: 使用此注解声明出来的自定义注解.在使用此自定义注解时,
如果注解在类上面时,子类会自动继承此注解,否则的话,子类不会继承此注解.这里一定要记住,使用Inherited声明出来的注解,只有在类上使用时才会有效,对方法,属性等其他无效.

@Target:用于描述注解的范围,即注解在哪用。它说明了Annotation所修饰的对象范围:
Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)
、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量等。
取值类型(ElementType)有以下几种:
CONSTRUCTOR:用于描述构造器
  FIELD:用于描述域即类成员变量
  LOCAL_VARIABLE:用于描述局部变量
  METHOD:用于描述方法
  PACKAGE:用于描述包
  PARAMETER:用于描述参数
  TYPE:用于描述类、接口(包括注解类型) 或enum声明
  TYPE_PARAMETER:1.8版本开始,描述类、接口或enum参数的声明
  TYPE_USE:1.8版本开始,描述一种类、接口或enum的使用声明

@Retention用于描述注解的生命周期,表示需要在什么级别保存该注解,即保留的时间长短。
取值类型(RetentionPolicy有以下几种:
1,SOURCE: 标记的注解仅保留在源码级别中,并被编译器忽略
2,CLASS: 标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略.
3,RUNTIME:在运行时有效(即运行时保留)
其实可以说是 \color{red}{RUNTIME > CLASS > SOURCE}
SOURCE可以用CLASS 和 RUNTIME 代替,
CLASS 可以用RUNTIME代替,反之不可.

Annotation 架构

CB4599CEC57E8B9FBCD123A8852CD4BA.png
  • Annotation 的每一个实现类(\color{red}{包括自定义注解}),都和 1 个 RetentionPolicy 关联 并且和1~n个ElementType 关联"。
  • Annotation 有许多系统已经帮我们实现的类,包括:Deprecated, Documented, Inherited, Override 等等。

下面的代码是自定义一个普通注解,并且有详细的注释.

/*用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,
    因此可以被例如javadoc此类的工具文档化。它是一个标记注解,没有成员。
*/
//@Documented
/*
    Inherited作用是,使用此注解声明出来的自定义注解,在使用此自定义注解时,
    如果注解在类上面时,子类会自动继承此注解,否则的话,子类不会继承此注解。
    这里一定要记住,使用Inherited声明出来的注解,只有在类上使用时才会有效,
    对方法,属性等其他无效。
 */
//@Inherited
/*
   用于描述注解的范围,即注解在哪用。它说明了Annotation所修饰的对象范围:
   Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)
   、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量等。
    取值类型(ElementType)有以下几种:
   CONSTRUCTOR:用于描述构造器
  FIELD:用于描述域即类成员变量
  LOCAL_VARIABLE:用于描述局部变量
  METHOD:用于描述方法
  PACKAGE:用于描述包
  PARAMETER:用于描述参数
  TYPE:用于描述类、接口(包括注解类型) 或enum声明
  TYPE_PARAMETER:1.8版本开始,描述类、接口或enum参数的声明
  TYPE_USE:1.8版本开始,描述一种类、接口或enum的使用声明
 */
@Target({ElementType.METHOD,ElementType.TYPE})// 1~n个ElementType
/*
 用于描述注解的生命周期,表示需要在什么级别保存该注解,即保留的时间长短。
    取值类型(RetentionPolicy)有以下几种:
    SOURCE:在源文件中有效(即源文件保留)
    CLASS:在class文件中有效(即class保留)
 RUNTIME:在运行时有效(即运行时保留)

   其实可以说是 RUNTIME > CLASS > SOURCE
   SOURCE可以用CLASS 和 RUNTIME 代替,
   CLASS 可以用RUNTIME代替,反之不可.
 */
@Retention(RetentionPolicy.SOURCE) //一个RetentionPolicy
public @interface ArouterAnnotaion {
    String value() default "Zyang真帅";
}

从上图可以看出,java中所有的注解都默认实现了Annotation接口, 有人会说我们自定义的注释没看到有implements实现啊.本文后面会为你揭晓.

这是Annotation.java的源码,从源码中可以看出,Anntation是一个接口.
604849DA996CD134418BA5B98DED55F6.png

下面写一个自定义的注解,并没有看到实现了Annotation的接口啊

public @interface ArouterAnnotaion {
}

其实在java在编译的时候, 编译器默认的给我们加上了implements java/lang/annotation/Annotation
下图是ArouterAnnotation字节码文件. 可以通过javap命令查看字节码,也可以通过ASM工具.


image.png

image.png

下面是对注解具体介绍:

一, 介绍RetentionPolicy
  • RetentionPolicy.SOURCE,标记的注解仅保留在源码级别中,并被编译器忽略
File: AAnnotaion.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AAnnotaion {
     String value() default "Zyang真帅";
     int id();
}
File: MyClass.java
@AAnnotaion(value = "value",id = 1)
public class MyClass {

}

通过字节码查看, 字节码里面并没有任何注解的信息, 说明RetentionPolicy.SOURCE只作用在源码级别中.
image.png
  • RetentionPolicy.CLASS标记的注解在编译时由编译器保留,但Java虚拟机(JVM)会忽略.
File:AAnnotaion.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface AAnnotaion {
    String value() default "Zyang真帅";
    int id();
}
File:MyClass.java
@AAnnotaion(value = "value",id = 1)
public class MyClass {

}

通过字节码查看, 字节码里面是有注解的信息的.并且这条信息被标识上了 invisible,告知JVM虚拟机忽略这条信息为不可见的.
image.png
  • RetentionPolicy.RUNTIME 标记的注解由JVM保留,因此运行时环境可以使用它.
File:AAnnotaion.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AAnnotaion {
    String value() default "Zyang真帅";
    int id();
}
File:MyClass.java
@AAnnotaion(value = "value",id = 1)
public class MyClass {
   
}

通过字节码查看, 字节码里面是有注解的信息的.
image.png
二, 介绍Target

主要是用于描述注解的范围,即注解用在在哪里. 由于很简单,文章前面已经有介绍了.这里不作介绍了.

其他两个元注解, 使用特别少,文章前面有简单介绍,这里不作介绍了.

注解的使用

在自定义注解中, 如果元素有default, 在使用这个自定义注解,可以不用去赋值,他会使用这个默认值; 如果没有default, 在使用时,必须要给这个注解的元素赋值. 赋值的方式以key-value形式赋值

File:AAnnotaion.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AAnnotaion {
    String value() default "";
    int id();
}

它的使用

File:MyClass.java
@AAnnotaion(id = 1)
public class MyClass {
    
}
File:MyClass1.java
@AAnnotaion(id = 1,value="value")
public class MyClass1 {
    
}

注解的应用场景

根据注解的保留级别不同, 对注解的使用自然存在不同的场景. 由注解的三个不同保留级别可知,注解作用于:源码(RetentionPolicy.SOURCE), 字节码(RetentionPolicy.CLASS),运行时(RetentionPolicy.RUNTIME).

源码级别(RetentionPolicy.SOURCE)

一般使用在 Apt技术,IDE语法检查

  • APT技术:在编译时期能够获取注解与注解生命的类,包括类中所有的成员信息, 一般用于生成额外的辅助类. 例如(ButterKnife,Arouter).

  • IDE语法检查: 在Android开发中, support-annotations与androidx.annotaion中均有提供@IntDef注解,这注解主要是提供语法检查的, 此注解源码如下

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})//这是作用在注解上面的注解, 也是元注解
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;

    /**
     * Whether any other values are allowed. Normally this is
     * not the case, but this allows you to specify a set of
     * expected constants, which helps code completion in the IDE
     * and documentation generation and so on, but without
     * flagging compilation warnings if other values are specified.
     */
    boolean open() default false;
}

在Java中Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中.比常量多5到10的内存占用. 这时候此注解的意义在于能够取代枚举.

public enum Teacher{
  ZHANGSAN,HANGMEIMEI
}
public void test(Teacher teacher){

}
test(Teacher.ZHANGSAN)

为了内存优化,我们现在不再使用枚举,这时候我们使用常量来代替枚举.

public static final int ZHANGSAN = 0;
public static final int HANGMEIMEI = 1;

但是我们使用的时候, 不能限制开发人员固定的某些值.

test(11);
test(0);

我们有更加好的代替方案,这时候我们使用@IntDef

@IntDef({ZHANGSAN,HANGMEIMEI})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface Teacher{
}
public void test(@Teacher int curTeacher){

}
/*
  这样就能达到使用枚举的效果了,如果你使用其他的, 
  IDE就会提示语法错误, 让我们必须使用上面两个值.  
  主要是由IDE 去实现的语法监测.. 
*/
test(ZHANGSAN);
字节码级别(RetentionPolicy.CLASS)

一般使用在字节码增强(字节码插桩技术,热修复等....).
在编译出Class后,通过修改Class数据以实现修改代码逻辑目的,对于是否需要修改的区分或者修改不同逻辑的骗到可以使用注解. 这里不作介绍,后续会发表插桩,热修复等技术文章.

运行时级别(RetentionPolicy.RUNTIME)

一般是在反射技术
在程序运行期间, 通过反射即使动态获取注解与其元素,从而完成不同的逻辑判断处理. 后续会发表反射技术.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • 从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在...
    CarlosLynn阅读 545评论 0 2
  • 关于注解首先引入官方文档的一句话:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代...
    编程小世界阅读 454评论 0 0
  • 以前,『XML』是各大框架的青睐者,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,『XML』...
    Java大生阅读 2,374评论 3 96
  • 深入理解 Java 注解 本文内容基于 JDK8。注解是 JDK5 引入的,后续 JDK 版本扩展了一些内容,本文...
    静默虚空阅读 453评论 0 0
  • 朔气连空岁已残。谁将素玉试清寒。 凭临冻柳疑飞絮,尽舞严冬欲隐山。 风瑟瑟,雾漫漫。轻呵韵笔向云端。 思寻何处春消...
    尘埃落定1阅读 251评论 4 16