注解定义
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:在运行时有效(即运行时保留)
其实可以说是
SOURCE可以用CLASS 和 RUNTIME 代替,
CLASS 可以用RUNTIME代替,反之不可.
Annotation 架构
CB4599CEC57E8B9FBCD123A8852CD4BA.png
- Annotation 的每一个实现类(),都和 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)
一般是在反射技术
在程序运行期间, 通过反射即使动态获取注解与其元素,从而完成不同的逻辑判断处理. 后续会发表反射技术.
网友评论