2012-11-15 54 views
12

我有兩個類與方法,我想將兩個類的方法合併到一個類。AnnotationProcessor使用多個源文件創建一個文件

@Service("ITestService") 
public interface ITest1 
{ 
    @Export 
    void method1(); 
} 

@Service("ITestService") 
public interface ITest2 
{ 
    @Export 
    void method2(); 
} 

的結果應該是:

public interface ITestService extends Remote 
{ 
    void method1(); 
    void method2(); 
} 

我AnnotationProcessor的第一次運行產生正確的輸出(因爲RoundEnvironment同時包含類)。

但是,如果我編輯類之一(例如添加新的方法),RoundEnviroment僅包含所編輯的類,因此結果被follwing(添加newMethod()接口ITest1)

public interface ITestService extends Remote 
{ 
    void method1(); 
    void newMethod(); 
} 

現在method2丟失。我不知道如何解決我的問題。有沒有辦法(Enviroment),來訪問項目中的所有課程?還是有另一種方法來解決這個問題?

生成這個類的代碼很長,所以這裏簡要說明我如何生成這個類。我通過與env.getElementsAnnotatedWith(Service.class)元素迭代並提取方法,並將其寫入新文件:

FileObject file = null; 
file = filer.createSourceFile("com/test/" + serviceName); 
file.openWriter().append(serviceContent).close(); 
+0

你在Eclipse中運行這個批註處理器嗎? –

+2

@ johncarl的觀點很重要,必須是真實的。標準的Java編譯器不允許增量編譯。 RoundEnvironment沒有辦法只能包含一個文件。 Eclipse編譯器是增量式的,只編譯已更改的文件。看起來這個邏輯對你來說不起作用,你需要通過Eclipse向一個給定的文件重新編譯。可能有些事情我們可以嘗試,但首先,爲了避免浪費精力,我們應該確定這隻會影響Eclipse編譯。 – Pace

+1

@Pace:是否指定了某處javac從不使用增量編譯?我認爲ant和maven也有增量編譯的模式,所以我猜他們也不能正確使用這樣的註釋處理器。 –

回答

7

- 選項1 - 命令行手動編譯---

我試圖做什麼你想要從處理器訪問所有類,並且正如人們所評論的那樣,javac始終在編譯所有類,並且從RoundEnvironment我可以訪問所有正在編譯的類,每次(即使沒有文件更改時),只有一個細節:只要所有類都顯示在要編譯的類的列表中。

我已經做了一些測試有兩個接口,其中一個(A)取決於(B)等(延伸),我有以下情況:

  1. 如果我要求編譯器編譯明確只有具有依賴關係(A)的接口,將java文件的完整路徑傳遞到命令行,並將輸出文件夾添加到類路徑,只有傳入命令行的接口才會被處理。
  2. 如果我只顯式編譯(A)並且不將輸出文件夾添加到類路徑中,編譯器仍然只處理接口(A)。但它也給了我警告:Implicitly compiled files were not subject to annotation processing.
  3. 如果我使用*或將兩個類傳遞給編譯器到命令行,那麼我會得到預期的結果,兩個接口都會得到處理。

如果您將編譯器設置爲冗長,您將收到一條顯示消息,顯示您將在每一輪中處理哪些類。這是我得到了什麼,當我明確地傳遞接口(A):

Round 1: 
input files: {com.bearprogrammer.test.TestInterface} 
annotations: [com.bearprogrammer.annotation.Service] 
last round: false 

這是我有什麼,當我加入這兩個類:

Round 1: 
input files: {com.bearprogrammer.test.AnotherInterface, com.bearprogrammer.test.TestInterface} 
annotations: [com.bearprogrammer.annotation.Service] 
last round: false 

在這兩種情況下,我看到了編譯器解析這兩個類,但以不同的順序。對於第一種情況(僅添加一個接口):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]] 
[parsing completed 15ms] 
[search path for source files: src\main\java] 
[search path for class files: ...] 
[loading ZipFileIndexFileObject[lib\processor.jar(com/bearprogrammer/annotation/Service.class)]] 
[loading RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 

對於第二種情況(添加所有接口):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 
... 
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]] 
[search path for source files: src\main\java] 
[search path for class files: ...] 
... 

