一 注解的定義
注解(Annotation),也叫元數(shù)據(jù)。一種代碼級別的說明。它是JDK1.5及以后版本引入的一個(gè)特性,與類、接口、枚舉是在同一個(gè)層次。它可以聲明在包、類、字段、方法、局部變量、方法參數(shù)等的前面,用來對這些元素進(jìn)行說明 。如果要對于元數(shù)據(jù)的作用進(jìn)行分類,還沒有明確的定義,不過我們可以根據(jù)它所起的作用,注解不會(huì)改變編譯器的編譯方式,也不會(huì)改變虛擬機(jī)指令執(zhí)行的順序,它更可以理解為是一種特殊的注釋,本身不會(huì)起到任何作用,需要工具方法或者編譯器本身讀取注解的內(nèi)容繼而控制進(jìn)行某種操作。大致可分為三類:
編寫文檔:通過代碼里標(biāo)識的元數(shù)據(jù)生成文檔。
代碼分析:通過代碼里標(biāo)識的元數(shù)據(jù)對代碼進(jìn)行分析。
編譯檢查:通過代碼里標(biāo)識的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查。
二 用途
因?yàn)樽⒔饪梢栽俅a編譯期間幫我們完成一些復(fù)雜的準(zhǔn)備工作,所以我們可以利用注解去完成我們的一些準(zhǔn)備工作。比如Greendao,我們注解一個(gè)實(shí)體類,它要處理成好多邏輯關(guān)系類,這些邏輯類讓我們自己去書寫的話那將是一個(gè)龐大的代碼量, extends AbstractDao等這些類。比如BufferKnife,我們用注解將控件的屬性傳遞給它,它將生成一些功能類去處理這些值,***_ViewBinding等這些類型的類。運(yùn)行時(shí)注解的使用,比如Retrofit的@GET或者@POST等等都是運(yùn)行時(shí)注解,它的注解的處理必須跟Retrofit的對象有關(guān)聯(lián),所以必須定義成運(yùn)行時(shí)的。所以注解已經(jīng)成為一種趨勢,比如BufferKnife,EventBus,Darrger,Greendao,Arouter,Retrofit。。。看來我們也要去完成我們的一個(gè)注解了。
三 知識準(zhǔn)備
Java JDK中包含了三個(gè)注解分別為@Override(校驗(yàn)格式),@Deprecated(標(biāo)記過時(shí)的方法或者類),@SuppressWarnnings(注解主要用于抑制編譯器警告),對于每個(gè)注解的具體使用細(xì)節(jié)這里不再論述。我們可以通過點(diǎn)擊這里來看一下專業(yè)解釋! 來看一下@Override的源碼。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
通過源代碼的閱讀我們可以看出生命注解的方式為@interface,每個(gè)注解都需要不少于一個(gè)的元注解的修飾,這里的元注解其實(shí)就是修飾注解的注解,可以理解成最小的注解單位吧。。。下面詳細(xì)的看下每個(gè)注釋注解的意義吧:
-
@Target :說明了Annotation所修飾的對象范圍,也就是我們這個(gè)注解是用在那個(gè)對象上面的:Annotation可被用于 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標(biāo)。以下屬性是多選狀態(tài),我們可以定義多個(gè)注解作用域,比如:
(1).CONSTRUCTOR:用于描述構(gòu)造器。
(2).FIELD:用于描述域也就是類屬性之類的。
(3).LOCAL_VARIABLE:用于描述局部變量。
(4).METHOD:用于描述方法。
(5).PACKAGE:用于描述包。
(6).PARAMETER:用于描述參數(shù)。
(7).TYPE:用于描述類、接口(包括注解類型) 或enum聲明。
@Target({ElementType.METHOD,ElementType.FIELD}),單個(gè)的使用@Target(ElementType.FIELD) 。
-
@Retention:定義了該Annotation被保留的時(shí)間長短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對 Annotation的“生命周期”限制。來源于java.lang.annotation.RetentionPolicy的枚舉類型值:
(1).SOURCE:在源文件中有效(即源文件保留)編譯成class文件將舍棄該注解。
(2).CLASS:在class文件中有效(即class保留) 編譯成dex文件將舍棄該注解。
(3).RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留) 運(yùn)行時(shí)可見。
也就是說注解處理器能處理這三類的注解,我們通過反射的話只能處理RUNTIME類型的注解.
@Documented:用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個(gè)標(biāo)記注解,沒有成員。
@Inherited:元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。 注意:@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。
@Repeatable:
Repeatable可重復(fù)性,Java 1.8新特性,其實(shí)就是把標(biāo)注的注解放到該元注解所屬的注解容器里面。可重復(fù)性的意思還是用demo來解釋一下吧:
//定義了一個(gè) 注解里面屬性的返回值是其他注解的數(shù)組
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCar {
MyTag[] value(); ----MyTag 這里就是MyTag注解的容器。
}
//另外一個(gè)注解 就是上一個(gè)注解返回的注解
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
@Repeatable(MyCar.class) --------這里添加這個(gè)屬性之后 我們的這個(gè)注解就可以重復(fù)的添加到我們定義的容器中了,注意里面的值時(shí) 我們定義的容器注解的class對象.
public @interface MyTag { ........MyTag
String name () default "" ;
int size () default 0 ;
}
//使用
@MyTag(name = "BWM", size = 100)
@MyTag()
public Car car;
//如果我們的注解沒有@Repeatable的話,這樣寫的話是報(bào)錯(cuò)的,加上之后就是這樣的了
這個(gè)注解是很特殊的,我們的注解中有@Repeatable(MyCar.class)這樣的元注解的話,就是說當(dāng)前標(biāo)注的注解(MyTag注解)放到我們的值(MyCar.class)這個(gè)注解容器里面。那么我們再處理注解的時(shí)候獲取到的是我們最后的注解容器(MyCar注解),這樣說有點(diǎn)生硬下面看demo:
使用:
public class HomeActivity extends AppCompatActivity {
@MyTag(name = "BWM", size = 100)
@MyTag(name = "大眾" ,size = 200) ......這里用了它的重復(fù)性.
Car car;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
AnnotationProccessor.instance().inject(this); //這里去處理注解
// Log.e("WANG", "HomeActivity.onCreate." + car.toString());
}
}
處理過程:
Class<?> aClass = o.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field:declaredFields) {
if(field.getName().equals("car")){
Annotation[] annotations = field.getAnnotations();
for (int i = 0; i <annotations.length; i++) {
Annotation annotation = annotations[i];
//我們獲取的該字段上面的注解只有一個(gè) 那就是 MyCar注解,看結(jié)果1的打印.
//但是我們明明標(biāo)注的是 MyTag. 為什么獲取的是注解容器呢.
//這就是@Repeatable的強(qiáng)大之處.
Class<? extends Annotation> aClass1 = annotation.annotationType();
Log.e("WANG","AnnotationProccessor.MyCar"+aClass1 );
}
MyCar annotation = field.getAnnotation(MyCar.class);
MyTag[] value = annotation.value();
for (int i = 0; i <value.length; i++) {
MyTag myTag = value[i];
Log.e("WANG","AnnotationProccessor.MyTag name value is "+myTag.name() );
}
}
結(jié)果是:
AnnotationProccessor.MyCarinterface cn.example.wang.routerdemo.annotation.MyCar.1
AnnotationProccessor.MyTag name value is BWM.2
AnnotationProccessor.MyTag name value is 大眾.3
三 自定義運(yùn)行時(shí)注解
通過以上的學(xué)習(xí)我們知道@interface是聲明注解的關(guān)鍵字,每個(gè)注解需要注明生命周期以及作用范圍.你可以給注解定義值.也就是再注解內(nèi)部定義我們需要的方法.這樣注解就可以再自己的生命周期內(nèi)為我們做事.這里我們就自定義一個(gè)為一個(gè)對象屬性初始化的注解吧,類似于Dagger的功能。
public @interface MyTag {
}
注解里面的定義也是有規(guī)定的:
注解方法不能帶有參數(shù)。
注解方法返回值類型限定為:基本類型、String、Enums、Annotation或者這些類型的數(shù)組。
注解方法可以有默認(rèn)值。
注解本身能夠包含元注解,元注解被用來注解其他注解。
我們就來試一下吧!
public @interface MyTag {
//聲明返回值類型,這里可沒有大括號啊,可以設(shè)置默認(rèn)返回值,然后就直接";"了啊。
String name () default "" ;
int size () default 0 ;
}
定義好了注解我們就來規(guī)定我們自定義的注解要在哪里用?要何時(shí)用?因?yàn)槲覀冞@里使用了反射來處理注解,反射就是在代碼的運(yùn)行的時(shí)候通過class對象反相的去獲取類內(nèi)部的東西,不熟悉反射機(jī)制的請移步這里Android開發(fā)者必須了解的反射基礎(chǔ),所以我們定義該注解的生命周期在運(yùn)行時(shí),并且該注解的的目的是為自定義屬性賦值,那么我們的作用域就是FIELD。這里面定義了我們要初始化的bean的基本屬性,給了默認(rèn)值。這樣我們就可以用該注解去創(chuàng)建我們需要的bean對象。
@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
String name () default "" ;
int size () default 0 ;
}
好了接下來看怎么使用我們的這個(gè)自定義的注解!
public class HomeActivity extends AppCompatActivity {
@MyTag(name = "BMW",size = 100)
Car car;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
//這里我們要首先注冊一下這個(gè)類
AnnotationCar.instance().inject(this);
//當(dāng)程序運(yùn)行的時(shí)候這里將會(huì)輸出該類Car的屬性值。
Log.e("WANG","Car is "+car.toString());
}
}
注解如果沒有注解處理器,那么該注解將毫無意義。這里呢我們在這個(gè)Activity里面定義了一個(gè)Car類的屬性,然后再car這個(gè)變量上面定義我們的注解,并且給我們的注解賦值。然后我們再onCreate方法里面初始化我們的注解處理器。然后運(yùn)行代碼,log日志將打印Car類的信息,先來看下結(jié)果吧!
cn.example.wang.routerdemo E/WANG: Car is Car [name=BMW, size=100]
這樣我們的自定義注解就有作用了,下面是”注解處理器“的代碼,這里都是我們自己編寫的處理注解的代碼,其實(shí)系統(tǒng)是自帶注解處理器的,不過它一般用來處理源碼注釋和編譯時(shí)注釋。
//自定義的類
/**
* Created by WANG on 17/11/21.
*/
public class AnnotationCar {
private static AnnotationCar annotationCar;
public static AnnotationCar instance(){
synchronized (AnnotationCar.class){
if(annotationCar == null){
annotationCar = new AnnotationCar();
}
return annotationCar;
}
}
public void inject(Object o){
Class<?> aClass = o.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field:declaredFields) {
if(field.getName().equals("car") && field.isAnnotationPresent(MyTag.class)) {
MyTag annotation = field.getAnnotation(MyTag.class);
Class<?> type = field.getType();
if(Car.class.equals(type)) {
try {
field.setAccessible(true);
field.set(o, new Car(annotation.name(), annotation.size()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
這就說明了為什么注解和反射是同時(shí)進(jìn)入我們的知識圈里面的吧!這里呢我們先獲取到類里面所有的屬性,然后去找到被我們的注解MyTag修飾的那個(gè)屬性,然后找到之后,先取我們注解里面的值,然后賦值給我們類里面的屬性!這樣我們就用注解去初始化了一個(gè)屬性值。就是這么簡單!
四 總結(jié)
運(yùn)行時(shí)注解是我們比較好理解的,知道反射和注解基礎(chǔ)之后就可以寫出來個(gè)小demo了。但是運(yùn)行時(shí)注解是是我們最不常用的注解,因?yàn)榉瓷湓龠\(yùn)行時(shí)的操作是十分的耗時(shí)的,我們不會(huì)因?yàn)橐恍┐a的簡潔而影響app的性能。所以呢運(yùn)行時(shí)注解只是大家認(rèn)識注解的一個(gè)入口。接下來我將陸續(xù)的介紹注解的通用寫法。
下一章節(jié)將詳細(xì)介紹,CLASS注解和SOURCE注解,讓我們來完成屬于自己的BufferKnife!
歡迎大家評論區(qū)留言指出文章錯(cuò)誤~ 謝謝各位看官!
歡迎大家關(guān)注!
我的掘金
我的CSDN
我的簡書
Github