天天看點

Java注解處理器使用詳解

在這篇文章中,我将闡述怎樣寫一個注解處理器(annotation processor)。在這篇教程中,首先,我将向您解釋什麼是注解器,你可以利用這個強大的工具做什麼以及不能做什麼;然後,我将一步一步實作一個簡單的注解器。

一些基本概念

在開始之前,我們首先申明一個非常重要的問題:我們并不讨論那些在運作時(runtime)通過反射機制運作處理的注解,而是讨論在編譯時(compile time)處理的注解。

注解處理器是一個在javac中的,用來編譯時掃描和處理的注解的工具。你可以為特定的注解,注冊你自己的注解處理器。到這裡,我假設你已經知道什麼是注解,并且知道怎麼申明的一個注解類型。如果你不熟悉注解,你可以在這官方文檔中得到更多資訊。注解處理器在java 5開始就有了,但是從java 6(2006年12月釋出)開始才有可用的api。過了一些時間,java世界才意識到注解處理器的強大作用,是以它到最近幾年才流行起來。

一個注解的注解處理器,以java代碼(或者編譯過的位元組碼)作為輸入,生成檔案(通常是<code>.java</code>檔案)作為輸出。這具體的含義什麼呢?你可以生成java代碼!這些生成的java代碼是在生成的.java檔案中,是以你不能修改已經存在的java類,例如向已有的類中添加方法。這些生成的java檔案,會同其他普通的手動編寫的java源代碼一樣被javac編譯。

虛處理器<code>abstractprocessor</code>

我們首先看一下處理器的api。每一個處理器都是繼承于<code>abstractprocessor</code>,如下所示:

<code>init(processingenvironment env)</code>: 每一個注解處理器類都必須有一個空的構造函數。然而,這裡有一個特殊的<code>init()</code>方法,它會被注解處理工具調用,并輸入<code>processingenviroment</code>參數。<code>processingenviroment</code>提供很多有用的工具類<code>elements</code>, <code>types</code>和<code>filer</code>。後面我們将看到詳細的内容。

<code>process(set&lt;? extends typeelement&gt; annotations, roundenvironment env)</code>: 這相當于每個處理器的主函數<code>main()</code>。你在這裡寫你的掃描、評估和處理注解的代碼,以及生成java檔案。輸入參數<code>roundenviroment</code>,可以讓你查詢出包含特定注解的被注解元素。後面我們将看到詳細的内容。

<code>getsupportedannotationtypes()</code>: 這裡你必須指定,這個注解處理器是注冊給哪個注解的。注意,它的傳回值是一個字元串的集合,包含本處理器想要處理的注解類型的合法全稱。換句話說,你在這裡定義你的注解處理器注冊到哪些注解上。

<code>getsupportedsourceversion()</code>: 用來指定你使用的java版本。通常這裡傳回<code>sourceversion.latestsupported()</code>。然而,如果你有足夠的理由隻支援java 6的話,你也可以傳回<code>sourceversion.release_6</code>。我推薦你使用前者。

在java 7中,你也可以使用注解來代替<code>getsupportedannotationtypes()</code>和<code>getsupportedsourceversion()</code>,像這樣:

因為相容的原因,特别是針對android平台,我建議使用重載<code>getsupportedannotationtypes()</code>和<code>getsupportedsourceversion()</code>方法代替<code>@supportedannotationtypes</code>和<code>@supportedsourceversion</code>。

接下來的你必須知道的事情是,注解處理器是運作它自己的虛拟機jvm中。是的,你沒有看錯,javac啟動一個完整java虛拟機來運作注解處理器。這對你意味着什麼?你可以使用任何你在其他java應用中使用的的東西。使用guava。如果你願意,你可以使用依賴注入工具,例如dagger或者其他你想要的類庫。但是不要忘記,即使是一個很小的處理,你也要像其他java應用一樣,注意算法效率,以及設計模式。如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載下傳。

注冊你的處理器

你可能會問,我怎樣處理器<code>myprocessor</code>到javac中。你必須提供一個<code>.jar</code>檔案。就像其他.jar檔案一樣,你打包你的注解處理器到此檔案中。并且,在你的jar中,你需要打包一個特定的檔案<code>javax.annotation.processing.processor</code>到<code>meta-inf/services</code>路徑下。是以,你的.jar檔案看起來就像下面這樣:

Java注解處理器使用詳解

打包進myprocessor.jar中的<code>javax.annotation.processing.processor</code>的内容是,注解處理器的合法的全名清單,每一個元素換行分割:

