Google App Engine帶來的Java開發2.0實現
Java開發2.0?聽起來是個挺新穎,但也挺老調長談的概念。隨著云計算的興起,而Google App Engine又提供了Java支持,IBMDW的Andrew Glover在本文中將介紹這個Java開發2.0是怎么一回事,以及在Google App Engine中又是如何體現的。
Java 世界如同一個豐富的生態系統,涉及開發人員、事務以及(最為重要的)應用程序等各種角色,其中大部分內容在過去十年里已經發展成熟。全球 Java 社區在 Java 平臺上投入了巨大的金錢、時間和腦力勞動,這些貢獻造就了一個包含成功的開源商業工具、框架以及解決方案的巨大寶庫。
在 Java 平臺方面的各種投入使 Java 開發的方式產生了微妙的變化。兩個重要的趨勢正在快速改變 Java 開發的特征:
- 充分地利用開源工具和框架自上而下 構建應用程序
- 租用(或外借)各種應用程序基礎設施來管理軟件生命周期,包括運行應用程序本身
我所指的 Java開發2.0 的任何一個方面都不是新的或革命性的改變,僅僅是實現技術已經成熟到可以快速、便宜地組裝更好的應用程序,這在 Java 技術的歷史上是從未有過的 — 這是全世界都希望實現的主要業務需求之一。
本文開啟了一個嶄新的系列,將深入討論 Java開發2.0。您將了解以下內容:使用 Amazon EC2 構建和部署 Web 應用程序、使用 Google 的 App Engine、利用 CouchDB(被稱為 Web 的數據庫),以及在短期內以目前為止最低的成本組裝、測試和部署應用程序的工具和技術。
第一站:Google App Engine for Java。我將通過常用的 “Hello World” 方法來介紹這個平臺,然后展示如何使用 Groovy、Java Data Objects (JDO) 和 Eclipse plug-in for Google App Engine 來創建一個有效的 Web 應用程序。但是,在此之前,讓我們先快速了解一下 Java開發2.0 的商業價值。
速度快成本低
快速和便宜以前很少會和 Java 開發聯系在一起。事實上,它們常常讓人聯想到不太嚴肅的軟件開發 — 只有有限資源的小型企業進行的開發。然而,事實的真相就是,IT 對于許多公司(不論大公司還是小公司)都是一個成本中心,這促使企業在最大程度獲取價值的同時降低 IT 成本。
這就是 Java 開發 2.0 發揮作用的舞臺。通過利用開源工具、框架甚至是解決方案,企業可以快速地組裝應用程序,因為企業自身不需要編寫大量代碼。當我在十多年前第一次開始使用 Java 技術進行開發時,可供開發人員選擇的工具和框架非常有限。并且這些有限的工具還不是免費的。您必須購買一個 IDE、一個數據庫、一個對象-關系映射(ORM)框架(最糟的是,可能必須購買一個驅動程序才能與數據庫通信),當然,還需要購買在其上部署應用程序的機器。那么現在呢?我剛剛列出的所有(以及更多)內容都可以免費獲得,并且具有很棒的品質。
此外,通過借用基礎設施(比如 Amazon EC2 或 Google App Engine 提供的基礎設施),您可以以非常低的成本部署應用程序(您以前需要購買所需的基礎設施)。
構建、購買還是借用:這是個新問題
許多企業為運行應用程序,比如數據庫、應用服務器、變更管理系統和缺陷跟蹤工具,準備了一個硬件清單。但是,在現在這個時代,這個清單完全可以扔掉,取而代之的是在其他人的基礎設施上以運行服務的形式使用相同的軟件包。
團隊用來管理開發流程的全部應用程序堆棧都可以外借 — 也就是說,花費少量的費用租用 — 這樣公司就不需要再購買運行應用程序所需的硬件。例如,不需要購買機器來運行變更管理系統(比如 Subversion 或 Git,兩者都是開源的免費產品),團隊可以使用 GitHub 之類的共享變更管理服務。出租 GitHub 的企業引入了硬件資產成本,因此需要向使用 Git 的其他組織收取少量費用(通常為每用戶每月收取)。從其他提供商以服務形式租用軟件的原理可以應用于缺陷跟蹤、測試用例管理和需求管理(比如,通過 Hosted JIRA 或 Pivotal Tracker)。
相同的原理可以應用于運行其他軟件平臺的底層硬件資產(通常為定制的)。企業可以放棄針對特定 Web 應用程序的底層硬件,而傾向于在由 Amazon、Google 或該領域的其他競爭者提供的硬件上運行應用程序。這些企業提供了以不同程度租用 硬件的能力,這足以托管應用程序。并且,這些公司還可以管理可伸縮性、備份甚至安全性。想一下:Amazon 和 Google 許久之前就解決了這些(以及更多)問題,現在它們更加擅長處理并創新高效運行軟件平臺的方面(這是真的,面對事實吧)。
例如,通過使用 Google 的 App Engine,一家 IT 公司就可以降低購買基礎設施以運行所需應用程序的總體成本。并且可以更加快速地部署這些應用程序,因為已經考慮并提出了各種與應用程序部署和管理有關的交叉問題(并且很可能以一種完美的方式)。
快速 和便宜 不再意味著劣質。相反,Java 開發 2.0 是一種戰略性方法,已經設想了一個以質量為重點的可靠流程。
#p#
使用 Google App Engine 減輕負擔
Google App Engine 是一個可以在 Google 的昂貴基礎設施上構建和部署 Java(和 Python)Web 應用程序的真正平臺。無需任何許可費用(當然,除非您選擇在基礎設施上使用的軟件庫要求擁有一個許可)、無需為帶寬或存儲空間支付前期成本。App Engine 基礎設施在最初是完全免費的,直到您達到了一個使用閾值 — 500MB 的存儲空間,引述 Google 的話,“為每個月大約 500 萬的頁面瀏覽提供足夠的 CPU 和帶寬”。可以這樣說,一旦您達到了 Google 開始收費的那個點,您的 Web 應用程序已經很明顯地產生了巨大的通信量(以及利益)。
啟動并運行 App Engine 再簡單不過了。Google 甚至提供了一個 Eclipse 插件,可以為您處理幾乎任何事情。并且該插件包含 “Hello World” servlet 應用程序的基本組件,該應用程序可以幫助您開始了解此平臺。在其最近一篇 developerWorks 文章(“Google App Engine for Java:第 1 部分:運轉起來!” 中,Rick Hightower 向您介紹了部署 Hello World 應用程序(包含屏幕快照)的整個過程。如果您還沒有閱讀 Rick 的文章,那么可以遵循下面的步驟:
- 創建一個 Google App Engine 帳戶(是免費的),方法是在 http://code.google.com/appengine/ 中單擊 Getting Started 下的 Sign up 鏈接。
- 從 http://code.google.com/appengine/downloads.html 下載 Google App Engine plug-in for Eclipse 并安裝它。
- 在 Eclipse 中通過單擊 New Web Application Project 按鈕創建一個新項目;在顯示的對話框中,不要勾選 Use Google Web Toolkit 選項。命名項目和您感興趣的相應的包。
- 在層次結構中選擇項目并單擊 Deploy App Engine Project 按鈕。
- 輸入憑證(在步驟 1 中創建 App Engine 帳戶時使用的內容)。
- 將本地項目與在最初創建 App Engine 帳戶時生成的應用程序 ID 關聯起來。(您最多可擁有 10 個 ID)。
- 單擊 Deploy 按鈕。將看到 Eclipse 控制臺中閃過大量文本(插件在后臺執行大量工作,包括增強那些利用 Google 的出色的數據存儲服務所需的類)。當屏幕穩定后(并且一切工作正常),您應當會看到一條 “Deployment completed successfully” 消息。
- 訪問在 Google 上的 App Engine 帳戶頁面并在 Google 指示板上找到 Versions 鏈接。您將看到自己的已部署的版本及對應的 URL。單擊 URL,然后單擊通向生成的 servlet 的鏈接,您會看到單調但令人欣慰的 “Hello, world” 純文本。
使用 Groovlets 編寫更少的代碼
您已經成功部署了您的第一個 Google App Engine 應用程序,并且沒有編寫一行代碼。事實上,如果計劃利用 App Engine,您總是要編寫一些代碼的 — 但要記住,您可以重用已有的大量代碼來更加輕松地 完成工作。這些可重用代碼可能是 Google 提供的一些服務(比如其數據存儲或 Google 帳戶服務)或被移植到 Google 基礎設施上的開源庫。重用其他人的代碼意味著您常常只需編寫更少的代碼 — 而更少的代碼意味著更少的缺陷。
我最喜歡的開源庫(以及平臺)之一就是 Groovy,它總是可以生成更少的代碼行來創建有效的應用程序。Groovy 團隊最近發布了可以使用 App Engine 的平臺版本,使您能夠利用 Groovlets 而不是 servlets 來在短期內創建一個有效的應用程序。Groovlets 是一些在行為上類似 servlets 的簡單 Groovy 腳本。由于您已經實現了一個可以輸出 “Hello, world” 的 servlet,因此我將展示使用 Groovlet 完成同樣的事情是多么地簡單(您將看到 Groovy 可以減少多少代碼)。
使用 Eclipse 插件在 App Engine 上部署 Groovlet 只需要很簡單的一些步驟:
- 從 http://groovy.codehaus.org/Download 下載 Groovy 的最新模板(撰寫本文時為 1.6.3 版本)。
- 找到 groovy-all-1.6.3.jar 并將它放到您的 App Engine 項目的 war/WEB-INF/lib 目錄中。順便說一句,在這個目錄中,您可以放置應用程序所需的任何庫(我將在稍后給出一些注意事項)。
- 將清單 1 中的內容(將 Groovlets 映射到指定的請求)添加到 war/WEB-INF 目錄中的 web.xml 文件:
清單 1. 更新 web.xml 文件以支持 Groovlets< servlet> < servlet-name>GroovyServlet< /servlet-name> < servlet-class>groovy.servlet.GroovyServlet< /servlet-class> < /servlet> < servlet-mapping> < servlet-name>GroovyServlet< /servlet-name> < url-pattern>*.groovy< /url-pattern> < /servlet-mapping>
- 將 groovy 目錄添加到 WEB-INF 目錄;這是保存 Groovlets 的位置。在 groovy 目錄中,創建一個名為 helloworld.groovy 的新文件。在這個新文件中,輸入
println "Hello, Groovy baby!"
- 更新應用程序的版本(假設 1-1)并重新部署。通過 Google 指示板找到相應的 URL,然后在瀏覽器中打開 /helloworld.groovy,會看到 Groovy 在 Google 的基礎設施上輸出了一條 hip 消息。
非常簡單,不是嗎?您只需要添加 Groovy JAR、更新 web.xml 文件、創建一個新的 groovy 目錄、編寫一個 Groovlet 然后部署它。您是否還注意到 Groovlet 如何只使用一行代碼 完成與默認 servlet 插件的工作?您希望編寫并維護哪一個:一個龐大的類或是具有相同行為的很小的類?
Groovy + Java = 快速構建有效的應用程序
現在,我將展示如何結合使用 Groovy 和 Google 的 App Engine 快速創建一個有效的應用程序。我將使用一個簡單的 HTTP 頁面、一個 Groovlet 以及一個增強了 JDO 的 Java 類來持久化事件(在本例中為 triathlon)。我將在這里保持簡單性,但是您將會看到這個應用程序可以不斷演變來包括其他特性,并且在本系列后續文章中,您將實現這些特性(當然,使用不同的基礎設施和技術)。
快速 JDO
Google App Engine 提供了使用 JDO 持久化數據的能力,JDO 是一個 Java 持久化標準。對于大部分 Java 開發人員來說,持久化數據常常意味著將信息保存到一個關系數據庫中;然而,對于 Google 來講,底層存儲機制就是它的 Big Table,而后者并不是關系型的。也就是說,這一點無關緊要:Google 如何持久化特定屬性的細節在很大程度上已經被隱藏。可以這樣說,您可以使用普通的 Java 對象(或 Groovy 對象,就本文而言)來構建一個應用程序,這個應用程序可以像任何其他應用程序那樣存儲信息。這就是 Google 的方法,您必須使用 JDO。(Hibernate 無疑是面向 Java 的最流行的 ORM 框架,但它并不能用于 App Engine)。
JDO 非常簡單。您將創建 POJO — 老式普通 Java 對象(可以和其他 Java 對象建立聯系),您通過類級別的 @PersistenceCapable
注釋將其聲明為具有持久能力。通過 @Persistent
注釋指定要進行持久化的對象的屬性。例如,我希望存儲 triathlon 事件(目前而言,我將關注事件而不是與 triathlon 有關的各種結果)— 就是說,事件擁有一個名稱(triathlon 的名稱),可能還有一個描述(triathlon 的類型)和一個日期。目前為止,我的 JDO 看上去類似清單 2:
import java.util.Date; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.IdentityType; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Triathlon { @Persistent private Date date; @Persistent private String name; @Persistent private String description; } |
無論使用哪一種底層機制(即關系型或 Google 的 Big Table),數據持久性始終需要涉及鍵(key)的概念:一種為了避免數據崩潰而確保數據的不同方面具有惟一性的方法。例如,對于 triathlon,它的鍵可以是 triathlon 的名稱。如果兩個 triathlon 擁有相同的名稱,那么可以將名稱和日期組合起來作為鍵。不管您使用何種方式通過 Google App Engine 和 JDO 表示鍵,必須通過 @PrimaryKey
注釋在 JDO 對象中指定一個鍵。您還可以為鍵的生成方式選擇一些策略 — 由您或 Google 生成。我將使用 Google 生成并保持簡單性:我的 triathlon 對象的鍵被表示為一個普通的 Java Long
對象,并且我將通過指定一個值策略 來讓 Google 確定實際的值。清單 3 添加了一個主鍵:
import java.util.Date; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; import javax.jdo.annotations.IdentityType; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ReflectionToStringBuilder; @PersistenceCapable(identityType = IdentityType.APPLICATION) public class Triathlon { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id; @Persistent private Date date; @Persistent private String name; @Persistent private String description; public Triathlon(Date date, String name, String description) { super(); this.date = date; this.name = name; this.description = description; } //...setters and getters left out public String toString() { return ReflectionToStringBuilder.toString(this); } public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } } |
如清單 3 所示,我的 triathlon JDO 擁有一個由 Google 基礎設施管理的鍵,并且添加了一些標準的方法(toString
、hashCode
和 equals
),為調試、登錄以及適當的功能提供了極大的幫助。我并沒有親自編寫這些內容,相反,我使用了 Apache commons-lang 庫。我還添加了一個構造函數,與調用大量 setter 方法相比,這個構造函數可以更加輕松地創建完全初始化的對象。
我有意維持了 JDO 的簡單性,但是正如您所見,并沒有包含多少內容(就是說,為了保持簡單性,我去掉了所有的關系并忽略了 getter 和 setter 方法)。您只需對域進行建模并隨后使用一些注釋來修飾模型,然后剩下的工作就由 Google 來完成。
將對象定義為具有持久性后,還剩下最后一個步驟。要與底層的數據存儲交互,需要使用 PersistenceManager
,這是一個 JDO 標準類,顧名思義,它的作用就是在一個底層數據存儲中保存、更新、檢索和刪除對象(非常類似于 Hibernate 的 Session
對象)。這個類通過一個工廠(PersistenceManagerFactory
)創建,這個工廠非常復雜;因此,Google 建議創建一個獨立的對象來管理工廠的單個實例(后者在您需要時返回一個合適的 PersistenceManager
)。相應地,我可以定義一個簡單的獨立對象來返回 PersistenceManager
的實例,如清單 4 所示:
PersistenceManager
實例的簡單獨立對象
import javax.jdo.JDOHelper; import javax.jdo.PersistenceManager; import javax.jdo.PersistenceManagerFactory; public class PersistenceMgr { private static final PersistenceManagerFactory instance = JDOHelper.getPersistenceManagerFactory("transactions-optional"); private PersistenceMgr() {} public static PersistenceManager manufacture() { return instance.getPersistenceManager(); } } |
可以看到,我的 PersistenceMgr
非常的簡單。manufacture
方法從 PersistenceManagerFactory
的單個實例返回一個 PersistenceManager
實例。您還會注意到,清單 4 中沒有出現任何特定于 Google 的代碼或任何其他利用 JDO 的代碼 — 所有引用都是指向標準 JDO 類和接口的。
新添加的兩個 Java 對象位于我的項目的 src 目錄中,并且我將 commons-lang 庫添加到了 war/WEB-INF/lib 目錄中。
利用定義好的簡單 triathlon JDO POJO 和方便的 PersistenceMgr
對象,我已經有了很好的起點。我所需要的就是能夠捕獲 triathlon 信息。
#p#
大多數 Web 應用程序都遵循相同的模式:通過 HTML 表單捕捉信息,然后將它們提交到服務器端資源以進行處理。當然,這一過程中還混合了許多其他技術,但是不管底層技術或基礎設施如何,模式始終保持不變。Google App Engine 也是如此 — 我已經編碼了服務器端資源來處理保存的 triathlon 數據。剩下的工作就是捕捉信息 — 表單 — 以及將服務器端與表單連接起來。按 Model-View-Controller (MVC) 的話說,我需要一個控制器(通常為一個 servlet);我將利用 Groovlet 替代,因為我希望編寫更少的代碼。
我的 HTML 表單非常簡單:我所需做的就是創建一個 HTML 頁面,利用某些簡單的 Cascading Style Sheets (CSS) 代碼來創建表單,如圖 1 所示,看上去更接近 Web 2.0,而不是 1998 年出現的 HTML 頁面:
可以從圖 1 中看到,表單捕捉到一個名稱、描述和一個日期。然而,日期并不簡單 — 它實際上是一個日期的三個屬性。
快速 Groovlet
Groovlets 使得編寫控制器變得非常簡單:它們需要更少的代碼并自動提供了所需的對象。在 Groovlet 中,您分別通過 request
和 response
對象隱式地訪問 HTML 請求和響應。在我的 Groovlet 中,我可以通過 request.getParameter("name")
調用獲得提交的 HTML 表單的所有屬性,如清單 5 所示:
def triname = request.getParameter("tri_name") def tridesc = request.getParameter("tri_description") def month = request.getParameter("tri_month") def day = request.getParameter("tri_day") def year = request.getParameter("tri_year") |
前面編寫的 JDO 使用了一個 Java Date
對象;然而,在清單 5 中,我處理了 Date
的三個不同屬性。因此我需要一個 DateFormat
對象來將 month
、day
、year
三者的組合轉換為一個普通的 Java Date
,如清單 6 所示:
def formatter = new SimpleDateFormat("MM/dd/yyyy") def tridate = formatter.parse("${month}/${day}/${year}") |
最后,從已提交 HTML 表單獲得所有參數后,我可以使用清單 7 的代碼,通過我的 JDO 和清單4的 PersistenceMgr
對象將它們持久化到 Google 的基礎設施中:
def triathlon = new Triathlon(tridate, triname, tridesc) def mgr = PersistenceMgr.manufacture() try { mgr.makePersistent(triathlon) } finally { mgr.close() } |
就是這么簡單!當然,隨著更多的頁面加入到我的簡單應用程序中(比如捕捉特定 triathlon 的結果),我可能需要轉發或重定向到另一個表單,這將捕捉額外的信息,與向導十分類似。不管怎樣,通過一些簡短的代碼片段,我快速組合了一個簡單的 Web 應用程序,它可以通過 JDO(使用普通 Java 編碼)和一個 Groovlet(當然,使用 Groovy 編碼)將數據持久化到 Google 的基礎設施中。部署應用程序非常簡單,只需在 appengine-web.xml 文件中指定一個版本并單擊 Deploy 按鈕。
但是,這個用于捕捉 triathlon 事件的只包含一個表單的 Web 應用程序并沒有試圖實現全部的功能,所以說,我僅僅是將應用程序部署到一個不規則的、普遍存在的環境中。我不需要觸發一個 Web 容器甚至指定在哪里 部署應用程序。(它位于 California、我的硬盤或者是月球上?)妙處在于這并不重要 — Google 負責處理這個問題。注意,是解決所有問題。此外,可以肯定的是,Google 已經知道如何進行全球性擴展,這樣位于印度的用戶在查看應用程序時會擁有和阿根廷用戶相同的體驗。
綜上所述,您的確有必要牢記一些東西。Google 的基礎設施支持 Java 技術,但是并不意味著所有內容;如果您回憶一下多年前 J2ME 問世的情景,那么 App Engine 的限制可能在本質上有些類似。也就是說,并非所有核心 Java 庫和相關開源庫都受支持。如前所述,Hibernate 就不受支持(主要是因為使用 App Engine 時,您無法擁有關系數據庫)。我在使用某些內置了 base64 編碼的開源庫時還遇到了一些挑戰(Google 要求您使用它的 URL Fetch 服務)。App Engine 是一個平臺 — 您必須以它為方向進行開發,就目前而言,這是一個單向的過程。
結束語
面向對象編程的創始人之一 Alan Kay 曾經這樣說道,“預測未來的最佳方式就是實現它”。我同意 Alan Kay 的這個說法。不管其他人如何預測 Java 技術的未來,我認為未來已經在您的面前。
正如您在本文中了解的那樣,Google App Engine 正是面向未來的一個平臺 — 假設您在其沙盒中試用。(注意,我只介紹了沙盒中的部分特性;App Engine 具有大量特性)。如果您希望獲得更多的靈活性(就是說,希望擁有一個關系數據庫并且必須使用 Hibernate),但是也希望借用其他人的可擴展基礎設施,那么可以使用替代解決方案。Amazon 的 EC2 實際上就是位于按需使用的混合基礎設施上的虛擬服務器。您將在下個月的 Java 開發 2.0 中了解它。
【編輯推薦】