注解概述
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明
在java.lang.annotation这个接口的描述中有这么一句话,用来描述『注解』
The common interface extended by all annotation types.
注解的作用
1、用于其他工具来产生额外的代码或者配置文件
生成文档
这是最常见的,也是Java最早提供的注解。常用的有@param、@return等。
跟踪代码依赖性,实现替代配置文件功能
IOC依赖注入,可以使用大量注解配置,具有很大用处。
配合工具生成代码
配合一些第三方的工具,可以在编译阶段生成java文件,或者在运行时在指定的位置插入一段代码
在框架中会使用的比较多
2、提供信息给编译器或者IDE工具
比如在编译时进行格式检查
如@Override放在方法上,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出错误。
3、部分注解在程序运行时访问,增加程序的动态性
一般配合反射使用
4.使代码更加简洁,方便使用
比如现在很多的框架 butterknife,retrofit都大量的使用注解封装代码
注解的基本元素
一般常用的注解可以分为三类:
- 一类是Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
- 一类为元注解,元注解是用于定义注解的注解
- 一类为自定义注解,可以根据自己的需求定义注解
注解的基本元素:
@Retention(标明注解被保留的阶段)— —注解的生命周期
RetentionPolicy.SOURCE:只在源码期有效,在编译时会被编译器丢弃,不会写入 class 文件
即生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。 当在Java源程序上加了一个
注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时
会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处
理器Processor 也能处理源码注解,编译器处理完之后就没有该注解信息了;
这个应用场景主要是用于APT处理 生成一些额外的辅助类,同时还可以用于一些语法检查 比如androidx中的@IntDef
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
这时候就存在疑问SOURCE和Class有啥区别呢?
虽然把Class修改为Source编译时不会存在问题,但是有一个点很明显,就是Class中是否写入了该注解
而Android的打包过程是
Java源码 —> Class文件 —> Dex文件 —> apk
显然,在 Class文件 —> Dex文件我们是可以通过操作class文件获取到对应的注解信息,从而
在这个阶段通过asm技术动态的对我们的代码做一些处理
主要应用场景是用于字节码增强,比如 插桩
RetentionPolicy.RUNTIME :永久保存,可以反射获取
具体的区别如下:
运行时注解就是就是运行时运用反射,动态获取对象、属性、方法等,一般的IOC框架就是这样,可能会牺牲一点效率。
编译时注解就是在程序编译时根据注解进行一些额外的操作,大名鼎鼎的ButterKnife运用的就是编译时注解,ButterKnife在我们编译时,就根据注解,自动生成了一些辅助类。要玩编译时注解呢,你得先依赖apt,然后自己写一个类继承AbstractProcessor,重写process方法,在里面实现如何把配置或注解的信息变成所需要的类。
@Target(标明注解使用的范围) — —注解的作用目标
ANNOTATION_TYPE //允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD //注解作用于属性字段
ElementType.METHOD //注解作用于方法
ElementType.PARAMETER //注解作用于方法参数上
ElementType.CONSTRUCTOR //注解作用于构造方法
ElementType.LOCAL_VARIABLE //注解作用于在本地局部变量上
ElementType.ANNOTATION_TYPE //允许作用在注解上就是元注解
ElementType.PACKAGE //注解作用于包
@Inherited(标明注解可继承)
是否可以被继承,默认为 false
@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型
被用于一个class,则这个annotation将被用于该class的子类
@Documented(生成说明文档,添加类的解释 )
是否会保存到 Javadoc 文档中
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例
如javadoc此类的工具文档化。Documented是一个标记注解,没有成员
自定义注解
注解使用示例如下:
使用关键字@interface进行自定义注解,注解内容可以指定注解属性的类型,缺省值等
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id() default 0;
String value() default "";
}
注意,在注解的定义中 id()以及value(),其实并不是注解的方法,我们应该把它看做注解的属性,id()以及value()其实就表示注解的一个成员变量id和value,其默认值分别是0和空字符;
就这样我们定义好了一个自己的注解;
那么怎么去使用这个注解呢?
如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Utils.log();
}
@TestAnnotation(id=1,value="zhangsan")
public void testAno(){
Log.d("tag","testAno");
}
@TestAnnotation(id=2)
public void testAno3(){
Log.d("tag","testAno");
}
}
显然TestAnnotation 的@Target定义为ElementType.METHOD,那么它书写的位置应该在方法定义的上方,
就如我们的示例一样,写在testAno()方法上;
由于我们在TestAnnotation 中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上(),并且在()内以“元素名=元素值“的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用“,”号分割;
注意元素的类型只能使用基本数据类型,String,Class、enum、Annotation,或者这些类型的数组类型。像Long Integer编译报错。如上代码,可以定义为int、long、String、Class、Target[]、ElementType[] 。
同时默认值不能设置为null
注解声明时,定义了默认值,那么使用时也可以不用传
还有一种情况:只有一个成员变量而且名字就叫value的时候我们就可以省略掉value=的写法,写成@ViewAnnotation(“acd”)这种写法和@ViewAnnotation(value=“acd”)是一样的效果;
那么我们的注解是这样:
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewAnnotation {
String value() default "";
}
@ViewAnnotation("acd")
public void TestAno2(){
Log.d("tag","testAno2");
}
最后,还需要注意的一种情况是一个注解没有任何属性。那么我们连@ViewAnnotation后面的()都可以省略
我们定义了我们自己的注解后应该怎么使用呢?
先简单的说下,注解的原理其实质就是反射;
java.lang.reflect.AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响
所以我们使用注解都是通过反射使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Utils.log();
}
@TestAnnotation(id=1,value="zhangsan")
public void testAno(){
Log.d("tag","testAno");
}
@TestAnnotation(id=2)
public void testAno3(){
Log.d("tag","testAno");
}
public void useAnnotation(){
try {
Class classObj = getClass();
Method[] methods = classObj.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
TestAnnotation ano = method.getAnnotation(TestAnnotation.class);
if (ano!=null){
//表示该方法存在了该注解
Log.d("Eric","get Annottaion info "+ano.getid());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}