領先一步
VMware 提供培訓和認證,助您加速前進。
瞭解更多上個月,我們發現使用 Spring Roo(我們為 Java 開發者提供的新生產力工具)構建一個功能齊全的企業應用只需幾分鐘時間,這非常容易。雖然許多 Java 開發者已經開始評估Roo,以幫助在他們的專案上節省時間,但我收到了很多來自好奇 Roo 實際工作原理的人的提問。在這篇部落格文章中,我將深入探討 Roo 的架構,包括其目標、原型備選方案、設計理念和實現細節。讀完本文,您將對 Roo 的運作機制及其方法為何對 Java 專案行之有效有一個很好的理解。
在深入探討 Roo 架構的細節之前,我應該簡要提及,我們今天釋出了 Spring Roo 1.0.0.M2。此新版本包含數十個 bug 修復和次要增強功能,還包括以下特性:
自從我的上一篇部落格文章以來,我們還發布了 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 本身,還包括 Spring、JSP、Hibernate 等事實上的 Java 構建模組)。
考慮到上述要求,我在 2008 年原型化了許多不同的技術,包括 JSR 269(Java 6 中的可插拔註解處理 API)、構建時原始碼生成、IDE 外掛、開發時位元組碼生成、執行時位元組碼生成以及高階反射方法(如 Spring Framework AOP 的擴充套件)。我沒有原型化其他 JVM 語言,因為 Roo 的核心動機是成為一個支援 Java 程式設計的工具。
我原型化的每種方法都或多或少存在問題,因此被排除。每種方法都需要特殊的執行時、特殊的 IDE 外掛或次優的構建步驟(或它們的組合)。大多數方法還會永久鎖定使用者,移除極其困難,從而造成了採用障礙,阻止許多 Java 開發者享受所提供的生產力提升。許多方法還依賴於執行時的反射技術,這會慢且難以除錯,並且大多數提供的 IDE 整合很少或根本沒有。我還特別偏愛提供一個輕量級的命令列工具,因為我堅信這將比 GUI 提供更好的可用性體驗。這就是我們沒有使用上述方法的原因。
經過大量的原型設計,我們最終確定了 Roo 的架構,其關鍵要素包括:
這個架構不需要特殊的構建系統、執行時元件、IDE 外掛等。它也滿足了之前提到的所有設計要求。
使這成為可能的“新想法”是自動將 ITDs 用作程式碼生成製品。以這種方式使用 ITDs 帶來了巨大的實際好處,因為它允許 Roo 生成的程式碼與開發者編寫的程式碼位於不同的編譯單元(即物理檔案)中。儘管位於單獨的檔案中,但在編譯時 ITDs 會被合併到同一個編譯後的 .class 檔案中。由於最終的類與開發者自己編寫所有程式碼所得的類本質上相同,因此傳統 Java 程式設計的所有優勢(如 IDE、偵錯程式支援、程式碼輔助、型別內省、型別安全等)都能正常工作。此外,由於編譯後的類只是一個 .class 檔案,一切在執行時都能完美執行。具體來說,您不必擔心諸如反射效能、記憶體使用、令人困惑且難以除錯的操作、可能需要審批和升級的額外庫等問題。
將 ITDs 用於程式碼生成的另一個令人興奮之處在於它實現了關注點分離。關注點分離使應用開發者受益,因為他們可以安全地忽略 Roo 建立的 ITD 檔案(因為開發者知道 Roo 會管理它們)。但是,關注點分離對 Roo 外掛也非常好。外掛的開發變得更加容易,因為外掛開發者知道他們控制著整個 ITD 編譯單元的內容。一個更微妙的好處是它提供的自動升級支援。在 Roo 的開發過程中,我們看到了許多這樣的例子:我們改進了一個外掛,然後隨後載入 Roo 的使用者會自動獲得一個升級後的 ITD。同樣,使用者可以從他們的環境中移除外掛,Roo 會自動移除相關的 ITDs。這是一種我們發現極其實用和有價值的技術。
ITDs 的最後一個主要好處是避免鎖定。正如我們稍後將看到的,ITDs 本質上是普通的 Java 原始檔。它們只是和所有其他原始碼一樣儲存在您的磁碟上,這意味著開發者可以選擇不再載入 Roo,而他們的專案仍然可以工作。那些想要更徹底移除的人可以使用諸如 Eclipse AJDT 的“推入重構”(push in refactoring)之類的功能。它的作用是將 ITDs 中的所有原始碼自動移動到正確的 Java 原始檔中。這意味著如果您不想再使用 Roo,只需對您的專案進行“推入重構”,您就擁有了一個完全正常的 Java 專案——就像您自己親手編寫所有程式碼一樣。這是個極好的訊息:
Roo 使用 AspectJ 提供的 ITDs。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 建立的所有 ITDs 都遵循特定的命名約定。約定是 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 檔案。正如您所見,即使您之前從未接觸過 ITDs,它們也非常簡單。特別是,不需要任何切入點(pointcuts)。
讓我們編輯 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,下次載入它時,會執行一個自動的啟動時掃描。這包括在相關外掛升級時自動升級任何現有的 ITDs(甚至在外掛不再存在時刪除 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 元資料發生變化時收到通知。外掛通常會監聽其他元資料項的變化,然後相應地修改 ITDs(或 XML 檔案或 JSPs 等)。
您可以透過鍵入“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 使用 ITDs 為 Java 開發者實現了可持續的生產力提升。我們探討了 Roo 的 ITD 方法的優勢,並深入瞭解了它的實際工作原理,包括如何自定義它們,它們如何在元資料層面運作,以及它們的生命週期如何透明且自動地與外掛升級關聯。我們還討論了 ITDs 如何在提供成熟且經過驗證的關注點分離的同時,避免鎖定、執行時影響以及在大型實際專案中重要的其他微妙問題。最後,我們回顧了 Roo 的元資料系統,並探討了它的一些事件通知、型別內省和可伸縮性特性。
我們期待支援社群參與 Roo 並開發新的外掛。我們邀請您試用 Roo,並且非常歡迎您的反饋、bug 報告、功能建議和評論。希望您喜歡使用Roo。