領先一步
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。同樣地,使用者可以從他們的環境中移除附加元件,相關的 ITD 將會被 Roo 自動移除。這是一個非常務實且有用的技術,我們發現它非常寶貴。
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。