Java基础之注解Annotation初入篇_雷惊风

   最近再看一些Android三方源码库的时候发现很多库都在用注解这个东西,什么EventBus3.0、Jack大神的butterknife,这东西为啥越来越多的人使用它呢?之前没有了解过Annotation的相关知识,正好借这个机会对Java注解做一个深入的了解。今天想着主要先整理一下关于注解比较初级的一些概念与知识,从几个方面总结吧。注解的基础概念、自定义注解、通过反射机制写一个注解处理器处理运行时(Runtime)注解。那我们放下怀中的妹妹、扔了手里的DULEISI开始吧。

 

一、注解的基本概念

       Java注解是在java5.0版本引入,通过Java自身的注解我们可以知道很多事情,比如@Override,这个可以说是我们日常开发中最常见的一个Java系统为我们写好的注解,通过它我们就知道这个这个类有父类或者实现了某个接口并且当前的方法就是重写的父类的方法;通过我们自己自定义的注解可以做很多事情,比如我们可以定义运行时注解,在项目运行时通过反射做一些事情,当然这些都是后话。这里,我大致将Java中的注解分为三类:1.元注解;2.Java系统为我们提供的基于元注解的注解(如上边的@Override);3.由开发者也就是我们自己定义的注解。下面我们一个一个的来大致了解一下。

1.元注解:由系统为我们提供,当我们自定义注解时,就会用到而且必须用到我们的元注解,元注解的作用就是为我们自定义注解服务的。包括Java系统提供的一些已经写好的注解,比如我们的@Override。元注解在Java5.0上为我们提供了4个:1.@Target,2.@Retention,3.@Documented,4.@Inherited,我们在java.lang.annotation包中找到它们。我们看一下第一个:

 

1.@target:这个元注解是用来定义一个注解可以作用在哪些地方的,我们先来看一下系统是怎么应用它的,看一下@Override的实现源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

可以看到系统在定义@Override注解的时候用到了@Target元注解,并且有一个小括号,里边出入了一个ElementType的东西,我们看一下它的源码:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    /** Field declaration (includes enum constants) */
    FIELD,
    /** Method declaration */
    METHOD,
    /** Formal parameter declaration */
    PARAMETER,
    /** Constructor declaration */
    CONSTRUCTOR,
    /** Local variable declaration */
    LOCAL_VARIABLE,
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    /** Package declaration */
    PACKAGE,
    /**
     * Type parameter declaration
     *
     * @since 1.8
     * @hide 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     * @hide 1.8
     */
    TYPE_USE
}

可以看到在ElementType是一个枚举类其中包含一下字段:

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

2.FIELD:用于描述成员变量

3.METHOD:用于描述方法
4.PARAMETER:用于描述参数
5.CONSTRUCTOR:用于描述构造器
6.LOCAL_VARIABLE:用于描述局部变量
7.PACKAGE:用于描述包
8....

可以看到@Target是通过ElementType这个枚举类来定义omen一个注解的作用范围的。如果一个注解被定义成@Target(ElementType.FIELD)的,那么这个注解只能用到成员变量上边。

 

2.@Retention定义一个注解将要保留的时间端。可以看到在@Override中也有用到。

在用时传入了一个RetentionPolicy对象,看一下这个对象:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

可以看到它也是一个枚举类,只提供了三中类型,SOURCE、CLASS、RUNTIME,他们分别代表当前定义的注解保留到的不同时间段,Source表示只在源代码中保留,比如我们的@Override,Class表示保留到编译阶段,也就是被JavaC编译成class文件以前;Runtime表示保留到运行时,及我们的程序跑起来了都有效。可见@Retention是通过RetentionPolicy枚举类实现的不同时间段定义。

 

3.@Documented:描述其它类型的annotation应该被作为被标注的程序成员的公共API,可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。(感觉不是重点)。

4.@Inherited描述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。元注解就先到这里。


        下边看一下Java系统基于元注解为我们提供的一些系统注解,这里举几个例子,Java5.0中为我们定义了3个标准注解,为:我们上边已经介绍过的@Override、@Deprecated、@SuppressWarnings,下边分别说一下他们的作用:

1.@Override:表示当前方法为重写覆盖了父类方法,通过上边的源码可以看到,它只能作用在方法上边,并且是在源码中有效的。

 

