您的位置:首页 > 移动开发 > Android开发

Java Android Annotation深入理解

2015-11-15 02:13 375 查看
一、概念

Annontation是Java5开始引入的新特征。中文名称一般叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。

更通俗的意思是为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且是供指定的工具或框架使用的。

Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。我们经常在程序中看到的@Override、@param、@return、@SuppressWarnings等都是annotation

官网的解释是:能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”

二、原理

Annotation其实是一种接口。通过Java的反射机制相关的API来访问annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。利用反射处理注解的方式是运行时注解,还有一类是编译时注解。

annotation是不会影响程序代码的执行,无论annotation怎么变化,代码都始终如一地执行。

Java语言解释器在工作时会忽略这些annotation,因此在JVM 中这些annotation是“不起作用”的,只能通过配套的工具才能对这些annontaion类型的信息进行访问和处理。

Annotation与interface的异同:

1)、Annotation类型使用关键字@interface而不是interface。

这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface

2)、Annotation类型、方法定义是独特的、受限制的。

Annotation 类型的方法必须声明为无参数、无异常抛出的。这些方法定义了annotation的成员:方法名成为了成员名,而方法返回值成为了成员的类型。而方法返回值类型必须为primitive类型、Class类型、枚举类型、annotation类型或者由前面类型之一作为元素的一维数组。方法的后面可以使用 default和一个默认数值来声明成员的默认值,null不能作为成员默认值,这与我们在非annotation类型中定义方法有很大不同。

Annotation类型和它的方法不能使用annotation类型的参数、成员不能是generic。只有返回值类型是Class的方法可以在annotation类型中使用generic,因为此方法能够用类转换将各种类型转换为Class。

3)、Annotation类型又与接口有着近似之处。

它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承

三、作用

a. 标记,用于告诉编译器一些信息,例如@SuppressWarnings、@Override

b. 编译时动态处理,如动态生成代码,例如ButterKnife,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别。

c. 运行时动态处理,如得到注解信息,例如xutils , afinal

四、元注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:

    1.@Target,

    2.@Retention,

    3.@Documented,

    4.@Inherited

  这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Target:

   @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

  取值(ElementType)有:

    1.CONSTRUCTOR:用于描述构造器

    2.FIELD:用于描述域

    3.LOCAL_VARIABLE:用于描述局部变量

    4.METHOD:用于描述方法

    5.PACKAGE:用于描述包

    6.PARAMETER:用于描述参数

    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

实例

/**
 * 作用于类、接口(包括注释类型)或枚举声明
 */
@Target(ElementType.TYPE)
public @interface Table {
/**
* 数据表名称注解,默认值为类名称
* @return
*/
public String tableName() default "className";
}
/**
 * 作用域成员变量
 */
@Target(ElementType.FIELD)
public @interface NoDBColumn {

}



@Retention:  

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)取值(RetentionPoicy)有:1.SOURCE:在源文件中有效(即源文件保留)2.CLASS:在class文件中有效(即class保留)3.RUNTIME:在运行时有效(即运行时保留)Retention
meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。
package com.cn.annotation.test1;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

//作用于成员变量
@Target(ElementType.FIELD)
// 在运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
// 注解变量,默认值给"fieldName"
public String name() default "fieldName";

public String setFuncName() default "setField";

public String getFuncName() default "getField";

public boolean defaultDBValue() default false;
}


@Documented:

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

//只能注解成员变量
@Target(ElementType.FIELD)
//运行时注解
@Retention(RetentionPolicy.RUNTIME)
//标记注解,被生成javadoc处理
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}


@Inherited:

  @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

  注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

  当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。想想都开销大啊。

//父类注解了,子类也跟着被注解了
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}


五、自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

  定义注解格式:

  public @interface 注解名 {定义体}

  注解参数的可支持数据类型:

    1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)

    2.String类型

    3.Class类型

    4.enum类型

    5.Annotation类型

    6.以上所有类型的数组

  Annotation类型里面的参数该怎么设定:

  第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;   

  第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  

  第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。