把<code>myprocessor.jar</code>放到你的builpath中,javac會自動檢查和讀取<code>javax.annotation.processing.processor</code>中的内容,并且注冊<code>myprocessor</code>作為注解處理器。

例子:工廠模式

是時候來說一個實際的例子了。我們将使用maven工具來作為我們的編譯系統和依賴管理工具。如果你不熟悉maven,不用擔心,因為maven不是必須的。本例子的完成代碼在github上。

開始之前,我必須說,要為這個教程找到一個需要用注解處理器解決的簡單問題,實在并不容易。這裡我們将實作一個非常簡單的工廠模式(不是抽象工廠模式)。這将對注解處理器的api做一個非常簡明的介紹。是以,這個問題的程式并不是那麼有用,也不是一個真實世界的例子。是以在此申明,你将學習關于注解處理過程的相關内容,而不是設計模式。

我們将要解決的問題是:我們将實作一個披薩店,這個披薩店給消費者提供兩種披薩(“margherita”和“calzone”)以及提拉米蘇甜點(tiramisu)。

看一下如下的代碼,不需要做任何更多的解釋:

為了在我們的披薩店<code>pizzsstore</code>下訂單,消費者需要輸入餐(meal)的名字。

正如你所見,在<code>order()</code>方法中,我們有很多的<code>if</code>語句,并且如果我們每添加一種新的披薩,我們都要添加一條新的<code>if</code>語句。但是等一下,使用注解處理和工廠模式,我們可以讓注解處理器來幫我們自動生成這些<code>if</code>語句。如此以來,我們期望的是如下的代碼:

<code>mealfactory</code>應該是如下的樣子:

<code>@factory</code>注解

你能猜到麼:我們想用注解處理器自動生成<code>mealfactory</code>。更一般的說,我們将想要提供一個注解和一個處理器來生成工廠類。

我們先來看一下<code>@factory</code>注解:

想法是這樣的:我們将使用同樣的<code>type()</code>注解那些屬于同一個工廠的類,并且用注解的<code>id()</code>做一個映射,例如從<code>"calzone"</code>映射到<code>"clzonepizza"</code>類。我們應用<code>@factory</code>注解到我們的類中,如下:

你可能會問你自己,我們是否可以隻把<code>@factory</code>注解應用到我們的<code>meal</code>接口上?答案是,注解是不能繼承的。一個類<code>class x</code>被注解,并不意味着它的子類<code>class y extends x</code>會自動被注解。在我們開始寫處理器的代碼之前,我們先規定如下一些規則:

隻有類可以被<code>@factory</code>注解,因為接口或者抽象類并不能用<code>new</code>操作執行個體化;

被<code>@factory</code>注解的類,必須至少提供一個公開的預設構造器(即沒有參數的構造函數)。否者我們沒法執行個體化一個對象。

被<code>@factory</code>注解的類必須直接或者間接的繼承于<code>type()</code>指定的類型;

具有相同的<code>type</code>的注解類,将被聚合在一起生成一個工廠類。這個生成的類使用factory字尾,例如<code>type = meal.class</code>,将生成<code>mealfactory</code>工廠類;

<code>id</code>隻能是string類型,并且在同一個<code>type</code>組中必須唯一。

處理器

我将通過添加代碼和一段解釋的方法,一步一步的引導你來建構我們的處理器。省略号(<code>...</code>)表示省略那些已經讨論過的或者将在後面的步驟中讨論的代碼,目的是為了讓我們的代碼有更好的可讀性。正如我們前面說的,我們完整的代碼可以在github上找到。好了,讓我們來看一下我們的處理器類<code>factoryprocessor</code>的骨架:

你看到在代碼的第一行是<code>@autoservice(processor.class)</code>,這是什麼?這是一個其他注解處理器中引入的注解。<code>autoservice</code>注解處理器是google開發的,用來生成<code>meta-inf/services/javax.annotation.processing.processor</code>檔案的。是的,你沒有看錯,我們可以在注解處理器中使用注解。非常友善,難道不是麼?在<code>getsupportedannotationtypes()</code>中,我們指定本處理器将處理<code>@factory</code>注解。

elements和typemirrors

在<code>init()</code>中我們獲得如下引用:

elements:一個用來處理<code>element</code>的工具類(後面将做詳細說明);

types:一個用來處理<code>typemirror</code>的工具類(後面将做詳細說明);

