註解學習筆記
什麼是註解
在Java語法中,使用@符號作為開頭,並在@後面緊跟註解名。被運用於類,介面,方法和欄位之上。
註解也叫
後設資料
,是一種程式碼級別的說明
,與類,介面。列舉是在用一個層次上,他可以宣告在包,類,欄位,方法,區域性變數,方法引數等的前面,用來對這些變數進行說明,註釋。註解可以提高程式碼的可讀性,它可以向編譯器,虛擬機器等解釋說明一些事情。降低專案的耦合度,自動生成Java程式碼,自動完成一些規律性的程式碼,減少開發者的工作量。
註解分類
- Java內建註解
- 元註解
- 自定義註解
- 執行時註解
- 編譯時註解
註解作用分類
- 編寫文件
- 通過程式碼裡標識的後設資料生成文件【生成文件doc文件】
- 程式碼分析
- 通過程式碼裡標識的後設資料對程式碼進行分析【使用反射】
- 編譯檢查
- 通過程式碼裡標識的後設資料讓編譯器能夠實現基本的編譯檢查【Override】
Java欄位(類成員)和屬性
屬性只侷限於類中方法宣告,並不與類中其他的成員相關
Java中的屬性通常可以理解為get和set方法;而欄位通常叫做類成員
欄位通常是在類中定義的類成員變數
元註解(負責註解其他的註解)
-
@Target
- 表示該註解用於什麼地方,可能的ElementType引數包括:
- CONSTRUCTOR:構造器的宣告
- FIELD:域宣告
- LOCAL_VARIABLE:區域性變數宣告
- METHOD:方法宣告
- PACKAGE:包宣告
- PARAMETER:引數宣告
- TYPE:類,介面或enum宣告
- 表示該註解用於什麼地方,可能的ElementType引數包括:
-
@Retention
- 表示在什麼級別保留此資訊,可選的RetentionPolicy引數包括:
- SOURCE:註解僅存在程式碼中,註解會被編譯器丟棄
- CLASS:註解會在class檔案中保留,但會被VM丟棄
- RUNTIME:VM執行期間也會保留該註解,因此可以通過反射來獲得該註解
- 表示在什麼級別保留此資訊,可選的RetentionPolicy引數包括:
-
@Documented
- 將註解包含在javadoc中
-
@Inherited
- 允許子類繼承父類的註解
Java內建註解
-
@Override,表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。
- 當我們的子類覆寫父類中的方法的時候,我們使用這個註解,這一定程度的提高了程式的可讀性也避免了維護中的一些問題,比如說,當修改父類方法簽名(方法名和引數)的時候,你有很多個子類方法簽名也必須修改,否則編譯器就會報錯,當你的類越來越多的時候,那麼這個註解確實會幫上你的忙。如果你沒有使用這個註解,那麼你就很難追蹤到這個問題。
-
@Deprecated:如果使用此註解,編譯器會出現警告資訊。
- 一個棄用的元素(類,方法和欄位)在java中表示不再重要,它表示了該元素將會被取代或者在將來被刪除。
當我們棄用(deprecate)某些元素的時候我們使用這個註解。所以當程式使用該棄用的元素的時候編譯器會彈出警告。當然我們也需要在註釋中使用@deprecated標籤來標示該註解元素。
- 一個棄用的元素(類,方法和欄位)在java中表示不再重要,它表示了該元素將會被取代或者在將來被刪除。
-
@SuppressWarnings:忽略編譯器的警告資訊
- 當我們想讓編譯器忽略一些警告資訊的時候,我們使用這個註解。比如在下面這個示例中,我們的deprecatedMethod()方法被標記了@Deprecated註解,所以編譯器會報警告資訊,但是我們使用了@SuppressWarnings("deprecation")也就讓編譯器不在報這個警告資訊了
自定義註解
- 執行時註解大多數時候實時執行時使用反射來實現所需效果,這很大程度上影響效率
- 編譯時註解在編譯時生成對應Java程式碼實現程式碼注入
自定義註解實現及使用
自定義註解使用@interface來宣告一個註解。建立一個自定義註解遵循: public @interface 註解名 {方法引數}
自定義註解示例一
@Documented
@Target(ElementType.METHOD)
@Inherited @Retention(RetentionPolicy.RUNTIME)
public @interface Annotation{
int studentAge() default 18; //定義預設值
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) {
}
}
}
}
編譯時註解
說到編譯時註解,就不得不說註解處理器 AbstractProcessor,如果你有注意,一般第三方註解相關的類庫(基於註解的框架),如bufferKnike、ARouter,都有一個Compiler命名的Module,如下圖X2.3,這裡面一般都是註解處理器,用於編譯時處理對應的註解。
註解處理器(Annotation Processor)是javac的一個工具,它用來在編譯時掃描和處理註解(Annotation)。你可以對自定義註解註冊相應的註解處理器,用於處理註解邏輯。
javac是收錄於JDK中的Java語言編譯器。該工具可以將字尾名為.java的原始檔編譯為字尾名為.class的可以執行於Java虛擬機器的位元組碼。
註解處理器
實現一個自定義註解處理器,至少重寫四個方法,並註冊你的Processor(為自定義註解註冊相應的註解處理器,用於處理註解邏輯)
-
@AutoService(Processor.class)
,谷歌提供的自動註冊註解,為你生成註冊Processor
所需要的格式檔案(com.google.auto
相關包)。 -
init(ProcessingEnvironment env)
,初始化處理器,一般在這裡獲取我們需要的工具類。 -
getSupportedAnnotationTypes()
,指定註解處理器是註冊給哪個註解的,返回指定支援的註解類集合。 -
getSupportedSourceVersion()
,指定java
版本。 -
process()
,處理器實際處理邏輯入口。
註解處理器基本程式碼
init()方法傳入一個引數processingEnv,可以幫助我們去初始化一些輔助類:
- Filer mFileUtils; 跟檔案相關的輔助類,生成JavaSourceCode.
- Elements mElementUtils;跟元素相關的輔助類,幫助我們去獲取一些元素相關的資訊。
- Messager mMessager;跟日誌相關的輔助類。
註解處理器一般處理邏輯
1、遍歷得到原始碼中,需要解析的元素列表。
2、判斷元素是否可見和符合要求。
3、組織資料結構得到輸出類引數。
4、輸入生成Java檔案。
5、錯誤處理。
Processor處理過程中,會掃描全部Java原始碼,程式碼的每一個部分都是一個特定型別(比如類、變數、方法)的Element,它們像是XML一層的層級機構,比如類、變數、方法等,每個Element代表一個靜態的、語言級別的構件。
Element代表的是原始碼,而TypeElement代表的是原始碼中的型別元素,例如類。然而,TypeElement並不包含類本身的資訊。你可以從TypeElement中獲取類的名字,但是你獲取不到類的資訊,例如它的父類。這種資訊需要通過TypeMirror獲取。你可以通過呼叫elements.asType()獲取元素的TypeMirror。
Element 相關子類
- VariableElement //一般代表成員變數
- ExecutableElement //一般代表類中的方法
- TypeElement //一般代表代表類
- PackageElement //一般代表Package
如何編寫基於編譯時註解的專案
在Android應用開發中,我們常常為了提升開發效率會選擇使用一些基於註解的框架,但是由於反射造成一定執行效率的損耗,所以我們會更青睞於編譯時註解的框架,例如:
- ButterKnife免去我們編寫View的初始化以及事件的注入的程式碼。
- EventBus3方便我們實現組建間通訊。
- Fragmentargs輕鬆的為Fragment新增引數資訊,並提供建立方法。
- ParcelableGenerator可實現自動將任意物件轉換為Parcelable型別,方便物件傳輸。
專案結構劃分
在編寫此類框架的時候,一般需要建立多個module,例如:
- xxx-annotation 用於存放註解等,Java模組
- xxx-compiler 用於編寫註解處理器,Java模組
- xxx-api 用於給使用者提供使用的API,本例為Andriod模組
- xxx-sample 示例,本例為Andriod模組
註解處理器只需要在編譯的時候使用,並不需要打包到APK中。因此為了使用者考慮,我們需要將註解處理器分離為單獨的module。
對於module間的依賴,因為編寫註解處理器需要依賴相關注解,所以:ioc-compiler
依賴ioc-annotation>
。我們在使用的過程中,會用到註解以及相關API。所以ioc-sample
依賴ioc-api
;ioc-api
依賴ioc-annotation
註解模組的實現
註解模組,主要用於存放一些註解類。
註解處理器的實現
實現一個註解處理器,至少需要重寫四個方法。該模組,我們一般會依賴註解模組,以及可以使用一個auto-service庫,auto-service庫可以幫我們去生成META-INF等資訊。
build.gradle的依賴情況如下:
dependencies {
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile project (':ioc-annotation')
}
process的實現
process()
註解處理器實際處理邏輯入口。主要是獲取被註解的引數列表,組織資料結構得到輸出類引數,生成Java檔案。process中的實現一般可以認為兩個步驟:
- 收集資訊
- 生成代理類(本文把編譯時生成的類叫代理類)
什麼叫收集資訊呢?就是根據你的註解宣告,拿到對應的Element,然後獲取到我們所需要的資訊,這個資訊肯定是為了後面生成JavaFileObject所準備的。
例如本例,我們會針對每一個類生成一個代理類,例如MainActivity我們會生成一個MainActivity$$ViewInjector。那麼如果多個類中宣告瞭註解,就對應了多個類,這裡就需要:
- 一個類物件,代表具體某個類的代理類生成的全部資訊,本例中為ProxyInfo
- 一個集合,存放上述類物件(到時候遍歷生成代理類),本例中Map<String, ProxyInfo>,key為類的全路徑。
收集資訊
首先呼叫mProxyMap.clear()
,因為process
可能會多次呼叫,避免生成重複的代理類,避免生成類的類名已存在異常。
然後,通過roundEnv.getElementsAnnotatedWith()
獲取被@BindView
註解的元素,這裡返回值,按照我們的預期應該是VariableElement
集合,因為我們用於成員變數上。
接下來for
迴圈我們的元素,首先檢查型別是否是VariableElement
(對元素列表進行額外判斷,校驗元素是否可用),然後獲取元素VariableElement
對應的類資訊TypeElement
,繼而生成ProxyInfo
物件。這裡先通過一個mProxyMap
進行檢查,key
為qualifiedName
即類的全路徑,如果沒有生成才會去生成一個新的ProxyInfo例項,ProxyInfo
與類是一一對應的。
接下來,會將與該類對應的且被@BindView宣告的VariableElement加入到ProxyInfo中去,key為我們宣告時填寫的id,即View的id。
這樣就完成了資訊的收集,收集完成資訊後,應該就可以去生成代理類了。
生成代理類
遍歷mProxyMap
,然後取得每一個ProxyInfo
,最後通過mFileUtils.createSourceFile()
來建立檔案物件,類名為proxyInfo.getProxyClassFullName()
,寫入的內容為proxyInfo.generateJavaCode()
(生成Java程式碼)。
生成Java程式碼
ProxyInfo.generateJavaCode()
方法通過收集得到的資訊,拼接完成的代理類物件。也可以使用開源庫,例如:javapoet
,來通過Java API的方式來生成程式碼。javapoet (com.squareup:javapoet)
是一個根據指定引數,生成java檔案的開源庫。
生成的程式碼實現了一個介面ViewInjector<T>
,該介面是為了統一所有的代理類物件的型別,到時候我們需要強轉代理類物件為該介面型別,呼叫其方法。介面是泛型,主要就是傳入實際類物件
,例如:MainActivity
,因為我們在生成代理類中的程式碼,實際上就是實際類.成員變數
的方式進行訪問,所以,使用編譯時註解的成員變數一般都不允許private
修飾符修飾(有的允許,但是需要提供getter
,setter
訪問方法)。
API模組的實現
有了代理類之後,我們一般還會提供API供使用者去訪問
API一般如何編寫呢?
- 根據傳入的
host
尋找我們生成的代理類:例如:MainActivity->MainActity$$ViewInjector
。 - 強轉為統一的介面,呼叫介面提供的方法。
這兩件事應該不復雜,第一件事是拼接代理類名,然後反射生成物件,第二件事強轉呼叫。拼接代理類的全路徑,然後通過newInstance
生成例項,然後強轉,呼叫代理類的inject()
方法。
ButterKnife工作流程解析
Butter Knife,專門為Android View設計的繫結註解,專業解決各種findViewById。
ButterKnife有哪些優勢?
- 強大的View繫結和Click事件處理功能,簡化程式碼,提升開發效率
- 方便的處理Adapter裡的ViewHolder繫結問題
- 執行時不會影響APP效率,使用配置方便
- 程式碼清晰,可讀性強
ButterKnife工作流程
- 開始它會掃描Java程式碼中所有的ButterKnife註解@Bind、@OnClick、@OnItemClicked等。
- 當它發現一個類中含有任何一個註解時, ButterKnifeProcessor會幫你生成一個Java類,名字<類名>$$ViewInjector.java,這個新生成的類實現了ViewBinder介面。
- 這個ViewBinder類中包含了所有對應的程式碼,比如@Bind註解對應findViewById(), @OnClick對應了view.setOnClickListener()等等。
- 最後當Activity啟動ButterKnife.bind(this)執行時,ButterKnife會去載入對應的ViewBinder類呼叫它們的bind()方法。
Java註解工作流程
- 註解是在編譯(Compile)時期進行處理的
- 註解處理器(Annotation Processor)讀取Java程式碼處理相應的註解,並且生成對應的程式碼
- 生成的Java程式碼被當做普通的Java類再次編譯
-
註解處理器不能修改存在Java輸入檔案,也不能對方法做修改或者新增
參考資料
相關文章
- 註解和反射學習筆記反射筆記
- Java註解與反射學習筆記Java反射筆記
- Spring學習筆記2(IOC註解方式&AOP)Spring筆記
- springMVC學習筆記(二)-----註解和非註解入門小程式SpringMVC筆記
- Kubernetes學習筆記(二):Pod、標籤、註解筆記
- Spring學習筆記三: 通過註解配置BeanSpring筆記Bean
- Consul 學習筆記-服務註冊筆記
- 小白的學習筆記——eureka註冊中心筆記
- SpringMvc筆記-註解SpringMVC筆記
- Go學習筆記-GMP詳解Go筆記
- numpy的學習筆記\pandas學習筆記筆記
- bootstrap-modal.js學習筆記(原始碼註釋)bootJS筆記原始碼
- Java學習_註解Java
- @Param註解學習
- Java註解學習Java
- IT學習筆記筆記
- 學習筆記筆記
- JVM學習筆記(3)---OutOfMemory詳解JVM筆記
- (Redis學習筆記):Redis解決方案Redis筆記
- Powershell學習筆記——瞭解Powershell薦筆記
- SAP Fiori Elements 公開課第三單元學習筆記 - OData 和註解深入講解筆記
- 【學習筆記】數學筆記
- 強化學習-學習筆記11 | 解決高估問題強化學習筆記
- 《JAVA學習指南》學習筆記Java筆記
- 十六章 CI框架學習筆記(三)註冊登入流程框架筆記
- mysql學習筆記-底層原理詳解MySql筆記
- Nginx變數詳解(學習筆記十九)Nginx變數筆記
- Elasticsearch學習筆記Elasticsearch筆記
- Scala學習筆記筆記
- MySql學習筆記MySql筆記
- jQuery 學習筆記jQuery筆記
- react學習筆記React筆記
- 學習筆記(4.3)筆記
- 學習筆記(4.4)筆記
- 學習筆記(3.29)筆記
- 學習筆記(4.1)筆記
- AOP學習筆記筆記
- AspectJ學習筆記筆記