歡迎您光臨本站 註冊首頁

實戰 Groovy: Groovy:Java 程序員的 DSL

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
Groovy 專家 Scott Davis 將重新開始撰寫 實戰 Groovy 系列文章,該系列文章於 2006 年停止編寫。作為開篇文章,本文將介紹 Groovy 最近的發展以及 Groovy 當前的狀態。然後了解大約 從 2009 年開始,使用 Groovy 是多麼輕鬆。

Andrew Glover 於 2004 年開始為 developerWorks 撰寫關於 Groovy 的文章,他先撰寫了 alt.lang.jre 系列中的介紹性文章 “alt.lang.jre: 感受 Groovy”,又繼續撰寫了長期刊發的 實戰 Groovy 系列。發表這些文章時市場上還沒有出現關於 Groovy 的書籍(現在這樣的書籍超過十幾本),而且 Groovy 1.0 在幾年後才於 2007 年 1 月發布。自 2006 年末發布 實戰 Groovy 的最後一期后,Groovy 發生了很大的變化。

現在,Groovy 每個月的平均下載數量大約為 35,000。Mutual of Omaha 等保守的公司擁有超過 70,000 行 Groovy 生產代碼。Groovy 在 Codehaus.org 中有一個最忙碌的郵件列表,這是託管該項目的位置(請參閱 參考資料)。Grails 是惟一一個擁有較多下載數量以及繁忙郵件列表的項目,它是在 Groovy 中實現的流行 Web 框架(請參閱 參考資料)。

在 JVM 中運行非 Java™ 語言不僅常見,而且也是 Sun 的 JVM 策略的核心部分。Groovy 加入進了 Sun 支持的備選語言(如 JavaScript, JavaFX, JRuby 和 Jython)行列中。2004 年所做的實驗現在成為了最前沿的技術。

2009 年撰寫的關於 Groovy 的文章在許多方面與 Andy 開始撰寫的文章相同。2005 年確立的語法仍然保留至今。每個發行版都添加了引人注目的新功能,但是對於項目主管來說,保留向後兼容性是極為重要的。這項可靠的基礎使得 Java 開發組織在其應用程序進入生產環境並開始依賴各項技術時,毫不猶豫地選擇了 Groovy。

本文的目標是使經驗豐富的 Java 開發人員可以像 Groovy 開發人員一樣進行快速開發。不要被它的表面所矇騙。本系列如名稱所示全部都是實際使用的 Groovy 實踐知識。在最開始編寫完 “Hello, World” 之後,請準備好儘管掌握實際的應用。

關於本系列

Groovy 是運行在 Java 平台上的現代編程語言。它將提供與現有 Java 代碼的無縫集成,同時引入閉包和元編程等出色的新功能。簡言之,Groovy 是 21 世紀根據 Java 語言的需要編寫的。

把任意一個新工具集成到開發工具包中的關鍵是:知道何時使用以及何時不應使用該工具。Groovy 可以提供強大的功能,但是必須正確地應用到適當的場景中。為此,實戰 Groovy 系列將探究 Groovy 的實際應用,幫助您了解何時及如何成功應用它們。

安裝 Groovy

如果您以前從未使用過 Groovy,則首先需要安裝 Groovy。安裝步驟非常簡單,這些步驟與安裝 Ant 和 Tomcat 等常見 Java 應用程序甚至安裝 Java 平台本身的步驟相同:

  1. 下載 最新的 Groovy ZIP 文件或 tarball。
  2. 將存檔解壓縮到所選目錄中(您應當避免在目錄名稱中使用空格)。
  3. 創建 GROOVY_HOME 環境變數。
  4. 把 GROOVY_HOME/bin 添加到 PATH 中。

Groovy 運行在 Java 5 或 6 上的效果最佳。在命令提示中輸入 java -version 以確認您使用的是最新版本。然後鍵入 groovy -version 以確保 Groovy 已正確安裝。