2.@Deprecated:表示不赞成使用的代码或者将要被弃用的代码,当我们写一个新类或者新的方法,想要在以后的版本迭代中慢慢取代旧的类或者方法的时候在旧的类中可以使用这个注解,告诉编译器我们的类或者方法将要被弃用。我们可以看一下当一个类被标记上@Deprecated后的状态:

当我们给一个类标记@Deprecated注解后:


可以看到编译器为我们在该类上花了一条弃用线。当我们把@Deprecated作用在一个方法上的时候看一下:


可以看到当我们调用一个我们添加了@Deprecated的方法是,我们的编译器会在我们的方法上添加一条弃用线,我们在Android中为ImageView设置透明度或者背景的时候应该也注意到过,它也是用了@Deprecated修饰,但是当我们想要取代的时候我们必须要给出替换的方法或者是类,让开发者知道我们如果想实现相同的功能要换用什么方法,比如为ImageView设置透明,看一下它的源码:

/**
 * Sets the alpha value that should be applied to the image.
 *
 * @param alpha the alpha value that should be applied to the image
 *
 * @deprecated use #setImageAlpha(int) instead
 */
@Deprecated
@RemotableViewMethod
public void setAlpha(int alpha) {
    alpha &= 0xFF;          // keep it legal
    if (mAlpha != alpha) {
        mAlpha = alpha;
        mColorMod = true;
        applyColorMod();
        invalidate();
    }
}

可以看到它在注释中为我们描述了use #setImageAlpha(int) instead所以当我们调用这个方法的时候,就会知道我们需要调用setImageAlpha(int)方法取代原来的setAlpha()方法。这也是一个很好的编程习惯。看一下@Deprecated的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

它被保留到了运行时,并且可以作用在构造器、成员变量、方法、包等很多属性上边。

3.@SuppressWarnings:关闭编译器警告信息。

到这里Java系统的一些注解就算是整理的差不多了,其中包括元注解与系统提供给我们用的系统注解。


二、自定义注解

接下来我们看一下自定义注解,我们知道我们如果想要自定义注解就会用到元注解,我们可以看一下系统的@Override注解是怎么定义的:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

它就是用了两个我们上边介绍过的元注解@Target来定义作用域,@Retention来定义保留时段。然后后边的跟我们定义一个接口差不多也有一个interface修饰,但是不同的是它的前边多了一个@符号,通过这个来告诉我们的系统这是一个注解类,当然Override类就是一个普普通通的.java格式的类,没有什么特别的。好了,那么我们现在也创建一个java文件来按照上边的文件来写把:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PersonName {
String value() default "";
}

我们定义了一个PersonName类作用域为成员变量,有效到运行时。但是这并没有什么乱用,我们怎么用呢,我们怎么给他添加字段呢,不要急骚年,上边你可能已经看到了,我在PersonName内部添加了一行代码,没错,就是String value() default "";这就是给我们的PersonName定义一个值,并且我还设置类一个默认值,有没有感觉他像一个我们平时的方法,但实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。关于自定义注解里边参数的设定我从其他博客中搬过来点东西,毕竟人家已经写好的嘛,比我总结的要好多了,如下:

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

  第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
     第二,参数成员只能用基本byte,short, char,int,long,float,double, boolean八种基本数据类型和 String, Enum, Class, annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  
     第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:上面的例子PersonName 注解就只有一个参数成员。

     PS:注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。

好吧到这里我们的自定义注解就算是完了。

三、反射处理自定义注解

        下面我们开始给大家整理如何通过反射机制处理我们的注解。到我目前的理解为止,我知道两种注解处理方式一种就是马上要讲解的反射方式处理运行时注解,另一种就是通过继承系统的AbstractProcessor处理编译时注解,ButterKnife就是是通过这种方式实现的编译前搜索绑定数据的。好了,堵住嘴,进正题。

我们在提供一个PersonAge的自定义注解类与上边的PersonName注解一起完成这个例子。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PersonName {
	String value() default "";

}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PersonAge {
public enum Age{little,young,old};
Age personAge() default Age.young;
}

我在PersonAge中定义了一个枚举类型的Age属性,并且有一个personAge变量的值就是Age枚举类中的值。可以看到PersonName与PersonAge都是作用在成员变量上的,都是保留到运行时有效。关于反射,Java为我们单独提供了一个包,com.lang.reflect,在里边你可以看到一些我们在反射中会用到的类,比如Method、Parameter、Type、Constructor等。

