什么是注解
在Java語(yǔ)法中,使用@符號(hào)作為開(kāi)頭,并在@后面緊跟注解名。被運(yùn)用于類,接口,方法和字段之上。
注解也叫
元數(shù)據(jù),是一種代碼級(jí)別的說(shuō)明,與類,接口。枚舉是在用一個(gè)層次上,他可以聲明在包,類,字段,方法,局部變量,方法參數(shù)等的前面,用來(lái)對(duì)這些變量進(jìn)行說(shuō)明,注釋。注解可以提高代碼的可讀性,它可以向編譯器,虛擬機(jī)等解釋說(shuō)明一些事情。降低項(xiàng)目的耦合度,自動(dòng)生成Java代碼,自動(dòng)完成一些規(guī)律性的代碼,減少開(kāi)發(fā)者的工作量。
注解分類
- Java內(nèi)置注解
- 元注解
- 自定義注解
- 運(yùn)行時(shí)注解
- 編譯時(shí)注解
注解作用分類
- 編寫文檔
- 通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔【生成文檔doc文檔】
- 代碼分析
- 通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析【使用反射】
- 編譯檢查
- 通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能夠?qū)崿F(xiàn)基本的編譯檢查【Override】
Java字段(類成員)和屬性
屬性只局限于類中方法聲明,并不與類中其他的成員相關(guān)
Java中的屬性通??梢岳斫鉃間et和set方法;而字段通常叫做類成員
字段通常是在類中定義的類成員變量
元注解(負(fù)責(zé)注解其他的注解)
-
@Target
- 表示該注解用于什么地方,可能的ElementType參數(shù)包括:
- CONSTRUCTOR:構(gòu)造器的聲明
- FIELD:域聲明
- LOCAL_VARIABLE:局部變量聲明
- METHOD:方法聲明
- PACKAGE:包聲明
- PARAMETER:參數(shù)聲明
- TYPE:類,接口或enum聲明
- 表示該注解用于什么地方,可能的ElementType參數(shù)包括:
-
@Retention
- 表示在什么級(jí)別保留此信息,可選的RetentionPolicy參數(shù)包括:
- SOURCE:注解僅存在代碼中,注解會(huì)被編譯器丟棄
- CLASS:注解會(huì)在class文件中保留,但會(huì)被VM丟棄
- RUNTIME:VM運(yùn)行期間也會(huì)保留該注解,因此可以通過(guò)反射來(lái)獲得該注解
- 表示在什么級(jí)別保留此信息,可選的RetentionPolicy參數(shù)包括:
-
@Documented
- 將注解包含在javadoc中
-
@Inherited
- 允許子類繼承父類的注解
Java內(nèi)置注解
-
@Override,表示當(dāng)前的方法定義將覆蓋超類中的方法,如果出現(xiàn)錯(cuò)誤,編譯器就會(huì)報(bào)錯(cuò)。
- 當(dāng)我們的子類覆寫父類中的方法的時(shí)候,我們使用這個(gè)注解,這一定程度的提高了程序的可讀性也避免了維護(hù)中的一些問(wèn)題,比如說(shuō),當(dāng)修改父類方法簽名(方法名和參數(shù))的時(shí)候,你有很多個(gè)子類方法簽名也必須修改,否則編譯器就會(huì)報(bào)錯(cuò),當(dāng)你的類越來(lái)越多的時(shí)候,那么這個(gè)注解確實(shí)會(huì)幫上你的忙。如果你沒(méi)有使用這個(gè)注解,那么你就很難追蹤到這個(gè)問(wèn)題。
-
@Deprecated:如果使用此注解,編譯器會(huì)出現(xiàn)警告信息。
- 一個(gè)棄用的元素(類,方法和字段)在java中表示不再重要,它表示了該元素將會(huì)被取代或者在將來(lái)被刪除。
當(dāng)我們棄用(deprecate)某些元素的時(shí)候我們使用這個(gè)注解。所以當(dāng)程序使用該棄用的元素的時(shí)候編譯器會(huì)彈出警告。當(dāng)然我們也需要在注釋中使用@deprecated標(biāo)簽來(lái)標(biāo)示該注解元素。
- 一個(gè)棄用的元素(類,方法和字段)在java中表示不再重要,它表示了該元素將會(huì)被取代或者在將來(lái)被刪除。
-
@SuppressWarnings:忽略編譯器的警告信息
- 當(dāng)我們想讓編譯器忽略一些警告信息的時(shí)候,我們使用這個(gè)注解。比如在下面這個(gè)示例中,我們的deprecatedMethod()方法被標(biāo)記了@Deprecated注解,所以編譯器會(huì)報(bào)警告信息,但是我們使用了@SuppressWarnings("deprecation")也就讓編譯器不在報(bào)這個(gè)警告信息了
自定義注解
- 運(yùn)行時(shí)注解大多數(shù)時(shí)候?qū)崟r(shí)運(yùn)行時(shí)使用反射來(lái)實(shí)現(xiàn)所需效果,這很大程度上影響效率
- 編譯時(shí)注解在編譯時(shí)生成對(duì)應(yīng)Java代碼實(shí)現(xiàn)代碼注入
自定義注解實(shí)現(xiàn)及使用
自定義注解使用@interface來(lái)聲明一個(gè)注解。創(chuàng)建一個(gè)自定義注解遵循: public @interface 注解名 {方法參數(shù)}
自定義注解示例一
@Documented
@Target(ElementType.METHOD)
@Inherited @Retention(RetentionPolicy.RUNTIME)
public @interface Annotation{
int studentAge() default 18; //定義默認(rèn)值
String studentName();
String stuAddress();
String stuStream() default "CSE";
}
@Annotation(studentName = "Chaitanya", stuAddress = "Agra, India")
public class Class {
...
}
自定義注解示例二
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface getViewTo {
int value() default -1;
}
public class MainActivity extends AppCompatActivity {
@getViewTo(R.id.textview)
private TextView mTv;
/**
* 解析注解,獲取控件
*/
private void getAllAnnotationView() {
//獲得成員變量
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
//判斷注解
if (field.getAnnotations() != null) {
//確定注解類型
if (field.isAnnotationPresent(GetViewTo.class)) {
//允許修改反射屬性
field.setAccessible(true);
GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
//findViewById將注解的id,找到View注入成員變量中
field.set(this, findViewById(getViewTo.value()));
}
}
} catch (Exception e) {
}
}
}
}
編譯時(shí)注解
說(shuō)到編譯時(shí)注解,就不得不說(shuō)注解處理器 AbstractProcessor,如果你有注意,一般第三方注解相關(guān)的類庫(kù)(基于注解的框架),如bufferKnike、ARouter,都有一個(gè)Compiler命名的Module,如下圖X2.3,這里面一般都是注解處理器,用于編譯時(shí)處理對(duì)應(yīng)的注解。
注解處理器(Annotation Processor)是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解(Annotation)。你可以對(duì)自定義注解注冊(cè)相應(yīng)的注解處理器,用于處理注解邏輯。
javac是收錄于JDK中的Java語(yǔ)言編譯器。該工具可以將后綴名為.java的源文件編譯為后綴名為.class的可以運(yùn)行于Java虛擬機(jī)的字節(jié)碼。
注解處理器
實(shí)現(xiàn)一個(gè)自定義注解處理器,至少重寫四個(gè)方法,并注冊(cè)你的Processor(為自定義注解注冊(cè)相應(yīng)的注解處理器,用于處理注解邏輯)
-
@AutoService(Processor.class),谷歌提供的自動(dòng)注冊(cè)注解,為你生成注冊(cè)Processor所需要的格式文件(com.google.auto相關(guān)包)。 -
init(ProcessingEnvironment env),初始化處理器,一般在這里獲取我們需要的工具類。 -
getSupportedAnnotationTypes(),指定注解處理器是注冊(cè)給哪個(gè)注解的,返回指定支持的注解類集合。 -
getSupportedSourceVersion(),指定java版本。 -
process(),處理器實(shí)際處理邏輯入口。
注解處理器基本代碼
init()方法傳入一個(gè)參數(shù)processingEnv,可以幫助我們?nèi)コ跏蓟恍┹o助類:
- Filer mFileUtils; 跟文件相關(guān)的輔助類,生成JavaSourceCode.
- Elements mElementUtils;跟元素相關(guān)的輔助類,幫助我們?nèi)カ@取一些元素相關(guān)的信息。
- Messager mMessager;跟日志相關(guān)的輔助類。
注解處理器一般處理邏輯
1、遍歷得到源碼中,需要解析的元素列表。
2、判斷元素是否可見(jiàn)和符合要求。
3、組織數(shù)據(jù)結(jié)構(gòu)得到輸出類參數(shù)。
4、輸入生成Java文件。
5、錯(cuò)誤處理。
Processor處理過(guò)程中,會(huì)掃描全部Java源碼,代碼的每一個(gè)部分都是一個(gè)特定類型(比如類、變量、方法)的Element,它們像是XML一層的層級(jí)機(jī)構(gòu),比如類、變量、方法等,每個(gè)Element代表一個(gè)靜態(tài)的、語(yǔ)言級(jí)別的構(gòu)件。
Element代表的是源代碼,而TypeElement代表的是源代碼中的類型元素,例如類。然而,TypeElement并不包含類本身的信息。你可以從TypeElement中獲取類的名字,但是你獲取不到類的信息,例如它的父類。這種信息需要通過(guò)TypeMirror獲取。你可以通過(guò)調(diào)用elements.asType()獲取元素的TypeMirror。
Element 相關(guān)子類
- VariableElement //一般代表成員變量
- ExecutableElement //一般代表類中的方法
- TypeElement //一般代表代表類
- PackageElement //一般代表Package
如何編寫基于編譯時(shí)注解的項(xiàng)目
在Android應(yīng)用開(kāi)發(fā)中,我們常常為了提升開(kāi)發(fā)效率會(huì)選擇使用一些基于注解的框架,但是由于反射造成一定運(yùn)行效率的損耗,所以我們會(huì)更青睞于編譯時(shí)注解的框架,例如:
- ButterKnife免去我們編寫View的初始化以及事件的注入的代碼。
- EventBus3方便我們實(shí)現(xiàn)組建間通訊。
- Fragmentargs輕松的為Fragment添加參數(shù)信息,并提供創(chuàng)建方法。
- ParcelableGenerator可實(shí)現(xiàn)自動(dòng)將任意對(duì)象轉(zhuǎn)換為Parcelable類型,方便對(duì)象傳輸。
項(xiàng)目結(jié)構(gòu)劃分
在編寫此類框架的時(shí)候,一般需要建立多個(gè)module,例如:
- xxx-annotation 用于存放注解等,Java模塊
- xxx-compiler 用于編寫注解處理器,Java模塊
- xxx-api 用于給用戶提供使用的API,本例為Andriod模塊
- xxx-sample 示例,本例為Andriod模塊
注解處理器只需要在編譯的時(shí)候使用,并不需要打包到APK中。因此為了用戶考慮,我們需要將注解處理器分離為單獨(dú)的module。
對(duì)于module間的依賴,因?yàn)榫帉懽⒔馓幚砥餍枰蕾囅嚓P(guān)注解,所以:
ioc-compiler依賴ioc-annotation>。我們?cè)谑褂玫倪^(guò)程中,會(huì)用到注解以及相關(guān)API。所以ioc-sample依賴ioc-api;ioc-api依賴ioc-annotation
注解模塊的實(shí)現(xiàn)
注解模塊,主要用于存放一些注解類。
注解處理器的實(shí)現(xiàn)
實(shí)現(xiàn)一個(gè)注解處理器,至少需要重寫四個(gè)方法。該模塊,我們一般會(huì)依賴注解模塊,以及可以使用一個(gè)auto-service庫(kù),auto-service庫(kù)可以幫我們?nèi)ド蒑ETA-INF等信息。
build.gradle的依賴情況如下:
dependencies {
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile project (':ioc-annotation')
}
process的實(shí)現(xiàn)
process()注解處理器實(shí)際處理邏輯入口。主要是獲取被注解的參數(shù)列表,組織數(shù)據(jù)結(jié)構(gòu)得到輸出類參數(shù),生成Java文件。process中的實(shí)現(xiàn)一般可以認(rèn)為兩個(gè)步驟:
- 收集信息
- 生成代理類(本文把編譯時(shí)生成的類叫代理類)
什么叫收集信息呢?就是根據(jù)你的注解聲明,拿到對(duì)應(yīng)的Element,然后獲取到我們所需要的信息,這個(gè)信息肯定是為了后面生成JavaFileObject所準(zhǔn)備的。
例如本例,我們會(huì)針對(duì)每一個(gè)類生成一個(gè)代理類,例如MainActivity我們會(huì)生成一個(gè)MainActivity$$ViewInjector。那么如果多個(gè)類中聲明了注解,就對(duì)應(yīng)了多個(gè)類,這里就需要:
- 一個(gè)類對(duì)象,代表具體某個(gè)類的代理類生成的全部信息,本例中為ProxyInfo
- 一個(gè)集合,存放上述類對(duì)象(到時(shí)候遍歷生成代理類),本例中Map<String, ProxyInfo>,key為類的全路徑。
收集信息
首先調(diào)用mProxyMap.clear(),因?yàn)?code>process可能會(huì)多次調(diào)用,避免生成重復(fù)的代理類,避免生成類的類名已存在異常。
然后,通過(guò)roundEnv.getElementsAnnotatedWith()獲取被@BindView注解的元素,這里返回值,按照我們的預(yù)期應(yīng)該是VariableElement集合,因?yàn)槲覀冇糜诔蓡T變量上。
接下來(lái)for循環(huán)我們的元素,首先檢查類型是否是VariableElement(對(duì)元素列表進(jìn)行額外判斷,校驗(yàn)元素是否可用),然后獲取元素VariableElement對(duì)應(yīng)的類信息TypeElement,繼而生成ProxyInfo對(duì)象。這里先通過(guò)一個(gè)mProxyMap進(jìn)行檢查,key為qualifiedName即類的全路徑,如果沒(méi)有生成才會(huì)去生成一個(gè)新的ProxyInfo實(shí)例,ProxyInfo與類是一一對(duì)應(yīng)的。
接下來(lái),會(huì)將與該類對(duì)應(yīng)的且被@BindView聲明的VariableElement加入到ProxyInfo中去,key為我們聲明時(shí)填寫的id,即View的id。
這樣就完成了信息的收集,收集完成信息后,應(yīng)該就可以去生成代理類了。
生成代理類
遍歷mProxyMap,然后取得每一個(gè)ProxyInfo,最后通過(guò)mFileUtils.createSourceFile()來(lái)創(chuàng)建文件對(duì)象,類名為proxyInfo.getProxyClassFullName(),寫入的內(nèi)容為proxyInfo.generateJavaCode()(生成Java代碼)。
生成Java代碼
ProxyInfo.generateJavaCode()方法通過(guò)收集得到的信息,拼接完成的代理類對(duì)象。也可以使用開(kāi)源庫(kù),例如:javapoet,來(lái)通過(guò)Java API的方式來(lái)生成代碼。javapoet (com.squareup:javapoet)是一個(gè)根據(jù)指定參數(shù),生成java文件的開(kāi)源庫(kù)。
生成的代碼實(shí)現(xiàn)了一個(gè)接口ViewInjector<T>,該接口是為了統(tǒng)一所有的代理類對(duì)象的類型,到時(shí)候我們需要強(qiáng)轉(zhuǎn)代理類對(duì)象為該接口類型,調(diào)用其方法。接口是泛型,主要就是傳入實(shí)際類對(duì)象,例如:MainActivity,因?yàn)槲覀冊(cè)谏纱眍愔械拇a,實(shí)際上就是實(shí)際類.成員變量的方式進(jìn)行訪問(wèn),所以,使用編譯時(shí)注解的成員變量一般都不允許private修飾符修飾(有的允許,但是需要提供getter,setter訪問(wèn)方法)。
API模塊的實(shí)現(xiàn)
有了代理類之后,我們一般還會(huì)提供API供用戶去訪問(wèn)
API一般如何編寫呢?
- 根據(jù)傳入的
host尋找我們生成的代理類:例如:MainActivity->MainActity$$ViewInjector。 - 強(qiáng)轉(zhuǎn)為統(tǒng)一的接口,調(diào)用接口提供的方法。
這兩件事應(yīng)該不復(fù)雜,第一件事是拼接代理類名,然后反射生成對(duì)象,第二件事強(qiáng)轉(zhuǎn)調(diào)用。拼接代理類的全路徑,然后通過(guò)newInstance生成實(shí)例,然后強(qiáng)轉(zhuǎn),調(diào)用代理類的inject()方法。
ButterKnife工作流程解析
Butter Knife,專門為Android View設(shè)計(jì)的綁定注解,專業(yè)解決各種findViewById。
ButterKnife有哪些優(yōu)勢(shì)?
- 強(qiáng)大的View綁定和Click事件處理功能,簡(jiǎn)化代碼,提升開(kāi)發(fā)效率
- 方便的處理Adapter里的ViewHolder綁定問(wèn)題
- 運(yùn)行時(shí)不會(huì)影響APP效率,使用配置方便
- 代碼清晰,可讀性強(qiáng)
ButterKnife工作流程
- 開(kāi)始它會(huì)掃描Java代碼中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等。
- 當(dāng)它發(fā)現(xiàn)一個(gè)類中含有任何一個(gè)注解時(shí), ButterKnifeProcessor會(huì)幫你生成一個(gè)Java類,名字<類名>$$ViewInjector.java,這個(gè)新生成的類實(shí)現(xiàn)了ViewBinder接口。
- 這個(gè)ViewBinder類中包含了所有對(duì)應(yīng)的代碼,比如@Bind注解對(duì)應(yīng)findViewById(), @OnClick對(duì)應(yīng)了view.setOnClickListener()等等。
- 最后當(dāng)Activity啟動(dòng)ButterKnife.bind(this)執(zhí)行時(shí),ButterKnife會(huì)去加載對(duì)應(yīng)的ViewBinder類調(diào)用它們的bind()方法。
Java注解工作流程
- 注解是在編譯(Compile)時(shí)期進(jìn)行處理的
- 注解處理器(Annotation Processor)讀取Java代碼處理相應(yīng)的注解,并且生成對(duì)應(yīng)的代碼
- 生成的Java代碼被當(dāng)做普通的Java類再次編譯
-
注解處理器不能修改存在Java輸入文件,也不能對(duì)方法做修改或者添加
Java編譯流程.png
