前言
在Android開(kāi)發(fā)作業(yè)中接觸到了很多開(kāi)源框架使用了Java Annotation機(jī)制闰蛔,我接觸到的就有GreenRobot、Dagger2图柏、AndFix等項(xiàng)目序六。
那么 Annotation機(jī)制到底是如何發(fā)揮作用的?下面將介紹Annotation的常見(jiàn)類(lèi)型及基本語(yǔ)法蚤吹。
從@Override認(rèn)識(shí)注解
相信大部分同學(xué)對(duì)@Override一點(diǎn)都不陌生例诀,在子類(lèi)覆蓋超類(lèi)的方法時(shí),Eclipse等IDE會(huì)在方法上自動(dòng)生成這個(gè)注解裁着。
那么來(lái)看一下這個(gè)注解的語(yǔ)法形式:
package java.lang;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
其中@interface 定義了Override是一個(gè)Annotation類(lèi)型繁涂,或者叫元數(shù)據(jù)(meta-data)。
@Target和@Retetion是對(duì)Override的注解二驰,稱之為元注解(元數(shù)據(jù)的注解)扔罪。
@Target
再來(lái)看下@Target的定義:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
首先Target也是一個(gè)注解類(lèi)型,在其內(nèi)部定義了方法ElementType[] value();
細(xì)心觀察就會(huì)發(fā)現(xiàn)這個(gè)方法的返回值就是@Target(ElementType.METHOD)中的ElementType.METHOD,也就是注解的屬性桶雀,是一個(gè)ElementType枚舉矿酵。
再來(lái)看ElementType的定義:
package java.lang.annotation;
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
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
這個(gè)枚舉其實(shí)就是定義了注解的適用范圍,在Override注解中背犯,@Target的屬性是ElementType.METHOD,所以O(shè)verride這個(gè)注解只能用于注解方法坏瘩。
而Target注解本身也有一個(gè)@Target元注解,這個(gè)@Target元注解屬性是ElementType.ANNOTATION_TYPE漠魏,也就是說(shuō)Target注解只能用作元數(shù)據(jù)(注解)的注解倔矾,所以叫它元注解。
@Retention
@Target聲明了Override注解只能使用代碼中的方法定義柱锹,@Retention注解則定義了注解的保留范圍哪自,如:在源代碼、CLASS文件或運(yùn)行時(shí)保留禁熏。
超出@Retention定義的屬性壤巷,注解將被丟棄。
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
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
}
如果像Override注解中的@Retention定義RetentionPolicy .SOURCE屬性瞧毙,那么生成CLASS文件時(shí)不會(huì)在方法上見(jiàn)到@Override胧华。由于Override注解用于檢測(cè)子類(lèi)有無(wú)實(shí)現(xiàn)超類(lèi)或接口的抽象方法寄症,所以只在編譯階段檢測(cè)語(yǔ)法是否正確就足夠了。
@Documented
@Documented也屬于元注解:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
如果一個(gè)注解定義了@Ducumented矩动,在javadoc API文檔時(shí)有巧,被這個(gè)注解標(biāo)記的元素在文檔上也會(huì)出現(xiàn)該注解,例如:
//自定義一個(gè)注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.Main;
}
//使用自定義的注解
public class A {
@Subscribe
public void a(EventA a){
System.out.println("tid "+Thread.currentThread().getId()+" run A.a() ,event name is "+a.name);
}
}
在javadoc生成的文檔中可見(jiàn):
@Subscribe
public void a(EventA a)
而不在Subsribe注解上添加@Documented則不會(huì)在方法a(EventA a)上出現(xiàn)@Subscribe悲没。
@Inherited
該元注解比較特殊篮迎,只對(duì)@Target為ElementType.TYPE(類(lèi)、接口示姿、枚舉)有效甜橱,并且只支持類(lèi)元素。
使用了@Inherited的注解修飾在一個(gè)class上栈戳,可以保證繼承它的子類(lèi)也擁有同樣的注解岂傲。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
小結(jié)
通過(guò)常見(jiàn)的@Override我們認(rèn)識(shí)了一個(gè)注解定義的語(yǔ)法,并且認(rèn)識(shí)了四個(gè)基本元注解@Target荧琼、@Retention譬胎、@Documented、@Inherited以及它們的功能命锄。
其中@Target用來(lái)指定注解可以修飾的源代碼中的元素(構(gòu)造器堰乔、方法、字段脐恩、包镐侯、參數(shù)驶冒、局部變量、類(lèi)或接口)崇猫,@Retention指定注解保留的范圍(源代碼诅炉、class文件屋厘、運(yùn)行時(shí))汗洒,@Documented指定注解是否出現(xiàn)在javadoc生成的API文檔中的具體的元素上,@Inherited指定注解是否可以被子類(lèi)繼承瞻凤。
自定義注解
使用@interface可以定義一個(gè)注解鲫构,形如:
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.Main;
}
在本例中threadMode()返回的是注解的一個(gè)屬性结笨,其返回值可以是所有基本類(lèi)型湿镀、String、Class勉痴、enum、Annotation或以上類(lèi)型的數(shù)組瀑罗。
default關(guān)鍵字可以定義該屬性的默認(rèn)值雏掠,如果沒(méi)有指定默認(rèn)值在使用注解時(shí)必須顯示指定屬性值。
特別說(shuō)明的是注解不支持像extends語(yǔ)法這樣的繼承摧玫。
自定義注解以及反射使用注解的例子
本例將仿照Eventbus使用注解的語(yǔ)法實(shí)現(xiàn)一個(gè)超簡(jiǎn)易版的Mybus绑青。
//定義運(yùn)行線程枚舉
public enum ThreadMode {
Posting,Main;
}
//定義注解用于反射識(shí)別觀察者方法
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.Main;
}
//定義一個(gè)MyBus類(lèi)用于注冊(cè)闸婴、注銷(xiāo)觀察者或者發(fā)出通知給觀察者,在發(fā)出通知時(shí)會(huì)遍歷觀察者集合邪乍,找出觀察者中被@Subscribe 注解的方法溺欧,判斷通知事件的類(lèi)型與@Subscribe 注解方法的參數(shù)類(lèi)型是否匹配,然后根據(jù)ThreadMode交給觀察者在主線程或子線程處理芥牌。
public class MyBus {
LinkedList<Object> mObjs = new LinkedList<Object>();
public void register(Object o) {
mObjs.add(o);
}
public void unregister(Object o) {
mObjs.remove(o);
}
public void post(final Object event) {
// System.out.println("post參數(shù)類(lèi)型:"+event.getClass().getCanonicalName());
Iterator<Object> iterator = mObjs.iterator();
while (iterator.hasNext()) {
final Object obj = iterator.next();
Class<? extends Object> cl = obj.getClass();
// System.out.println("遍歷類(lèi) "+cl.getCanonicalName());
for (final Method method : cl.getDeclaredMethods()) {
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if (subscribe != null) {
// System.out.println("找到注解的方法 "+method.getName());
if (method.getParameterCount() == 1) {
Class pmClass = (method.getParameterTypes())[0];
// System.out.println(method.getName()+"的參數(shù)類(lèi)型:"+pmClass.getCanonicalName());
if (pmClass.equals(event.getClass())) {
// 判斷參數(shù)的類(lèi)型是post參數(shù)類(lèi)型的超類(lèi)或接口或相等
// System.out.println(method.getName()+" 是合法的");
try {
if (subscribe.threadMode() == ThreadMode.Main) {
method.invoke(obj, event);
}else{
new Thread(){
public void run() {
try {
method.invoke(obj, event);
} catch (IllegalAccessException
| IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
}
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
}
//事件A
public class EventA {
public String name;
}
//事件B
public class EventB {
public String name;
}
//觀察者A
public class A {
@Subscribe
public void a(EventA a){
System.out.println("tid "+Thread.currentThread().getId()+" run A.a() ,event name is "+a.name);
}
}
//觀察者B
public class B {
@Subscribe(threadMode=ThreadMode.Posting)
public void b(EventB e){
System.out.println("tid "+Thread.currentThread().getId()+" run B.b(),event name is "+e.name);
}
}
//測(cè)試
public class Main {
public static void main(String[] args) {
A a= new A();
B b=new B();
MyBus bus = new MyBus();
bus.register(a);
bus.register(b);
EventA eventA = new EventA();
eventA.name="eventA";
EventB eventB = new EventB();
eventB.name = "eventB";
bus.post(eventA);
bus.post(eventB);
}
}
測(cè)試結(jié)果:
tid 1 run A.a() ,event name is eventA
tid 10 run B.b(),event name is eventB
可以看到發(fā)出不同的事件A和B弃理,最后根據(jù)觀察者A和B中方法的注解找到方法處理痘昌,由注解中的ThreadMode屬性指定在哪個(gè)線程執(zhí)行處理方法钥勋。