然后我们定义一个Person类对象,如下:

public class Person {
	
	public String personName;
	
	public Age personAge;
	public String getPersonName() {
		return personName;
	}
	public void setPersonName(String personName) {
		this.personName = personName;
	}
	public Age getPersonAge() {
		return personAge;
	}
	public void setPersonAge(Age personAge) {
		this.personAge = personAge;
	}
	
}

这个Person类没有什么特别的,就是一个普通的Mode类,下边看一下真正的反射处理类:

public class PersonAnnotationUtils {
	
	public Person settingAnnotation(Class cl,Person p){
		if(p==null){
			p=new Person();
		}
		Field[] fields=cl.getDeclaredFields();
		for(Field f:fields){
			PersonName pn=f.getAnnotation(PersonName.class);
			if(pn!=null){
				p.personName=pn.value();
			}
			PersonAge pa=f.getAnnotation(PersonAge.class);
			if(pa!=null){
				p.personAge=pa.personAge();
			}
		}
		return p;
		
	}
	
	private static PersonAnnotationUtils mUtils=null;
	private PersonAnnotationUtils(){}
	public static PersonAnnotationUtils getInstance(){
		if(mUtils==null){
			synchronized(PersonAnnotationUtils.class){
				if(mUtils==null){
					mUtils=new PersonAnnotationUtils();
				}
			}
		}
		return mUtils;
	}

}

这里我用了一个懒汉式的单例模式获取Utils的实例,但这不是重点,重点是settingAnnotation(Class cl,Person p)方法,再次声明,这只是一个例子,并不代表什么,没有固定的格式,只是为了能够让大家明白怎样通过反射处理我们的注解。可以看到我写的是需要传入一个Class类型的数据,这里我想的是在哪个类里边要用我们的注解,就传入我们的哪个类,Person对象表示我们要处理的对象,我在此还判断了传入的Person对象是不是没有实例化,并且加入实例化代码,下边进入重点,我们通过传入的Class实例获取(通过反射方式)当前类的所有公开的Field(成员变量)Field[]fields=cl.getDeclaredFields();getDeclaredFields()为获取了一个数组,然后循环每一个成员变量,获取变量上边的注解信息,PersonName pn=f.getAnnotation(PersonName.class);如果我们要获取一个变量上边的多个注解还可以通过f.getAnnotations()方法。接着判断我们获取到的注解对象是否为空(如果一个成员变量上边没有我们指定的注解,那么我们通过调用getAnnotation()方法获取到的信息就是null),如果不是空则进行赋值操作,同样获取PersonAge信息的方式与获取PersonName信息的方式一样。这就是我们反射逻辑的代码,对于了解过的童鞋何以略过啊,不要在次耽误您的时间,初学者可以了解一下。好了,到现在为止,我们真正的反射处理逻辑就算是介绍完了,下边看一下应用类代码:

public class Main {
	@PersonName("张三")
	@PersonAge(personAge = Age.old)
	public static Person p;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		p=PersonAnnotationUtils.getInstance().settingAnnotation(Main.class, p);
		System.out.println(p.personName);
		System.out.println(p.personAge);
	}

}

没错,就是这么简单,声明了一个Person成员变量,直接调用Utils反射处理方法,然后我们就能得到一个我们已经处理好的Person对象。下边看一下输出信息:

张三
old

可以看到,我们写在Main.java里边Person成员变量上边的注解信息被成功获取到并赋值给了我们的p对象,下边我们把我们两个自定义注解里边的@Retention(RetentionPolicy.RUNTIME)改成CLASS看一下,这就变成了,编译时有效,看一下输出:

null
null

打印出来两个null,因为ClASS为编译时有效,因为我们的处理是在运行时进行的反射处理,所以没办法获取到我们在注解里设置的值,如果我们继承系统的AbstractProcessor实现里边的一些方法,就能够获取值,并做一些更牛逼,更高逼格的操作,有时间的话,在给大家总结一下AbstractProcessor的相关知识,这篇文者就先整理到这里,谢谢!

        这篇文章部分内容,只是作者个人的理解,如果有不对的地方,望大家指正,谢谢!下边是我写的例子的代码,感兴趣的可以下载,看一下。

  资源下载地址:http://download.csdn.net/detail/liuyonglei1314/9768302


猜你喜欢

转载自blog.csdn.net/liuyonglei1314/article/details/59494503