所有主要 IDE(Eclipse、IntelliJ 和 NetBeans)都有支持自動完成和分步調試等功能的 Groovy 插件。雖然擁有一個優秀的 IDE 對於目前編寫 Java 代碼來說幾乎成為了一項必要要求,但是對於 Groovy 來說並非絕對。得益於 Groovy 語言的簡明性,許多人都選擇使用簡單的文本編輯器編寫。vi 和 Emacs 等常見開源編輯器都提供 Groovy 支持,Textpad(面向 Windows®)和 TextMate(面向 Mac OS X)等便宜的商業文本編輯器也提供 Groovy 支持(有關更多信息,請參閱 參考資料)。

您稍後將在本文中看到,將 Groovy 與現有 Java 項目集成起來十分簡單。您只需將一個 Groovy JAR 從 GROOVY_HOME/embeddable 添加到類路徑中並把現有的 javac Ant 任務封裝到 groovyc 任務中(Maven 提供類似的支持)。

但是在掌握這些知識之前,我將從必修的 “Hello World” 示例開始。





進入 Groovy 世界

您知道 “Hello World” 示例應當會演示哪些內容 — 它是用給定語言可以編寫的最簡單的程序。清單 1 中所示的 “Hello World” Java 代碼的有趣之處在於,需要了解中間語言的知識才能完全了解代碼含義:


清單 1. 用 Java 代碼編寫的 “Hello World” 示例
				  public class HelloJavaWorld{    public static void main(String[] args){      System.out.println("Hello Java World");    }  }  

首先創建名為 HelloJavaWorld.java 的文件並輸入 public class HelloJavaWorld。許多剛開始使用 Java 的開發人員學到的第一課是如果類名與文件名不完全匹配(包括大小寫),則類無法編譯。另外,好奇的學生將在此時開始詢問關於 public 和 private 之類的訪問修飾符。

下一行 — public static void main(String[] args) — 通常將引發關於實現細節的一連串問題:什麼是 static?什麼是 void?為什麼需要將方法命名為 main?什麼是 String 數組?而最後,嘗試向剛開始使用 Java 的開發人員說明 out 是 System 類中的 PrintStream 對象的 public、static、final 實例。我永遠也忘不了學生說 “天哪!其實我只是想輸出 ‘Hello’”。

將此示例與用 Groovy 編寫的 “Hello World” 進行對照。創建名為 HelloGroovyWorld.groovy 的文件並輸入清單 2 中所示的代碼行:


清單 2. 用 Groovy 代碼編寫的 “Hello World” 示例
				  println "Hello Groovy World"  

是的,這段代碼是與清單 1 中所示的 Java 示例等效的 Groovy 代碼。在本例中,所有實現細節 — 並不立即解決手頭問題的 “知識” — 都隱藏在後台,只顯示簡單輸出 “Hello” 的代碼。輸入 groovy HelloGroovyWorld 以確認它可以工作。

這個小示例將演示 Groovy 的雙重價值:它將顯著地減少需要編寫的代碼行數,同時保留 Java 等效代碼的語義。在下一節中,您將進一步探究這種理念。





深入研究 Hello World

有經驗的 Java 開發人員都知道在 JVM 中運行代碼之前必須先編譯這些代碼。但是,Groovy 腳本在任何位置都不顯示為類文件。這是否意味著可以直接執行 Groovy 源代碼?答案是 “不一定,但是它看上去是這樣,對不對?”

Groovy 解釋器將先在內存中編譯源代碼,然後再將其轉到 JVM 中。您可以通過輸入 groovyc HelloGroovyWorld.groovy 手動執行此步驟。但是,如果嘗試使用 java 運行得到的類,將顯示清單 3 中所示的異常:


清單 3. 嘗試在 CLASSPATH 中沒有 Groovy JAR 的情況下運行經過編譯的 Groovy 類
				  $ java HelloGroovyWorld  Exception in thread "main" java.lang.NoClassDefFoundError: groovy/lang/Script  

如前述,Groovy JAR 必須包含在 CLASSPATH 中。再次嘗試執行代碼,這一次把 -classpath 實參傳遞到 java 中,如清單 4 所示:


清單 4. 用 java 命令成功運行經過編譯的 Groovy 類
				  //For UNIX, Linux, and Mac OS X  $ java -classpath $GROOVY_HOME/embeddable/groovy-all-x.y.z.jar:. HelloGroovyWorld  Hello Groovy World    //For Windows  $ java -classpath %GROOVY_HOME%/embeddable/groovy-all-x.y.z.jar;. HelloGroovyWorld  Hello Groovy World  