filer:正如這個名字所示,使用filer你可以建立檔案。

在注解處理過程中,我們掃描所有的java源檔案。源代碼的每一個部分都是一個特定類型的<code>element</code>。換句話說:<code>element</code>代表程式的元素,例如包、類或者方法。每個<code>element</code>代表一個靜态的、語言級别的構件。在下面的例子中,我們通過注釋來說明這個:

你必須換個角度來看源代碼,它隻是結構化的文本,他不是可運作的。你可以想象它就像你将要去解析的xml檔案一樣(或者是編譯器中抽象的文法樹)。就像xml解釋器一樣,有一些類似dom的元素。你可以從一個元素導航到它的父或者子元素上。

舉例來說,假如你有一個代表<code>public class foo</code>類的<code>typeelement</code>元素,你可以周遊它的孩子,如下:

正如你所見,element代表的是源代碼。<code>typeelement</code>代表的是源代碼中的類型元素,例如類。然而,<code>typeelement</code>并不包含類本身的資訊。你可以從<code>typeelement</code>中擷取類的名字,但是你擷取不到類的資訊,例如它的父類。這種資訊需要通過<code>typemirror</code>擷取。你可以通過調用<code>elements.astype()</code>擷取元素的<code>typemirror</code>。

搜尋@factory注解

我們來一步一步實作<code>process()</code>方法。首先,我們從搜尋被注解了<code>@factory</code>的類開始:

這裡并沒有什麼高深的技術。<code>roundenv.getelementsannotatedwith(factory.class))</code>傳回所有被注解了<code>@factory</code>的元素的清單。你可能已經注意到,我們并沒有說“所有被注解了<code>@factory</code>的類的清單”,因為它真的是傳回<code>element</code>的清單。請記住:<code>element</code>可以是類、方法、變量等。是以,接下來,我們必須檢查這些element是否是一個類:

為什麼要這麼做?我們要確定隻有class元素被我們的處理器處理。前面我們已經學習到類是用<code>typeelement</code>表示。我們為什麼不這樣判斷呢<code>if (! (annotatedelement instanceof typeelement) )</code>?這是錯誤的,因為接口(interface)類型也是typeelement。是以在注解處理器中,我們要避免使用<code>instanceof</code>,而是配合<code>typemirror</code>使用<code>ementkind</code>或者<code>typekind</code>。

錯誤處理

在<code>init()</code>中,我們也獲得了一個<code>messager</code>對象的引用。<code>messager</code>提供給注解處理器一個報告錯誤、警告以及提示資訊的途徑。它不是注解處理器開發者的日志工具,而是用來寫一些資訊給使用此注解器的第三方開發者的。在官方文檔中描述了消息的不同級别。非常重要的是<code>kind.error</code>,因為這種類型的資訊用來表示我們的注解處理器處理失敗了。很有可能是第三方開發者錯誤的使用了<code>@factory</code>注解(例如,給接口使用了@factory注解)。這個概念和傳統的java應用有點不一樣,在傳統java應用中我們可能就抛出一個異常<code>exception</code>。如果你在<code>process()</code>中抛出一個異常,那麼運作注解處理器的jvm将會崩潰(就像其他java應用一樣),使用我們注解處理器factoryprocessor第三方開發者将會從javac中得到非常難懂的出錯資訊,因為它包含factoryprocessor的堆棧跟蹤(stacktace)資訊。是以,注解處理器就有一個<code>messager</code>類,它能夠列印非常優美的錯誤資訊。除此之外,你還可以連接配接到出錯的元素。在像intellij這種現代的ide(內建開發環境)中,第三方開發者可以直接點選錯誤資訊,ide将會直接跳轉到第三方開發者項目的出錯的源檔案的相應的行。

我們重新回到<code>process()</code>方法的實作。如果遇到一個非類類型被注解<code>@factory</code>,我們發出一個出錯資訊:

讓messager顯示相關出錯資訊,更重要的是注解處理器程式必須完成運作而不崩潰,這就是為什麼在調用<code>error()</code>後直接<code>return</code>。如果我們不直接傳回,<code>process()</code>将繼續運作,因為<code>messager.printmessage( diagnostic.kind.error)</code>不會停止此程序。是以,如果我們在列印錯誤資訊以後不傳回的話,我們很可能就會運作到一個nullpointerexception等。就像我們前面說的,如果我們繼續運作<code>process()</code>,問題是如果在<code>process()</code>中抛出一個未處理的異常,javac将會列印出内部的nullpointerexception,而不是<code>messager</code>中的錯誤資訊。