重要這裏的細節是,編譯器加載的依賴作爲隱式在第一種情況下編譯的對象。在第二種情況下,它將把它作爲待編譯對象的一部分加載(你可以看到這一點,因爲它在提供的類被解析後開始搜索文件的其他路徑)。看起來,隱式對象不包含在註釋處理列表中。請參考Compilation Overview。其中沒有明確說明要處理哪些文件。

在這種情況下,解決方案將始終將所有類添加到編譯器的命令中。

---選項2 - 從Eclipse的編譯---

如果從Eclipse的編譯,增量構建會讓你的處理器失敗(沒有測試過)。但我認爲你可以繞過那個要求一個乾淨的構建(Project> Clean ...,也沒有測試過)或者編寫一個總是清理classes目錄並設置一個Ant Builder from Eclipse的Ant構建。

---選項3 - 使用構建工具---

如果您使用的是像螞蟻,Maven的或搖籃一些其他的構建工具,最好的解決辦法是在比單獨的步驟中源代你的彙編。你還需要讓你的處理器在一個單獨的前一步中編譯(或者如果使用Maven/Gradle構建多項目,則需要單獨的子項目)。

  1. 對於處理步驟,你總是可以做一個全面清理「彙編」,而不實際編譯代碼(使用選項-proc:only從javac的只處理的文件)
  2. 附:這是因爲是最好的方案生成的源代碼就位,如果您使用的是Gradle,那麼如果它們沒有改變,就足夠聰明,不會重新編譯生成的源文件。 Ant和Maven只會重新編譯所需的文件(生成的文件和它們的依賴文件)。

對於第三個選項,您還可以設置一個Ant構建腳本,以從Eclipse中生成這些文件作爲在Java構建器之前運行的構建器。在一些特殊的文件夾中生成源文件並將其添加到Eclipse中的類路徑/構建路徑中。

+0

不錯的答案,如果你可以找到一些官方的鏈接,它說javac沒有增量編譯你已經贏得了賞金。 –

+0

我不認爲規範要求編譯器是增量的或不是。這似乎是編譯器實現者的決定。 在Filer javadoc(http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Filer.html)中,甚至有關於增量或非增量的引用,並沒有將其明確化,暗示它確實取決於實施。 「這些信息可能會在增量環境中使用,以確定是否需要重新運行處理器或刪除生成的文件。非增量環境可能會忽略原始元素信息。」 – visola

+0

@JörnHorstmann在閱讀了很多關於它之後,我的結論是Java是關於語言和JVM的。編譯器沒有規範。該規範僅解釋字節碼的格式。如果你想手動生成字節碼並使用筆記本和筆處理註釋,那很好!只要您尊重用於註釋處理的API並在最後生成正確的字節碼格式。所以實際上這個實施需要增量或不增量。一個簡單的例子是Eclipse編譯器,它也生成標準字節碼,但是是增量式的。 – visola

2

NetBeans @Messages註釋會爲同一個包中的所有類生成單個Bundle.java文件。它的漸進式編譯感謝與正常工作到下面的技巧在annotation processor

Set<Element> toProcess = new HashSet<Element>(); 
for (Element e : roundEnv.getElementsAnnotatedWith(Messages.class)) { 
    PackageElement pkg = findPkg(e); 
    for (Element elem : pkg.getEnclosingElements()) { 
    if (elem.getAnnotation(Message.class) != null) { 
     toProcess.add(elem); 
    } 
    } 
} 
// now process all package elements in toProcess 
// rather just those provided by the roundEnv 

PackageElement findPkg(Element e) { 
    for (;;) { 
    if (e instanceof PackageElement) { 
     return (PackageElement)e; 
    } 
    e = e.getEnclosingElement(); 
    } 
} 

通過這樣做,可以肯定包中的所有(頂層)的元素被一起處理,即使編譯只被調用上包中的單個源文件。

如果您知道在哪裏查找註釋(包中的頂層元素或者包中的任何元素),您應該始終能夠獲取所有這些元素的列表。

+0

這對我不起作用,第一個循環pkg.getEnclosedElements()返回元素,但註釋僅由正在編譯的元素返回,另一個getAnnotation返回null,儘管它們已經在類上定義了它。 (我在上面的鏈接中檢查了註釋處理器,並添加了if(roundEnv.processingOver())返回false;)編輯:我通過javac – RookieGuy