現在您已經取得了一些進展。但是為了證明 Groovy 腳本真正地保留 Java 示例的語義,您需要深入鑽研位元組碼。首先,輸入 javap HelloJavaWorld,如清單 5 所示:


清單 5. 解釋 Java 位元組碼
				  $ javap HelloJavaWorld  Compiled from "HelloJavaWorld.java"  public class HelloJavaWorld extends java.lang.Object{    public HelloJavaWorld();    public static void main(java.lang.String[]);  }  

除了為您添加了 javac 編譯器之外,這段代碼中不應當有過多令人驚訝之處。您無需顯式輸入 extends java.lang.Object 或提供類的默認構造函數。

現在,輸入 javap HelloGroovyWorld,如清單 6 所示:


清單 6. 解釋 Groovy 位元組碼
				  $ javap HelloGroovyWorld  Compiled from "HelloGroovyWorld.groovy"  public class HelloGroovyWorld extends groovy.lang.Script{    ...    public static void main(java.lang.String[]);    ...  }  

什麼是 DSL?

Martin Fowler 普及了特定於領域語言的理念(請參閱 參考資料)。他把 DSL 定義為 “側重特定領域的表達有限的計算機編程語言”。“有限的表達” 並不是指語言的用途有限,只是表示這種語言提供了足夠用於適當表達 “特定領域” 的辭彙表。DSL 是一種很小的專用語言,這與 Java 語言等大型通用語言形成對比。

SQL 就是一種優秀的 DSL。您無法使用 SQL 編寫操作系統,但它是處理關係資料庫這一有限領域的理想選擇。在同樣意義上,Groovy 是 Java 平台的 DSL,因為它是有限領域的 Java 開發的理想選擇。我在這裡使用 DSL 是為了啟發讀者,並不是特別的精確。如果我把 Groovy 稱為 常用 Java 語言的內部 DSL,可能更容易被 DSL 純粹主義者接受。

Dave Thomas 進一步闡明 DSL 的概念(請參閱 參考資料)。他寫道,“無論領域專家在何時交流……他們都在說行業術語,這是他們為與同行進行有效交流而創造出的更簡略的專用語言”。可能將 Groovy 視為 “簡略的 Java 語言” 更能說明 Groovy 與 Java 語言之間的關係。本文的下一節將提供另一個示例。

在這裡,您可以看到 groovyc 編譯器將獲取源文件的名稱並創建了一個同名的類(該類擴展 groovy.lang.Script 而非 java.lang.Object,這應當能幫助您理解嘗試在 CLASSPATH 中沒有 Groovy JAR 的情況下運行文件拋出 NoClassDefFoundError 異常的原因)。在所有其他編譯器提供的方法之中,您應當能夠找到一種優秀的舊 public static void main(String[] args) 方法。groovyc 編譯器把腳本行封裝到此方法中以保留 Java 語義。這意味著在使用 Groovy 時可以利用所有的現有 Java 知識。

例如,下面是在 Groovy 腳本中接受命令行輸入的方法。創建名為 Hello.groovy 的新文件並添加清單 7 中的代碼行:


清單 7. 接受命令行輸入的 Groovy 腳本
				  println "Hello, " + args[0]  

現在在命令行中輸入 groovy Hello Jane。args String 數組就在這裡,就像任何一位 Java 開發人員期望的那樣。在這裡使用 args 對於新手可能沒意義,但是它對於經驗豐富的 Java 開發人員意義非凡。

Groovy 將把 Java 代碼縮減為基本要素。您剛剛編寫的 Groovy 腳本幾乎和可執行的偽代碼一樣。表面上,該腳本簡單得足以讓新手能夠理解,但是對於經驗豐富的開發人員,它沒有去掉 Java 語言的底層強大功能。這就是我將 Groovy 視為 Java 平台的特定於領域語言(DSL)的原因(請參閱 什麼是 DSL? 側欄)。





普通的舊 Groovy 對象

JavaBean — 或更通俗的名稱,普通的舊 Java 對象(Plain Old Java Object,POJO)— 是 Java 開發的主要支柱。在創建 POJO 以表示域對象時,您應當遵循定義好的一組期望。類應當為 public,並且欄位應當為帶有一組對應的 public getter 和 setter 方法的 private。清單 8 顯示了一個典型的 Java POJO:


清單 8. Java POJO
				  public class JavaPerson{    private String firstName;    private String lastName;      public String getFirstName(){ return firstName; }    public void setFirstName(String firstName){ this.firstName = firstName; }      public String getLastName(){ return lastName; }    public void setLastName(String lastName){ this.lastName = lastName; }  }  

普通的舊 Groovy 對象(Plain Old Groovy Object,POGO)是 POJO 的簡化的替代者。它們完全保留了 POJO 的語義,同時顯著減少了需要編寫的代碼量。清單 9 顯示了用 Groovy 編寫的 “簡易” person 類:


清單 9. Groovy POGO
				  class GroovyPerson{    String firstName    String lastName  }  

除非您另外指定,否則 Groovy 中的所有類都是 public 的。所有屬性都是 private 的,而所有方法都是 public 的。編譯器將為每個屬性自動提供一組 public getter 和 setter 方法。用 javac 編譯 JavaPerson 並用 groovyc 編譯 GroovyPerson。現在通過 javap 運行它們以確認該 Groovy 示例擁有 Java 示例所擁有的所有內容,甚至可以擴展 java.lang.Object(在先前的 HelloGroovyWorld 示例中未指定類,因此 Groovy 轉而創建了擴展 groovy.lang.Script 的類)。

所有這些意味著您可以立即開始使用 POGO 作為 POJO 的替代選擇。Groovy 類是只剩下基本元素的 Java 類。在編譯了 Groovy 類之後,其他 Java 類可以輕鬆地使用它,就好像它是用 Java 代碼編寫的。為了證明這一點,請創建一個名為 JavaTest.java 的文件並添加清單 10 中的代碼:


清單 10. 從 Java 代碼中調用 Groovy 類
				  public class JavaTest{    public static void main(String[] args){      JavaPerson jp = new JavaPerson();      jp.setFirstName("John");      jp.setLastName("Doe");      System.out.println("Hello " + jp.getFirstName());        GroovyPerson gp = new GroovyPerson();      gp.setFirstName("Jane");      gp.setLastName("Smith");      System.out.println("Hello " + gp.getFirstName());    }  }  

即使 getter 和 setter 不顯示在 Groovy 源代碼中,這項測試也證明了它們是存在於經過編譯的 Groovy 類中並且完全可以正常運行。但是如果我不向您展示 Groovy 中的相應測試,則這個示例不算完整。創建一個名為 TestGroovy.groovy 的文件並添加清單 11 中的代碼:


清單 11. 從 Groovy 中調用 Java 類
				  JavaPerson jp = new JavaPerson(firstName:"John", lastName:"Doe")  println "Greetings, " + jp.getFirstName() + ".      It is a pleasure to make your acquaintance."    GroovyPerson gp = new GroovyPerson(lastName:"Smith", firstName:"Jane")  println "Howdy, ${gp.firstName}. How the heck are you?"  

您首先可能會注意到,Groovy 提供的新構造函數允許您命名欄位並按照所需順序指定這些欄位。甚至更有趣的是,可以在 Java 類或 Groovy 類中使用此構造函數。這怎麼可能?事實上,Groovy 先調用默認的無實參構造函數,然後調用每個欄位的相應 setter。您可以模擬 Java 語言中的類似行為,但是由於 Java 語言缺少命名實參並且兩個欄位都是 Strings,因此您無法按照任何順序傳入名字和姓氏欄位。

接下來,注意 Groovy 支持傳統的 Java 方法來執行 String 串聯,以及在 String 中直接嵌入用 ${} 圈起的代碼的 Groovy 方法(這些稱為 GString,它是 Groovy Strings 的簡寫)。

最後,在類中調用 getter 時,您將看到使用 Groovy 語法的更多優點。您無需使用更冗長的 gp.getFirstName(),只需調用 gp.firstName。看上去您是在直接訪問欄位,但是事實上您在調用後台的相應的 getter 方法。Setter 將按照相同的方法工作:gp.setLastName("Jones") 和 gp.lastName = "Jones" 是等效的,後者將在後台調用前者。