資料模型

在繼續檢查被注解@fractory的類是否滿足我們上面說的5條規則之前,我們将介紹一個讓我們更友善繼續處理的資料結構。有時候,一個問題或者解釋器看起來如此簡單,以至于程式員傾向于用一個面向過程方式來寫整個處理器。但是你知道嗎?一個注解處理器任然是一個java程式,是以我們需要使用面向對象程式設計、接口、設計模式,以及任何你将在其他普通java程式中使用的技巧。

我們的<code>factoryprocessor</code>非常簡單,但是我們仍然想要把一些資訊存為對象。在<code>factoryannotatedclass</code>中,我們儲存被注解類的資料,比如合法的類的名字,以及@factory注解本身的一些資訊。是以,我們儲存<code>typeelement</code>和處理過的@factory注解:

代碼很多,但是最重要的部分是在構造函數中。其中你能找到如下的代碼:

這裡我們擷取@factory注解,并且檢查id是否為空?如果為空,我們将抛出illegalargumentexception異常。你可能感到疑惑的是,前面我們說了不要抛出異常,而是使用<code>messager</code>。這裡仍然不沖突。我們抛出内部的異常,你在将在後面看到會在<code>process()</code>中捕獲這個異常。我這樣做出于一下兩個原因:

我想示意我們應該像普通的java程式一樣編碼。抛出和捕獲異常是非常好的java程式設計實踐;

如果我們想要在<code>factoryannotatedclass</code>中列印資訊,我需要也傳入<code>messager</code>對象,并且我們在錯誤處理一節中已經提到,為了列印<code>messager</code>資訊,我們必須成功停止處理器運作。如果我們使用<code>messager</code>列印了錯誤資訊,我們怎樣告知<code>process()</code>出現了錯誤呢?最容易,并且我認為最直覺的方式就是抛出一個異常,然後讓<code>process()</code>捕獲之。

接下來,我們将擷取<code>@fractory</code>注解中的<code>type</code>成員。我們比較關心的是合法的全名:

這裡有一點小麻煩,因為這裡的類型是一個<code>java.lang.class</code>。這就意味着,他是一個真正的class對象。因為注解處理是在編譯java源代碼之前。如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載下傳。我們需要考慮如下兩種情況:

這個類已經被編譯:這種情況是:如果第三方<code>.jar</code>包含已編譯的被@factory注解<code>.class</code>檔案。在這種情況下,我們可以想<code>try</code>中那塊代碼中所示直接擷取<code>class</code>。

這個還沒有被編譯:這種情況是我們嘗試編譯被@fractory注解的源代碼。這種情況下,直接擷取class會抛出<code>mirroredtypeexception</code>異常。幸運的是,mirroredtypeexception包含一個<code>typemirror</code>,它表示我們未編譯類。因為我們已經知道它必定是一個類類型(我們已經在前面檢查過),我們可以直接強制轉換為<code>declaredtype</code>,然後讀取<code>typeelement</code>來擷取合法的名字。

好了,我們現在還需要一個資料結構<code>factorygroupedclasses</code>,它将簡單的組合所有的<code>factoryannotatedclasses</code>到一起。

正如你所見,這是一個基本的<code>map&lt;string, factoryannotatedclass&gt;</code>,這個映射表用來映射@factory.id()到factoryannotatedclass。我們選擇<code>map</code>這個資料類型,是因為我們要確定每個id是唯一的,我們可以很容易通過map查找實作。<code>generatecode()</code>方法将被用來生成工廠類代碼(将在後面讨論)。

比對标準

我們繼續實作<code>process()</code>方法。接下來我們想要檢查被注解的類必須有隻要一個公開的構造函數,不是抽象類,繼承于特定的類型,以及是一個公開類:

我們這裡添加了<code>isvalidclass()</code>方法,來檢查是否我們所有的規則都被滿足了:

必須是公開類:<code>classelement.getmodifiers().contains(modifier.public)</code>

必須是非抽象類:<code>classelement.getmodifiers().contains(modifier.abstract)</code>

