級別: 中級 |
Adrian Powell
資深軟件開發人員, IBM
2004 年 6 月
Eclipse 的 Java Emitter Templates(JET) 是一個開放源代碼工具,可以在 Eclipse Modeling Framework(EMF)中生成代碼。 JET 與 JSP 非常類似,不同之處在於 JET 功能更強大,也更靈活,可以生成 Java、 SQL 和任何其他語言的代碼,包括 JSP。本文將介紹如何創建和配置 JET,並將其部署到各種環境中。
Java Emitter Templates(JET) 概述
開發人員通常都使用一些工具來生成常用的代碼。 Eclipse 用戶可能對一些標準的工具非常熟悉,這些工具可以爲選定的屬性生成 for(;;)
循環, main()
方法, 以及選定屬性的訪問方法。將這些簡單而機械的任務變得自動化,可以加快編程的速度,並簡化編程的過程。在某些情況中,例如爲 J2EE 服務器生成部署代碼,自動生成代碼就可以節省大量時間,並可以隱藏具體實現特有的一些複雜性,這樣就可以將程序部署到不同的 J2EE 服務器上。自動生成代碼的功能並不只是爲開發大型工具的供應商提供的,在很多項目中都可以使用這種功能來提高效率。 Eclipse 的 JET 被包裝爲 EMF 的一部分,可以簡單而有效地向項目中添加自動生成的代碼。本文將介紹在各種環境中如何使用 JET 。
JET 是什麼?
JET 與 JSP 非常類似:二者使用相同的語法,實際上在後臺都被編譯成 Java 程序;二者都用來將呈現頁面與模型和控制器分離開來;二者都可以接受輸入的對象作爲參數,都可以在代碼中插入字符串值(表達式),可以直接使用 Java 代碼執行循環、聲明變量或執行邏輯流程控制(腳本);二者都可以很好地表示所生成對象的結構,(Web 頁面、Java 類或文件),而且可以支持用戶的詳細定製。
JET 與 JSP 在幾個關鍵的地方存在區別。在 JET 中,可以變換標記的結構來支持在不同的語言中生成代碼。通常 JET 程序的輸入都是一個配置文件,而不是用戶的輸入(當然也不禁止這樣使用)。而且對於一個給定的工作流來說,JET 通常只會執行一次。這並不是技術上的限制;您可以看到 JET 有很多完全不同的用法。
創建模板
要使用 JET,創建一個新 Java 項目 JETExample
,並將源文件夾設置爲 src
。爲了讓 JET 啓用這個項目,請點擊鼠標右鍵,然後選擇 Add JET Nature。這樣就會在新項目的根目錄下創建一個 templates
目錄。JET 的缺省配置使用項目的根目錄來保存編譯出來的 Java 文件。要修改這種設置,打開該項目的 properties 窗口,選擇 JET Settings,並將 source container 設置爲 src
。這樣在運行 JET 編譯器時,就會將編譯出來的 JET Java 文件保存到這個正確的源文件夾中。
現在我們已經準備好創建第一個 JET 了。JET 編譯器會爲每個 JET 都創建一個 Java 源文件,因此習慣上是將模板命名爲 NewClass.javajet
,其中 NewClass
是要生成的類名。雖然這種命名方式不是強制的,但是這樣可以避免產生混亂。
首先在模板目錄中創建一個新文件 GenDAO.javajet
。這樣系統會出現一個對話框,警告您在這個新文件的第 1 行第 1 列處有編譯錯誤。如果您詳細地看以下警告信息,就會發現它說 "The jet directive is missing"(沒有 jet 指令)。雖然這在技術上沒有什麼錯誤,因爲我們剛纔只不過是創建了一個空文件,但是這個警告信息卻很容易產生混亂並誤導我們的思路。單擊 'OK' 關閉警告對話框,然後單擊 'Cancel' 清除 New File 對話框(這個文件已經創建了)。爲了防止再次出現這種問題,我們的首要問題是創建 jet
指令。
每個 JET 都必須以 jet
指令開始。這樣可以告訴 JET 編譯器編譯出來的 Java 模板是什麼樣子(並不是模板生成了什麼內容,而是編譯生成的模板類是什麼樣子;請原諒,這個術語有些容易讓人迷惑)。此處還要給出一些標準的 Java 類信息。例如,在下面這個例子中使用了以下信息:
|
清單 1 的內容是真正自解釋的。在編譯 JET 模板時,會創建一個 Java 文件 GenDAO
,並將其保存到 com.ibm.pdc.example.jet.gen
中,它將導入指定的包。重複一遍,這只是說明模板像什麼樣子,而不是模板將要生成的內容 -- 後者稍後將會介紹。注意 JET 輸出結果的 Java 文件名是在 jet
的聲明中定義的,它並不侷限於這個文件名。如果兩個模板聲明瞭相同的類名,那麼它們就會相互影響到對方的變化,而不會產生任何警告信息。 如果您只是拷貝並粘貼模板文件,而沒有正確地修改所有的 jet
聲明,那就可能出現這種情況。因爲在模板目錄中創建新文件時會產生警告,而拷貝和粘貼是非常常見的,因此要自己小心這個問題。
JSP 可以通過預先聲明的變量(例如會話、錯誤、上下文和請求)獲取信息, JET 與此類似,也可以使用預先聲明的變量向模板傳遞信息。JET 只使用兩個隱式的變量: stringBuffer
,其類型爲 StringBuffer
(奇怪吧?),它用來在調用 generate()
時構建輸出字符串;以及一個參數,出於方便起見,我們稱之爲 argument
,它是 Object
類型。典型的 JET 模板的第一行會將其轉換爲一個更適合的類,如清單 2 所示。
|
正如您可以看到的一樣,JET 的缺省語法與 JSP 相同:使用 <%...%> 包括代碼,使用 <%= ... %> 打印表達式的值。與 JSP 類似,正確地使用 <% ... %> 標籤就可以添加任何邏輯循環或結構,就像是在任何 Java 方法中一樣。例如:
清單 3. 腳本和表達式
|
在定義完 JET 之後,保存文件並在包瀏覽器中在這個文件上點擊鼠標右鍵,選擇 Compile Template。如果一切正常,就會在 com.ibm.pdc.example.jet.gen
包中創建一個類 GenDAO
。其中只有一個方法 public String generate(Object argument)
(參見清單 4),這樣做的結果就是在 javajet
模板中定義的內容。
|
準備公共代碼
編寫好模板之後,您可能就會注意到一些公共的元素,這些元數會反覆出現,例如所有生成的代碼中都添加的版權信息。在 JSP 中,這是通過 include
聲明處理的。將所有想要添加的內容都放到一個文件中,並將該文件命名爲 'copyright.inc',然後在 javajet
模板中添加 <%@ include file="copyright.inc" %>
語句。所指定的包含文件會被添加到編譯後的輸出結果中,因此它可以引用到現在爲止已經聲明的任何變量。擴展名 .inc
可以任意,只是不要採用以 jet
或 JET 結尾的名字,否則將試圖編譯包含文件,這樣該文件的理解性自然很差。
定製 JET 編譯
如果只使用包含文件還不能滿足要求,您可能會想添加其他一些方法,或者對代碼生成過程進行定製;最簡單的方法是創建一個新的 JET 骨架。骨架文件就是描述編譯後的 JET 模板樣子的一個模板。缺省的骨架如清單 5 所示。
|
所有的 import 語句都位於最開始, CLASS
會被替換爲在 jet 聲明的 class
屬性中設置的類名, generate()
方法的代碼會被替換爲執行生成操作的代碼。因此,要修改編譯後的模板代碼的樣子,我們只需要創建一個新的骨架文件並進行自己想要的定製即可,但是仍然要在原來的地方保留基本的元素。
要創建一個定製的骨架,在 custom.skeleton
模板目錄中創建一個新文件,如清單 6 所示。
|
然後在想要使用這個定製骨架的任何 JET 模板中,向 javajet
文件中的 jet
聲明添加 skeleton="custom.skeleton"
屬性。
或者,也可以使用它對基類進行擴充,例如 public class CLASS extends MyGenerator
,並在基類中添加所有必要的幫助器方法。這樣可能會更加整潔,因爲它保留了代碼的通用性,並可以簡化開發過程,因爲 JET 編譯器並不能總是給出最正確的錯誤消息。
定製骨架也可以用來修改方法名和 generate()
方法的參數列表,這樣非常挑剔的開發人員就可以任意定製模板。說 JET 要將 generate()
的代碼替換爲要生成的代碼,其實有些不太準確。實際上,它只會替換在骨架中聲明的最後一個方法的代碼,因此如果粗心地修改骨架的代碼,就很容易出錯,而且會讓您的同事迷惑不解。
使用 CodeGen
正如您可以看到的一樣,模板一旦編譯好之後,就是一個標準的 Java 類。要在程序中使用這個類,只需要分發編譯後的模板類,而不需要分發 javajet
模板。或者,您可能希望讓用戶可以修改模板,並在啓動時自動重新編譯模板。EMF 可以實現這種功能,任何需要這種功能或對此感興趣的人都可以進入 plugins/org.eclipse.emf.codegen.ecore/templates
中,並修改 EMF 生成模型或編輯器的方式。
如果您只是希望可以只分發編譯後的模板類,那麼編譯過程可以實現自動化。迄今爲止,我們只看到瞭如何使用 JET Eclipse 插件來編譯 JET 模板,但實際上我們可以編寫一些腳本來實現這種功能,或者將生成代碼的工作作爲一項 ANT 任務。
運行時編譯模板
要讓最終用戶可以定製模板(以及對模板的調試),可以選擇在運行時對模板進行編譯。實現這種功能有幾種方法,首先我們使用一個非常有用的類 org.eclipse.emf.codegen.jet.JETEmitter
,它可以對細節進行抽象。常見的(但通常是錯誤的)代碼非常簡單,如清單 7 所示。
|
如果您試圖在一個標準的 main()
方法中運行這段代碼,就會發現第一個問題。 generate()
方法會觸發一個 NullPointerException
異常,因爲 JETEmitter
假設自己正被一個插件調用。在初始化過程中,它將調用 CodeGenPlugin.getPlugin().getString()
,這個函數會失敗,因爲 CodeGenPlugin.getPlugin()
爲空。
解決這個問題有一個簡單的方法:將這段代碼放到一個插件中,這樣的確可以管用,但卻不是完整的解決方法。現在 JETEmitter
的實現創建了一個隱藏項目 .JETEmitters
,其中包含了所生成的代碼。然而, JETEmitter
並不會將這個插件的 classpath 添加到這個新項目中,因此,如果所生成的代碼引用了任何標準 Java 庫之外的對象,都將不能成功編譯。2.0.0 版本初期似乎解決了這個問題,但是到 4 月初爲止,這還沒有完全實現。要解決這個問題,必須對 JETEmitter
類進行擴充,使其覆蓋 initialize()
方法,並將其加入您自己的 classpath 項中。Remko Popma 已經編寫了很好的一個例子 jp.azzurri.jet.article2.codegen.MyJETEmitter
(參閱 參考資料),這個例子可以處理這個問題,在 JET 增加這種正確的特性之前都可以使用這種方法。修改後的代碼如清單 8 所示。
|
命令行
在命令行中編譯 JET 非常簡單,不會受到 classpath 問題的影響,這個問題會使編譯一個 main()
方法都非常困難。在上面這種情況中,難點並不是將 javajet 編譯成 Java 代碼,而是將這個 Java 代碼編譯成 .class
。在命令行中,我們可以更好地控制 classpath,這樣可以分解每個步驟,最終再組合起來,就可以使整個工作順利而簡單。唯一一個技巧是我們需要以一種 "無頭" 模式(沒有用戶界面)來運行 Eclipse,但即便是這個問題也已經考慮到了。要編譯 JET,請查看一下 plugins/org.eclipse.emf.codegen_1.1.0/test
。這個目錄中包含了 Windows 和 Unix 使用的腳本,以及一個要驗證的 JET 例子。
作爲一個 ANT 任務執行
有一個 ANT 任務 jetc
,它要麼可以採用一個 template
屬性,要麼對多個模板有一個 fileset
屬性。一旦配置好 jetc
任務的 classpath 之後,模板的編譯就與標準的 Java 類一樣簡單。有關如何獲取並使用這個任務的更多信息,請參閱 參考資料。
定製 JET 以生成 JSP
最終,JET 使用 "<%" 和 "%>" 來標記模板,然而這與 JSP 使用的標記相同。如果您希望生成 JSP 程序,那就只能修改定界符。這可以在模板開頭的 jet
聲明中使用 startTag
和 endTag
屬性實現,如清單 9 所示。在這種情況中,我使用 "[%" 和 "%]" 作爲開始定界符和結束定界符。正如您可以看到的一樣, "[%= expression %]" 可以正確處理,就像前面的 "<%= expression %>" 一樣。
|
結束語
有一個不幸的事實:很多代碼都是通過拷貝/粘貼而實現重用的,不管是大型軟件還是小型軟件都是如此。很多時候這個問題並沒有明顯的解決方案,即使面嚮對象語言也不能解決問題。在重複出現相同的基本代碼模式而只對實現稍微進行了一些修改的情況中,將通用的代碼放到一個模板中,然後使用 JET 來生成各種變化,這是一種很好的節省時間和精力的辦法。JSP 早已採用了這種方法,因此 JET 可以從 JSP 的成功中借鑑很多東西。JET 使用與 JSP 相同的基本佈局和語義,但是允許更靈活的定製。爲了實現更好的控制,模板可以進行預編譯;爲了實現更高的靈活性,也可以在運行時編譯和分發。
在本系列的下一篇文章中,我們將介紹如何爲 Prime Time 生成代碼,這包括允許用戶定製代碼,以及集成以域或方法甚至更細粒度級別的修改,從而允許重新生成代碼。我們還會將它們都綁定到一個插件中,從而展示一種將生成的代碼集成到開發過程的方法。
- 閱讀 developerWorks 上 Eclipse Modeling Framework 系列的其他文章: 第 1 部分 創建和使用 EMF 模型, 和 第 3 部分 將生成的代碼合併到現有代碼中。
- EMF 項目主頁 上提供了所有的文檔、源代碼和最新的已編譯程序。
- IBM 紅皮書 Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework 中有更詳細的例子,其中介紹了更多定製特性。
- 更多有關 JET 的內容,可以閱讀 Remko Popma 的 JET 指南 第 1 部分 和 第 2 部分。如果您想在運行時編譯模板,可以參考第 2 部分中包括的代碼。
- 如果有問題,可以訪問 EMF 新聞組。如果您之前沒有使用過 Eclipse 新聞組,請閱讀 規則以及如何申請密碼。
- 在 developerWorks 的 開放源代碼項目專區 中有更多爲 爲 Eclipse 用戶提供的文章。 請參閱 alphaWorks 中最新的 Eclipse 技術下載區。
- 在 Developer Bookstore 的開放源代碼區,可以找到數百本 有關開放源代碼主題的書籍,其中包括幾本 有關 Eclipse 的書籍。
- 訂閱 developerWorks,使用其中介紹的最新 IBM 工具和中間件開發並測試應用程序:您可以獲得 IBM 的各種軟件,從基於 Eclipse 的 WebSphere、 DB2、 Lotus、 Rational 和 Tivoli,以及使用這些軟件的一個爲期 12 個月的許可證,這些內容只需要極少的錢就可以獲得。
- 訂閱 developerWorks,從 developerWorks 的 Speed-start your Linux app 部分下載在 Linux 上運行的產品的免費試用版,包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere Application Server、DB2 Universal Database Personal Developers Edition、Tivoli Access Manager 和 Lotus Domino Server。這樣可以更快入門,並且有助於蒐集每種產品的文檔和技術支持。