六、实例

package com.cn.annotation.test0;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//表明该注解用于注解域(只能对成员变量进行注解,方法和类都不行)
@Target(ElementType.FIELD)
// 表明该注解为运行时注解,运行时有效,如果RetentionPolicy.CLASS这个注解在编译就会被忽略掉
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomizedAnnatation {
// 注解名name,默认是"hello world"
public String name() default "hello world";
}
使用

package com.cn.annotation.test0;

import java.lang.reflect.Field;

public class TestClass {
/**
* 姓名
*/
@CustomizedAnnatation(name = "RanderC")
private String name;
/**
* 年龄
*/
@CustomizedAnnatation(name = "10岁")
private String age;

/**
* 个人介绍
*/
@CustomizedAnnatation
private String disc;
/**
* 提示信息
*/
private String tips;

public static void main(String[] args) {
// 得到TestClass.class对象
Class<TestClass> testClass = TestClass.class;
// 得到它的所有Fields(成员变量),也就是上面的name和age
Field[] fields = testClass.getDeclaredFields();
// 变量所有的Fields(成员变量)
for (Field field : fields) {
// 设置可见性,如果field设置为private,我们照样可以访问到
field.setAccessible(true);
// 判断这个类是否有CustomizedAnnatation注解,明显name、age和都有disc这个注解
if (field.isAnnotationPresent(CustomizedAnnatation.class)) {
// 取出这个field的注解对象
CustomizedAnnatation annotation = (CustomizedAnnatation) field
.getAnnotation(CustomizedAnnatation.class);
// 输出注解名
System.out.println(annotation);
// 输出注解的值
System.out.println(annotation.name());
}
}
}

}


输出结果:

@com.cn.annotation.test0.CustomizedAnnatation(name=RanderC)
RanderC
@com.cn.annotation.test0.CustomizedAnnatation(name=10岁)
10岁
@com.cn.annotation.test0.CustomizedAnnatation(name=hello world)
hello world


从结果可以看出,注解的变量可以在运行时解析到,那要跟当前对象的成员变量绑定起来当然也是可能咯。像ButterKnife这种框架,直接通过annotation能够将布局控件和关联绑定起来,省去了findViewById的事了。但不同的是ButterKnife这种框架是编译是注解,而非上面例子是运行时。

七、解析Annotation

解析Annotation包括运行时解析和变异是解析,通过上一个例子我们定义的是运行是解析的。

1.运行时解析

(1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析

method.getAnnotation(AnnotationName.class);
method.getAnnotations();
method.isAnnotationPresent(AnnotationName.class);


其他 @Target 如 Field,Class 方法类似

getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,因为一个 Target 可以被多个 Annotation 修饰

getAnnotations() 则表示得到该 Target 所有 Annotation

isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰

(2) 解析示例如下:

public static void main(String[] args) {
try {
Class cls = Class.forName("cn.trinea.java.test.annotation.App");
for (Method method : cls.getMethods()) {
MethodInfo methodInfo = method.getAnnotation(
MethodInfo.class);
if (methodInfo != null) {
System.out.println("method name:" + method.getName());
System.out.println("method author:" + methodInfo.author());
System.out.println("method version:" + methodInfo.version());
System.out.println("method date:" + methodInfo.date());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}


2.编译时的解析

(1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。需要做的

a. 自定义类集成自 AbstractProcessor

b. 重写其中的 process 函数

apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。

Android的ButterKnife注解框架就是这么干的。

(2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下:

@SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
HashMap<String, String> map = new HashMap<String, String>();
for (TypeElement te : annotations) {
for (Element element : env.getElementsAnnotatedWith(te)) {
MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);
map.put(element.getEnclosingElement().toString(), methodInfo.author());
}
}
return false;
}
}


SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。

process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境

process 函数返回值表示这组 anotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: