Java Android Annotation深入理解

20 篇文章 0 订阅

一、概念    

    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 进行处理。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值