天天看點

注解(二)- 自定義注解處理器

标簽: java annotation

上一篇部落格讨論了關于注解的基礎知識,以及運作時(Runtime)通過反射機制來處理注解,但既然是Runtime,那麼總會有效率上的損耗,如果我們能夠在編譯期(Compile time)就能處理注解,那自然更好,而很多架構其實都是在編譯期處理注解,比如大名鼎鼎的bufferknife,這個過程并不複雜,隻需要我們自定義注解處理器(Annotation Processor)就可以了。(Annotation Processor下文有些地方直接簡稱處理器,不要了解成cpu那個處理器)。

在Compile time注解就能起作用,這才是真正展現注解價值的地方,不過自定義Compile time的注解處理器也沒什麼神秘的。注解處理器是編譯器(javac)的一個工具,它用來在編譯時掃描和處理注解。我們可以自定義一個注解,并編寫和注冊對應的處理器。在寫法上它其實就是我們自定義一個類,該類 <code>extends javax.annotation.processing.AbstractProcessor</code>, <code>AbstractProcessor</code>是一個abstract的基類。它以我們寫好的java源碼或者編譯好的代碼做為輸入,然後就可以通過處理器代碼來實作我們所希望的輸出了,比如輸出一份新的java代碼,此時注解管理器就以遞歸的形式進行多趟處理,直到把代碼(包括你手寫的代碼,以及注解處理器生成的代碼)中所有的注解都被處理完畢。

我們已經寫好的代碼固然是不能修改了,但是這并不影響通過注解處理器來生成新的代碼。還以bufferknife為例,寫findViewById實在太無聊了,是以我們就使用了bufferknife的注解方式省略這個過程。

但是實際上呢,是bufferknife通過其注解處理器器來生成了相應的代碼,它生成的檔案是這樣的:

是以bufferknife就是通過這種方式來麻煩了自己,友善了我們。

注解處理器是運作在它自己的虛拟機jvm當中的,也就是說,javac啟動了一個完整的java虛拟機來運作注解處理器,這點非常重要,因為這說明你編寫的注解處理器代碼,和你寫的其他java代碼是沒什麼差別的。不管是你使用的API,還是設計時的思想,編碼習慣,甚至你想使用的其他第三方類庫,架構等,都是一樣的。

前面就說過,我們自定義的過程,就是<code>extends AbstractProcessor</code>,先來看看這個抽象處理器類。

這幾個主要方法,在代碼片段的注釋已經寫的很清楚了。

我們使用TestProcessor.java這個處理器的目的就是分析處理java代碼,而代碼是遵循一定的結構規範的,代碼檔案被讀取後,各個字元串會被分解成token進行處理,而javac的編譯器首先将java代碼分解為抽象文法樹(AST)。而這個結構,在處理器内部,其實是被表示成這樣的:

處理器在處理代碼時,其實就是對抽象文法樹進行周遊操作,分解出每一個的類,方法,屬性等,然後再将這些元素的内容進行處理。

而實際上,這些PackageElement,VariableElement等元素模型都是在一個專門的類包中<code>javax.lang.model</code>。<code>javax.lang.model</code>用來為 Java 程式設計語言建立模型的包的類和層次結構。 此包及其子包的成員适用于語言模組化、語言處理任務和 API(包括但并不僅限于注釋處理架構)。

我們現在通過繼承AbstractProcessor來實作一個小demo。

流程和功能如下:我們定義了一個注解<code>SQLString</code>,然後實作注解處理器 <code>DbProcessor</code>。該注解處理器功能很簡單,就是生成一個檔案,将實作了<code>SQLString</code>的屬性元素的相關内容寫入到這個檔案(比如所在類的名字,屬性名,所設定的注解的值)。

我們先自定義一個注解

然後再來定義注解處理器

結合着注釋,我們知道,這個處理器的功能就是将一些資訊輸出到 <code>/Users/yw/code/dbCustomProcFile</code> 這個檔案中。

我在代碼中使用了 <code>javax.annotation.processing.Messager</code>來輸出一些log資訊,因為這個過程是在編譯時輸出的,是以<code>System.out.println()</code>就沒用了,這個輸出資訊是給使用了該處理器的第三方程式員看的,不是給該處理器的作者看的。

比如demo當中的log代碼,在最後成功的打包成jar,在另一個項目中使用時(Android Studio環境下,Eclipse我愣是沒找到哪個視窗輸出編譯資訊),編譯時期輸出資訊如下:

處理器的代碼雖然寫完了,但是這還沒完呢,剩下還有非常重要的步驟,那就是添加注冊資訊。因為注解處理器是屬于javac的一個平台級的功能,是以我們的使用方式是将代碼打包成jar的形式,這樣就可以在其他第三方項目當中使用了。而在打包jar之前,則要在項目中添加注冊資訊。

先看一下這個目錄的結構:

注解(二)- 自定義注解處理器

(eclipse)注冊的步驟如下:

1,選中工程,滑鼠右鍵,New -&gt; Source Folder,建立 resources檔案夾,然後依次通過New -&gt; Folder 建立兩個檔案夾 : META-INF,services

2,在services檔案夾下,New -&gt; File, 建立一個檔案,javax.annotation.processing.Processor。在檔案中,輸入自定義的處理器的全名:

<code>com.yaoxiaowen.comp.proce.db.DbProcessor</code>

輸入之後記得鍵入回車一下。