我希望您也能夠承認,在任何情況下,Groovy 都類似於簡易版的 Java 語言 — “領域專家” 可能使用 Groovy “與同行進行有效地交流”,或者類似於老朋友之間隨意的談笑。





歸根結底,Groovy 就是 Java 代碼

Groovy 最被低估的一個方面是它完全支持 Java 語法的事實。如前述,在使用 Groovy 時,您不必放棄一部分 Java 知識。在開始使用 Groovy 時,大部分代碼最終看上去都像是傳統的 Java 代碼。但是隨著您越來越熟悉新語法,代碼將逐漸發展為包含更簡明、更有表現力的 Groovy 風格。

要證明 Groovy 代碼可以看上去與 Java 代碼完全相同,請將 JavaTest.java 複製到 JavaTestInGroovy.groovy 中,然後輸入 groovy JavaTestInGroovy。您應當會看到同樣的輸出,但是請注意,您無需在運行前先編譯 Groovy 類。

此次演示應當使經驗豐富的 Java 開發人員能夠不假思索地選擇 Groovy。由於 Java 語法也是有效的 Groovy 語法,因此最開始的學習曲線實際上是不存在的。您可以將現有的 Java 版本與 Groovy、現有的 IDE 以及現有的生產環境結合使用。這意味著對您日常工作的干擾非常少。您只需確保 Groovy JAR 位於 CLASSPATH 中並調整構建腳本,以便 Groovy 類與 Java 類同時編譯。下一節將向您展示如何向 Ant build.xml 文件中添加 groovyc 任務。





用 Ant 編譯 Groovy 代碼

如果 javac 是可插拔的編譯器,則可以指示它同時編譯 Groovy 和 Java 文件。但是它不是,因此您只需用 groovyc 任務在 Ant 中封裝 javac 任務。這將允許 groovyc 編譯 Groovy 源代碼,並允許 javac 編譯 Java 源代碼。

當然,groovyc 既可以編譯 Java 文件,又可以編譯 Groovy 文件,但是還記得 groovyc 添加到 HelloGroovyWorld 和 GroovyPerson 中的額外的方便方法么?這些額外的方法也將被添加到 Java 類中。最好的方法可能就是讓 groovyc 編譯 Groovy 文件,而讓 javac 編譯 Java 文件。

要從 Ant 中調用 groovyc,請使用 taskdef 定義任務,然後像平時使用 javac 任務一樣使用 groovyc 任務(有關更多信息,請參閱 參考資料)。清單 12 顯示了 Ant 構建腳本:


清單 12. 用 Ant 編譯 Groovy 和 Java 代碼
				  <taskdef name="groovyc"           classname="org.codehaus.groovy.ant.Groovyc"           classpathref="my.classpath"/>    <groovyc srcdir="${testSourceDirectory}" destdir="${testClassesDirectory}">   <classpath>     <pathelement path="${mainClassesDirectory}"/>     <pathelement path="${testClassesDirectory}"/>     <path refid="testPath"/>   </classpath>   <javac debug="on" />  </groovyc>  

順便說一下,包含 ${} 的 String 看上去疑似 GString,是不是?Groovy 是一種優秀的語言,它從各種其他語言和庫中借鑒了語法和功能。您經常會覺得似乎在其他語言中見過類似的特性。





結束語

這是一次 Groovy 旋風之旅。您了解了一些關於 Groovy 曾經的地位及其目前現狀的信息。您在系統中安裝了 Groovy,並且通過一些簡單的示例,了解了 Groovy 為 Java 開發人員提供的強大功能。

Groovy 不是運行在 JVM 上的惟一的備選語言。JRuby 是了解 Ruby 的 Java 開發人員的優秀解決方案。Jython 是了解 Python 的 Java 開發人員的優秀解決方案。但是正如您所見,Groovy 是了解 Java 語言的 Java 開發人員的優秀解決方案。Groovy 提供了類似 Java 的簡明語法,同時保留了 Java 語義,這一點非常引人注目。而且,採用新語言的路徑不包含 del *.* 或 rm -Rf * 是一次很好的變革,您不這樣認為嗎?(責任編輯:A6)



[火星人 ] 實戰 Groovy: Groovy:Java 程序員的 DSL已經有886次圍觀

http://coctec.com/docs/linux/show-post-68955.html