必須是<code>@factoy.type()</code>指定的類型的子類或者接口的實作:首先我們使用<code>elementutils.gettypeelement(item.getqualifiedfactorygroupname())</code>建立一個傳入的<code>class</code>(<code>@factoy.type()</code>)的元素。是的,你可以僅僅通過已知的合法類名來直接建立<code>typeelement</code>(使用typemirror)。接下來我們檢查它是一個接口還是一個類:<code>superclasselement.getkind() == elementkind.interface</code>。是以我們這裡有兩種情況:如果是接口,就判斷<code>classelement.getinterfaces().contains(superclasselement.astype())</code>;如果是類,我們就必須使用<code>currentclass.getsuperclass()</code>掃描繼承層級。注意,整個檢查也可以使用<code>typeutils.issubtype()</code>來實作。

類必須有一個公開的預設構造函數:我們周遊所有的閉元素<code>classelement.getenclosedelements()</code>,然後檢查<code>elementkind.constructor</code>、<code>modifier.public</code>以及<code>constructorelement.getparameters().size() == 0</code>。

如果所有這些條件都滿足,<code>isvalidclass()</code>傳回<code>true</code>,否者就列印錯誤資訊,并且傳回<code>false</code>。

組合被注解的類

一旦我們檢查<code>isvalidclass()</code>成功,我們将添加<code>factoryannotatedclass</code>到對應的<code>factorygroupedclasses</code>中,如下:

代碼生成

我們已經手機了所有的被<code>@factory</code>注解的類儲存到為<code>factoryannotatedclass</code>,并且組合到了<code>factorygroupedclasses</code>。現在我們将為每個工廠生成java檔案了:

寫java檔案,和寫其他普通檔案沒有什麼兩樣。使用<code>filer</code>提供的<code>writer</code>對象,我們可以連接配接字元串來寫我們生成的java代碼。幸運的是,square公司(因為提供了許多非常優秀的開源項目二非常有名)給我們提供了<code>javawriter</code>,這是一個進階的生成java代碼的庫:

注意:因為javawriter非常非常的流行,是以很多處理器、庫、工具都依賴于javawriter。如果你使用依賴管理工具,例如maven或者gradle,假如一個庫依賴的javawriter的版本比其他的庫新,這将會導緻一些問題。是以我建議你直接拷貝重新打包javawiter到你的注解處理器代碼中(實際它隻是一個java檔案)。

更新:javawrite現在已經被<code>javapoet</code>取代了。

處理循環

注解處理過程可能會多于一次。官方javadoc定義處理過程如下:

注解處理過程是一個有序的循環過程。在每次循環中,一個處理器可能被要求去處理那些在上一次循環中産生的源檔案和類檔案中的注解。第一次循環的輸入是運作此工具的初始輸入。這些初始輸入,可以看成是虛拟的第0此的循環的輸出。

一個簡單的定義:一個處理循環是調用一個注解處理器的<code>process()</code>方法。對應到我們的工廠模式的例子中:<code>factoryprocessor</code>被初始化一次(不是每次循環都會建立處理器對象),然而,如果生成了新的源檔案<code>process()</code>能夠被調用多次。聽起來有點奇怪不是麼?原因是這樣的,這些生成的檔案中也可能包含@factory注解,它們還将會被<code>factoryprocessor</code>處理。

例如我們的<code>pizzastore</code>的例子中将會經過3次循環處理:

round

input

output

1

calzonepizza.javatiramisu.javamargheritapizza.javameal.javapizzastore.java

mealfactory.java

2

— none —

3

我解釋處理循環還有另外一個原因。如果你看一下我們的<code>factoryprocessor</code>代碼你就能注意到,我們收集資料和儲存它們在一個私有的域中<code>map&lt;string, factorygroupedclasses&gt; factoryclasses</code>。在第一輪中,我們檢測到了magheritapizza, calzonepizza和tiramisu,然後生成了mealfactory.java。在第二輪中把mealfactory作為輸入。因為在mealfactory中沒有檢測到@factory注解,我們預期并沒有錯誤,然而我們得到如下的資訊:

這個問題是因為我們沒有清除<code>factoryclasses</code>,這意味着,在第二輪的<code>process()</code>中,任然儲存着第一輪的資料,并且會嘗試生成在第一輪中已經生成的檔案,進而導緻這個錯誤的出現。在我們的這個場景中,我們知道隻有在第一輪中檢查<code>@factory</code>注解的類,是以我們可以簡單的修複這個問題,如下:

我知道這有其他的方法來處理這個問題,例如我們也可以設定一個布爾值标簽等。關鍵的點是:我們要記住注解處理過程是需要經過多輪處理的,并且你不能重載或者重新建立已經生成的源代碼。

分離處理器和注解