其實這個手動注冊的過程,也是可以不用我們麻煩的。google開發了一個注解工具AutoService,我們可以直接在處理器代碼上使用。類似下面這樣:

這個注解工具自動生成META-INF/services/javax.annotation.processing.Processor檔案,檔案裡還包含了處理器的全名:

看到這裡,你也許會比較震驚,我們在注解處理器的代碼中也可以使用注解。

那麼此時請再看看本文開頭的那句話

注解處理器是運作在它自己的虛拟機jvm當中的,也就是說,javac啟動了一個完整的java虛拟機來運作注解處理器.....

做完這些,我們的項目就已經完成了,下面要做的就是打包成jar了。

1: 打包jar

前面說過,編譯期的注解處理器是平台級的功能,是要注冊給javac的, 是以需要打包成jar, 我們的項目打包的名字是

AnnoCustomProce.jar

關于具體的打包過程,參見gif圖(這是從鴻洋大神的部落格上學習到的)。

注解(二)- 自定義注解處理器

2: 建立新項目

eclipse下的java項目,建立立一個lib檔案夾,然後将AnnoCustomProce.jar手動拷貝到這個目錄下。

3: 引用包,并啟用annotation processor。

具體操作見gif圖。

注解(二)- 自定義注解處理器

要注意一下兩個gif圖中的各種選項和配置。

現在呢,已經大功告成了。下面就是使用了。

建立一個類,使用我們的注解。

當編譯完這個項目時(eclipse預設就是Build Automatically),我們就能在 <code>/Users/yw/code/</code>目錄下找到 dbCustomProcFile 檔案了,打開這個檔案,内容如下:

大功告成,我們成功的實作了一個能夠在編譯時期起作用的自定義注解處理器。

當然,這個demo沒有什麼實際作用,它的功能也非常簡單,但是了解了這個過程,我們在實際需求當中,就可以通過類似的方式來實作想要的功能了。

很多時候,我們都是希望注解處理器是來輸出java代碼的,既然是代碼,那麼總有格式的,這就不像簡單的檔案那樣進行輸出了,輸出java代碼,一般使用一個類庫:javapoet。而我們如果在注解處理器中引入了第三方的類庫,那麼将其打包成jar的過程,就和我們示範的有所不同。這點需要自行google。另外,如果想對java源碼進行遊刃有餘的處理,那麼需要對于<code>javax.lang.model</code> 包下的各種Elements,工具之類的比較熟悉。具體的api,需要參考oracle的文檔.

我在學習自定義注解處理器的過程中,參考了網上的很多部落格,敲代碼進行實測,但是實際上依舊碰到了很多的問題,也折騰了好久,在這裡我将自己所碰到的問題,都羅列出來。(雖然有些是可笑的低級錯誤),希望對大家有所幫助。

resource/META-INF/services com.yaoxiaowen.annotation.createjson.BeanProcessor 檔案中,寫的是 處理器的類<code>DbProcessor</code>,而不是你的注解類:<code>SQLString</code>。

resource 這個檔案夾 是 New Source Folder,後面兩個檔案,才是 New Folder, 關于兩者之間的差別: 後者就是一個普通的檔案夾而已,但是前者,是屬于項目的一部分,eclipse會編譯這個檔案夾。是以有些文章說建立的 是res(而不是resource)檔案夾,這個其實無所謂,隻要它是 Souce Folder.

在導入包的時候,注意不要導入錯誤的包(比如<code>import java.awt.Window.Type</code>)。因為往往同一個類名,它在不同的包裡都有實作。像常用的<code>List</code>,經常導錯包。<code>java.awt.List</code>和<code>java.util.List</code>。<code>List</code>比較常用,我們很容易找到錯誤,但是 <code>Type</code>之類的不常用,是以不是那麼容易發現。

在處理器的生成檔案的代碼中,有這樣一句:

那如果我将代碼改成這樣:

那麼請問此時,這個dbCustomProcFile檔案到底在那裡呢?

在我的電腦上,該檔案路徑分别如下

<code>D:\software\eclipse\dbCustomProcFile</code>(window)或<code>/Users/yw//Downloads/Eclipse.app/Contents/MacOS/dbCustomProcFile</code>(mac)。

一般我們在工程中使用<code>./</code>我們都認為是工程目前的目錄,但是在注解處理器中,這個路徑實際上是eclipse 安裝路徑下 javac的路徑。的确應該如此,因為注解處理器畢竟是javac的一個工具。

某一次測試時,當我向新工程導入生成的jar包時,剛剛導入eclipse就報錯。

注解(二)- 自定義注解處理器

後來終于發現了問題所在。

注解(二)- 自定義注解處理器

在編碼過程中,函數的行參原來是<code>processingEnv</code>,後來我嫌長,就修改為了<code>env</code>,但是下面一句<code>super.init()</code>卻忘記修改了。是以導緻找不到這個參數,而問題是在window和mac下,這句代碼IDE都沒有任何提示,我又在下面故意寫了一句錯代碼<code>String = 2</code>,此時IDE提示錯誤。 那為什麼第一句話IDE就是沒報錯呢。測試了普通java檔案的同種類型的錯誤,IDE就會報錯,是以這真的讓我不解。

以上就是本篇文章的全部内容,關于注解處理器深處的很多東西其實也沒搞懂。也歡迎大家留言指點交流。

作者:

www.yaoxiaowen.com

github:

https://github.com/yaowen369

歡迎對于本人的部落格内容批評指點,如果問題,可評論或郵件([email protected])聯系