領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多上個月我們發現了使用 Spring Roo(我們為 Java 開發人員提供的全新生產力工具)在短短幾分鐘內 構建一個功能齊全的企業應用程式 是多麼容易。雖然許多 Java 開發人員 已經 開始 評估 Roo 以 幫助 節省 他們 專案 的 時間,但我收到了許多人提出的關於 Roo 實際工作原理的問題。在這篇博文中,我將深入探討 Roo 的架構,包括其目標、原型替代方案、設計原理和實現細節。到最後,您將對 Roo 的運作方式及其方法為何適用於 Java 專案有很好的理解。
在深入探討 Roo 的架構細節之前,我應該簡要提及我們今天釋出了 Spring Roo 1.0.0.M2。這個新版本包含 數十個錯誤修復和次要增強,還包括:
自我的 上一篇博文 以來,我們還發布了 SpringSource Tool Suite (STS) 2.1.0.M2。STS 中的 Roo 支援持續改進,您現在甚至可以配置 STS 指向單獨下載的 Roo 安裝。這對於編寫自己的 Roo 附加元件或僅僅想將最新 Roo 版本與 STS 一起使用的人來說是個好訊息。STS 中其他不錯的 Roo 功能包括 CTRL + R “Roo 命令”分派、內建的 Roo shell、用於執行整合測試或部署(包括到雲環境!)的額外 Roo 命令等等。如果您尚未下載 STS 2.1.0.M2,我強烈建議您下載。
Roo 的核心提供了一套允許使用“附加元件”的核心服務。這些核心服務包括一個 shell、檔案系統管理器、檔案系統監視器、檔案撤銷功能、類路徑抽象、抽象語法樹 (AST) 解析和繫結、專案構建系統介面、元資料模型、程序管理、引導和實用程式服務。雖然我們稍後將間接探討其中一些核心服務,但終端使用者感興趣的絕大多數功能都來自附加元件。如果沒有附加元件,Roo 只是一個複雜的控制檯。
當您下載 Roo 時,我們提供核心服務和一系列常見附加元件。所有附加元件都可以透過其 JAR 名稱中出現的“addon”關鍵字來識別。Roo 附帶的每個附加元件都是可選的,終端使用者可以自由增強現有附加元件或建立新附加元件。事實上,我們非常歡迎社群開發和分享他們認為有用的附加元件。
考慮到 Roo 的核心服務與使用者可能希望使用的附加元件之間存在設計分離,我們為 Roo 1.0.0 投入的精力集中在確保主流 Web 應用程式能夠輕鬆高效地開發。在 Roo 的後續版本中,我們將提供越來越豐富的附加元件,幫助使用者構建其他型別的應用程式。
我經常被問到的一個領域是 Roo 對 Maven 的使用。正如我的 上一篇博文 中所述,Roo 1.0.0 建立的專案使用 Maven。由於這種 Maven 用法是透過附加元件實現的,因此很容易新增對其他專案構建系統的支援。事實上,我們收到了許多關於 Ant/Ivy 支援的請求,並且 Jira (ROO-91) 中已經有一個功能請求。
類似地,Roo 目前還附帶 JPA 和 JSP 附加元件。這兩個都是我們在 Roo 1.0.0 中支援典型 Web 應用程式開發所做的實用選擇。沒有任何技術原因可以阻止開發 JDBC、JDO、JSF、Velocity 和 FreeMarker 附加元件,我們希望隨著時間的推移看到這些附加元件。
由於這篇博文側重於 Roo 的架構,我將在此結束對單個附加元件的討論。如果您想了解有關如何使用當前 Roo 1.0.0 附加元件構建應用程式的更多資訊,可以閱讀我的 上一篇博文。現在,讓我們深入探討 Roo 的實際工作原理。
在審查任何技術時,重要的是要考慮影響其架構選擇的設計目標和目的。我在我的 原始 Roo 博文 中探討了其中一些目標,但讓我們在這裡更詳細地重新討論這個主題。
最重要的是,我們希望 Roo 成為 Java 開發人員的生產力解決方案。許多開發人員喜歡(或需要)使用 Java,而 Java 仍然是 全球使用最廣泛的程式語言。為這一龐大的開發人員群體提供一流的生產力工具是 Roo 最根本的目標。
其次,我們希望確保消除採用 Roo 的障礙。如果人們不習慣(或根本不允許)使用它,那麼擁有一個出色的生產力工具就毫無意義。具體來說,這意味著沒有 鎖定(即輕鬆移除 Roo),沒有執行時部分(以及許多組織中潛在的審批障礙),沒有不自然的開發技術,沒有 IDE 依賴,沒有許可成本,沒有奇怪的依賴使其工作,沒有陡峭的學習曲線,並且不犧牲速度、效能或靈活性。
第三,我們希望提供一個建立在 Java 諸多優勢之上的解決方案。這些優勢包括極佳的執行時效能、標準的可用性(如 JPA、Bean Validation、REST、Servlet API 等)、出色的 IDE 支援(如偵錯程式、程式碼輔助、重構等)、成熟的技術、型別安全,以及龐大的現有開發人員知識、技能和經驗庫(不僅是 Java 本身,還有事實上的 Java 構建塊,如 Spring、JSP、Hibernate 等)。
考慮到上述要求,我在 2008 年原型化了許多不同的技術,包括 JSR 269(Java 6 中的可插拔註解處理 API)、構建時原始碼生成、IDE 外掛、開發時位元組碼生成、執行時位元組碼生成以及高階反射方法,例如 Spring Framework AOP 的擴充套件。我沒有原型化其他 JVM 語言,因為支撐 Roo 的主要動機是實現 Java 程式設計的工具。
我原型化的每種方法都或多或少存在問題,導致其被排除。每種方法都需要特殊的執行時、特殊的 IDE 外掛或次優的構建步驟(或其組合)。大多數還永久性地將使用者鎖定在該方法中,移除過程異常困難,從而造成採用障礙,阻止許多 Java 開發人員享受所提供的生產力提升。許多方法還依賴於執行時反射技術,這會使除錯變得緩慢和混亂,並且大多數幾乎沒有提供 IDE 整合。我還特別傾向於提供一個輕量級的命令列工具,因為我堅信這將提供比 GUI 更好的可用性體驗。這些是我們沒有使用上述方法的原因。
經過大量的原型設計,我們確定了 Roo 架構,其關鍵要素包括:
這種架構不需要特殊的構建系統、執行時元件、IDE 外掛或類似的東西。它也滿足了前面提到的所有設計要求。
實現這一點的“新想法”是自動將 ITD 用作程式碼生成工件。以這種方式使用 ITD 帶來了巨大的實際好處,因為它允許 Roo 生成的程式碼位於與開發人員編寫的程式碼不同的 編譯單元(即物理檔案)中。儘管位於單獨的檔案中,但 ITD 在編譯時會合併到同一個編譯後的 .class 檔案中。由於生成的類本質上與開發人員自己編寫所有程式碼時相同,因此傳統 Java 程式設計的所有優點(如 IDE、偵錯程式支援、程式碼輔助、型別內省、型別安全等)都按您預期的方式工作。此外,由於編譯後的類只是一個類檔案,因此在執行時一切都完美執行。具體來說,您無需擔心 反射效能、記憶體使用、混亂且難以除錯的操作、可能需要批准和升級的額外庫等問題。
將 ITD 用於程式碼生成令人興奮的還有它所帶來的 關注點分離。關注點分離對應用程式開發人員有利,因為他們可以安全地忽略 Roo 建立的 ITD 檔案(因為開發人員知道 Roo 會管理它們)。但關注點分離對 Roo 附加元件也同樣出色。附加元件的開發要容易得多,因為附加元件開發人員知道他們控制著整個 ITD 編譯單元的內容。一個更微妙的好處是它提供了自動升級支援。在 Roo 的開發過程中,我們看到了許多示例,我們改進了一個附加元件,然後隨後載入 Roo 的使用者會自動收到升級後的 ITD。類似地,使用者可以從其環境中移除附加元件,Roo 會自動移除相關的 ITD。這是一種我們發現非常有價值的極其實用和有用的技術。
ITD 的最後一個主要好處是避免鎖定。正如我們稍後將看到的,ITD 本質上是普通的 Java 原始檔。它們只是與所有其他原始碼一起儲存在您的磁碟上,這意味著開發人員可以選擇不再載入 Roo,他們的專案仍然可以工作。那些想要更徹底移除的人可以使用 Eclipse AJDT 的“推入重構”等功能。這會自動將所有原始碼從 ITD 移動到正確的 Java 原始檔中。這意味著如果您不想再使用 Roo,只需“推入重構”您的專案,您就會得到一個完美的普通 Java 專案——就像您自己手動編寫所有程式碼一樣。這是一個絕佳的訊息:
Roo 使用 AspectJ 提供的 ITD。SpringSource 是 AspectJ 的大力支持者和使用者,以下是我們認為它非常適合基於 Roo 的專案的一些原因:
讓我們透過建立一個新專案來探討 Roo 的 ITD 用法和元資料模型。假設您已經安裝了 Roo 1.0.0.M2,讓我們為專案建立一個新目錄並啟動 Roo:
$ mkdir architecture
$ cd architecture
$ roo
收到歡迎屏幕後,輸入以下命令:
roo> project --topLevelPackage com.hello
roo> persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY
roo> entity --name World
roo> field string name
您的螢幕將如下所示:

現在讓我們開啟一個文字編輯器,看看 World.java 檔案內部
package com.hello;
import javax.persistence.Entity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooEntity;
@Entity
@RooJavaBean
@RooToString
@RooEntity
public class World {
private String name;
}
如圖所示,有幾個 @Roo* 註解。這些註解包含在 Roo 附加元件中,並指示 Roo 在需要時建立 ITD。@RooEntity 註解表示您希望 Roo 自動提供典型的 JPA 方法和欄位(包括識別符號和版本屬性)。@RooJavaBean 請求為每個欄位建立 getter 和 setter。@RooToString 請求建立一個 toString() 方法。
Roo 建立的所有 ITD 都遵循特定的命名約定。約定是 SimpleTypeName + "_Roo_" + AddOnSpecificKeyword + ".aj"。Roo 自動確保所有符合此格式的檔案都由相關附加元件妥善管理。如果某個特定關鍵字沒有安裝附加元件,Roo 將刪除孤立的 ITD 檔案。這確保您可以隨時更改附加元件配置,而無需手動清理。
讓我們看看 World_Roo_ToString.aj ITD 內部
package com.hello;
privileged aspect World_Roo_ToString {
public String World.toString() {
StringBuilder sb = new StringBuilder();
sb.append("id: ").append(getId()).append(", ");
sb.append("version: ").append(getVersion()).append(", ");
sb.append("name: ").append(getName());
return sb.toString();
}
}
正如你所見,ITD 看起來就像一個普通的 Java 原始檔。只有一個區別:在方法簽名中,"toString()" 方法名前面有一個 "World." 字首。這指示 AspectJ 在編譯期間將 toString() 方法引入到 World.class 檔案中。正如你所見,即使你以前從未遇到過 ITD,它們也極其簡單。特別是,不需要任何切入點。
讓我們編輯 World.java 檔案並向其中新增另一個欄位
private String comment;
如果您讓 Roo 保持執行,那麼只要您儲存 World.java,您就會注意到它會立即修改 World_Roo_JavaBean.aj 和 World_Roo_ToString.aj 檔案。這是因為 Roo 監視檔案系統以檢測您在 Roo shell 之外進行的任何更改,例如透過您首選的 IDE。如果您願意,也可以使用 Roo 的“add field string”命令。
如果您沒有執行 Roo,那麼下次載入它時,將執行自動啟動時掃描。這包括如果相關附加元件已升級,則自動升級任何現有的 ITD(甚至在附加元件不再存在時刪除 ITD)。重點是所有這一切都是自動且自然發生的,您無需擔心遵循關於 Roo 何時必須執行或如何更改檔案等特殊規則和約束。
所有 @Roo* 註解都允許您控制正在使用的成員名稱,並自行提供成員。讓我們編輯 World.java 檔案並將 @RooToString 註解更改為:
@RooToString(toStringMethod="rooIsFun")
如果您現在檢視 World_Roo_ToString.aj 檔案,您會看到方法名已自動更改。
package com.hello;
privileged aspect World_Roo_ToString {
public String World.rooIsFun() {
StringBuilder sb = new StringBuilder();
sb.append("id: ").append(getId()).append(", ");
sb.append("version: ").append(getVersion()).append(", ");
sb.append("comment: ").append(getComment()).append(", ");
sb.append("name: ").append(getName());
return sb.toString();
}
}
假設您不喜歡 Roo 的 toString() 方法(現在是 rooIsFun(),請記住!)。您有兩種方法可以刪除它。您可以刪除或註釋掉 World.java 檔案中的 @RooToString 註解,或者您可以直接在 World.java 中提供自己的 rooIsFun() 方法。隨意嘗試這兩種技術。在這兩種情況下,您都會看到 Roo 自動刪除 World_Roo_ToString.aj 檔案,因為它知道您不再需要 Roo 為您提供該方法。這反映了 Roo 的方法:您始終完全掌控一切,沒有任何意外。
雖然您當然不需要了解 Roo 的內部結構就可以簡單地使用 Roo,但好奇的讀者可能會想知道 World_Roo_ToString.aj 檔案是如何知道有 getId()、getVersion()、getComment() 和 getName() 方法可用的。考慮到這些方法甚至不在 World.java 檔案中,這尤其有趣。讓我們對此進行更深入的探討。
在 Roo shell 中,輸入以下命令:
roo> metadata for type --type com.hello.World
結果螢幕應類似於:

這總結了 Roo 對 World.java 型別的內部表示。它是透過對 World.java 檔案進行 AST 解析和繫結構建的。您可能已經注意到列出了下游依賴項。這些代表了其他元資料項,如果 World.java 元資料發生任何更改,它們希望收到通知。附加元件通常會監聽其他元資料項的更改,然後相應地修改 ITD(或 XML 檔案或 JSP 等)。
您可以透過輸入“metadata trace 1”然後更改 World.java 檔案來觀察元資料事件通知的發生。通知訊息將類似於以下內容:

在結束對 Roo 元資料模型的介紹之前,我要指出 Roo 不需要將元資料保留在記憶體中。這確保了非常大的專案仍然可以使用 Roo 而不會耗盡記憶體。Roo 自動跟蹤快取統計資訊以及單個附加元件的執行時配置檔案。記憶體充足的系統將享受自動的 LRU 快取。如果您對 LRU 快取統計資訊感到好奇,可以透過“metadata status”命令獲取(請注意,快取命中率令人欣喜地高)
roo> metadata status
2: org.springframework.roo.addon.configurable.ConfigurableMetadata
5: org.springframework.roo.addon.javabean.JavaBeanMetadata
8: org.springframework.roo.addon.finder.FinderMetadata
35: org.springframework.roo.addon.plural.PluralMetadata
53: org.springframework.roo.addon.beaninfo.BeanInfoMetadata
64: org.springframework.roo.addon.entity.EntityMetadata
124: org.springframework.roo.addon.tostring.ToStringMetadata
862: org.springframework.roo.process.manager.internal.DefaultFileManager
[DefaultMetadataService@6030f9 providers = 14, validGets = 369, cachePuts = 17, cacheHits = 352, cacheMisses = 17, cacheEvictions = 0, cacheCurrentSize = 6, cacheMaximumSize = 1000]
我希望您覺得這篇關於 Roo 工作原理的討論很有趣。我們已經看到 Roo 使用 ITD 為 Java 開發人員實現可持續的生產力提升。我們研究了 Roo ITD 方法的優點,並深入瞭解了它的實際工作原理,包括如何自定義它們,它們如何在元資料級別操作以及它們的生命週期如何透明地自動連結到附加元件升級。我們還討論了 ITD 如何提供成熟且經過驗證的關注點分離,同時避免了鎖定、執行時影響以及在大型實際專案中很重要的其他細微問題。最後,我們回顧了 Roo 的元資料系統,並探討了它的一些事件通知、型別內省和可擴充套件性功能。
我們期待支援社群參與 Roo 並開發新的附加元件。我們邀請您 試用 Roo,我們非常歡迎您的 反饋、錯誤報告、功能想法 和 評論。希望您喜歡使用 Roo。