如果你已經看了我們的代碼庫,你将發現我們組織我們的代碼到兩個maven子產品中了。我們這麼做是因為,我們想讓我們的工廠模式的例子的使用者,在他們的工程中隻編譯注解,而包含處理器子產品隻是為了編譯。有點暈?我們舉個例子,如果我們隻有一個包。如果另一個開發者想要把我們的工廠模式處理器用于他的項目中,他就必須包含<code>@factory</code>注解和整個<code>factoryprocessor</code>的代碼(包括factoryannotatedclass和factorygroupedclasses)到他們項目中。我非常确定的是,他并不需要在他已經編譯好的項目中包含處理器相關的代碼。如果你是一個android的開發者,你肯定聽說過65k個方法的限制(即在一個.dex檔案中,隻能尋址65000個方法)。如果你在factoryprocessor中使用guava,并且把注解和處理器打包在一個包中,這樣的話,android apk安裝包中不隻是包含factoryprocessor的代碼,而也包含了整個guava的代碼。guava有大約20000個方法。是以分開注解和處理器是非常有意義的。

生成的類的執行個體化

你已經看到了,在這個<code>pizzastore</code>的例子中,生成了<code>mealfactory</code>類,它和其他手寫的java類沒有任何差別。進而,你需要就想其他java對象,手動執行個體化它:

如果你是一個android的開發者,你應該也非常熟悉一個叫做<code>butterknife</code>的注解處理器。在butterknife中,你使用<code>@injectview</code>注解android的view。butterknifeprocessor生成一個<code>myactivity$$viewinjector</code>,但是在butterknife你不需要手動調用<code>new myactivity$$viewinjector()</code>執行個體化一個butterknife注入的對象,而是使用<code>butterknife.inject(activity)</code>。butterknife内部使用反射機制來執行個體化<code>myactivity$$viewinjector()</code>對象:

但是反射機制不是很慢麼,我們使用注解處理來生成本地代碼,會不會導緻很多的反射性能的問題?的确,反射機制的性能确實是一個問題。然而,它不需要手動去建立對象,确實提高了開發者的開發速度。butterknife中有一個哈希表hashmap來緩存執行個體化過的對象。是以<code>myactivity$$viewinjector</code>隻是使用反射機制執行個體化一次,第二次需要<code>myactivity$$viewinjector</code>的時候,就直接沖哈希表中獲得。

<code>fragmentargs</code>非常類似于butterknife。它使用反射機制來建立對象,而不需要開發者手動來做這些。fragmentargs在處理注解的時候生成一個特别的查找表類,它其實就是一種哈希表,是以整個fragmentargs庫隻是在第一次使用的時候,執行一次反射調用,一旦整個<code>class.forname()</code>的fragemnt的參數對象被建立,後面的都是本地代碼運作了。

作為一個注解注解處理器的開發者,這些都由你來決定,為其他的注解器使用者,在反射和可用性上找到一個好的平衡。

總結

到此,我希望你對注解處理過程有一個非常深刻的了解。我必須再次說明一下:注解處理器是一個非常強大的工具,減少了很多無聊的代碼的編寫。我也想提醒的是,注解處理器可以做到比我上面提到的工廠模式的例子複雜很多的事情。例如,泛型的類型擦除,因為注解處理器是發生在類型擦除(type erasure)之前的(譯者注:類型擦除可以參考這裡)。就像你所看到的,你在寫注解處理的時候,有兩個普遍的問題你需要處理:第一問題, 如果你想在其他類中使用elementutils, typeutils和messager,你就必須把他們作為參數傳進去。如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載下傳。在我為android開發的注解器<code>annotatedadapter</code>中,我嘗試使用dagger(一個依賴注入庫)來解決這個問題。在這個簡單的進行中使用它聽起來有點過頭了,但是它确實很好用;第二個問題,你必須做查詢<code>elements</code>的操作。就想我之前提到的,處理element就解析xml或者html一樣。對于html你可以是用jquery,如果在注解處理器中,有類似于jquery的庫那那絕對是酷斃了。如果你知道有類似的庫,請在下面的評論告訴我。

請注意的是,在factoryprocessor代碼中有一些缺陷和陷阱。這些“錯誤”是我故意放進去的,是為了示範一些在開發過程中的常見錯誤(例如“attempt to recreate a file”)。如果你想基于factoryprocessor寫你自己注解處理器,請不要直接拷貝粘貼這些陷阱過去,你應該從最開始就避免它們。