之前写过一遍Java注解 annotation文章,但是当时对注解的运用理解不多,有些知识点也一知半解。最近看了Retrofit源码,对注解的使用有了进一步的理解。此篇文章算是对之前知识的复习,以及对注解使用的补充说明。
注解是用来做什么的
注解就像是一个个标签,用来标记你的代码,可以理解成是一种应用于类、方法、参数、变量、构造器和包的特殊修饰符。
注解的作用我理解下来,一般有如下三种应用
1. 提供信息给编译器:编译器可以通过注解来探测错误和警告信息
这种应用,比较典型的例子就是@Override注解,用来标记方法重写,源码编译阶段该注解会被丢弃。
2. 编译阶段处理:软件工具可以利用注解信息来自动生成代码,HTML文档或者做其他相应处理
例如:butterknife框架里面的注解,就使用了编译生成代码(APT)技术。
3. 运行时的处理: 某些注解可以在程序运行时接收代码的提取,为程序提供逻辑判断依据或者逻辑处理依据
例如:Retrofit框架自定义的@Get、@Post注解,在处理请求逻辑时解析这些注解,实现相关的请求逻辑。
如何声明注解
格式
@元注解
public @interface 注解名称 {
属性列表;//可以没有
}
本质
注解和类、接口、枚举是同一级别的。
注解的本质是一个继承java.lang.annotation.Annotation接口的接口
属性
-
属性声明:
注解的属性,也叫成员变量
Q:既然注解本质是个接口,我们都知道接口是不能定义成员变量的,那么注解的属性是什么?
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface GET { String value() default ""; }以
Retrofit的@Get为例,我们可以看到注解的属性实际对应的是接口方法。在注解里面我们将类似String value()这种称为属性。显然对'属性'赋值听起来比对方法赋值好接受多了。A:注解的属性在注解的定义中以
无参方法的形式声明,方法名即为属性名,返回值为该属性的类型。注解的属性类型,必须为如下几个类型:
8种基本类型(byte,boolean,char,short,int,long,float,double) 和String,Enum,Class,annotation类型,以及这些类型的数组
-
属性赋值
属性赋值以
属性名=xxx的形式赋值,多个属性以,号隔开。注解的属性可以通过
default关键字指定默认值,如果指定了默认值则可以在使用时不赋值直接使用默认值,否则必须显式的赋值如果注解仅有一个属性且名为
value时,赋值时可以省略属性名以上面的
@Get注解为例:@GET(value = "1111") void testAssign2(); //或者省略value @GET("1111") void testAssign1();
元注解
关于注解的声明还有一个非常重要的概念就是元注解。
元注解是一种基本注解,可以用来注解其他注解;可以看成一种特殊的修饰符,用来解释说明注解。
常用的元注解有如下几种:
-
@Retention说明: 用来标识注解的存活周期
取值:
public enum RetentionPolicy { /** * 注解仅在源码阶段保留,编译时会被丢弃 */ SOURCE, /** * 注解会被保留到Class文件中,但是运行时会被JVM丢弃 */ CLASS, /** * 注解会被保留到Class文件中,运行时也会保留 */ RUNTIME } -
@Documented说明: 用来表示注解可以作为公共API,可以被例如javadoc之类的工具提取成文档。
@Documented仅用来标记,没有属性。 -
@Target说明: 用来限定当前注解的应用场景(类,方法等等),不使用则默认不限制。
@Target的属性类型为数组,因此同时赋值多个值,数组类型数组赋值方式:{}内,多个值使用逗号分隔,如果只赋值一个值可以省略{}示例:
@Target({METHOD, PARAMETER, FIELD}) public @interface FontRes { }取值:
public enum ElementType { /** 类、接口(包括注解)、枚举 */ TYPE, /** 属性 (包括枚举常量) */ FIELD, /** 方法 */ METHOD, /** 方法参数 */ PARAMETER, /** 构造函数 */ CONSTRUCTOR, /** 局部变量 */ LOCAL_VARIABLE, /** 注解 */ ANNOTATION_TYPE, /** 包 */ PACKAGE, /** * 用在类型参数声明的地方,java8新增 */ TYPE_PARAMETER, /** * 用在类型使用的地方,java8新增 */ TYPE_USE } -
@Inherited说明: 表示允许子类继承当前注解;一个超类被当前注解修饰,则其子类也具有此注解。
@Interited没有属性值。注意: 类不能从它实现的接口继承注解,方法也不能从它所重载的方法继承注解
-
@Repeatble说明: 用来标记当前注解可以重复,java1.8新增特性。在同一场景下需要多次使用同一注解时,可以通过
@Repeatable实现使用示例:
/** 自定义容器注解Roles(用来存放其他注解,里面必须要有一个 value 的属性, 属性类型是一个被 @Repeatable 注解过的注解数组) */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Roles { Role[] value(); } /** 自定义注解Role,使用@Repeatable注解,这样Role注解就可以多次使用 */ @Repeatable(Roles.class) public @interface Role { String role() default ""; } //使用示例 @Role(role="husband") @Role(role="father") @Role(role="son") public class Person { }
如何使用注解
这里所说的使用,不仅仅是简单含义的使用,还包括注解的读取,赋予注解真正意义上的作用
注解分为:
-
JDK内置注解:例如
@Override、@Deprecated等等,这类注解编译器会读取处理,实现它们实际代表的功能 -
第三方框架提供的注解:例如
Retrofit框架的@Get、@Post等注解,这类注解框架会读取处理,赋予注解实际的意义 -
自定义注解:这类注解就需要我们自己读取,来赋予它们实际的意义。不然仅仅是声明注解和在对应场景(方法,参数等地方)使用该注解,声明和使用等注解是没有任何意义的。
关于自定义的注解的提取和使用,一般我们有两种方式
-
编译时扫描和处理注解(即APT,Annotation Processing Tool),具体可先参考Java注解应用
-
运行时扫描和处理注解(通过java反射相关API)。这个可以参考
Retrofit框架对于运行时注解的运用,后面考虑写个这方面的使用示例,敬请期待...