標籤:
在本文中將會涉及到:
- 使用 CliBuilder 來實現對命令列選項的支援,指令碼執行時所需要的參數將通過命令列選項的方式傳遞。
- 使用 GroovyClassLoader 載入 Groovy class。
- 使用 AntBuilder 來構建 Jar 包。
開始之前關於本文
也許您寫了一些有趣或實用的 Groovy 指令碼並希望與您的朋友分享,可是您並不想強迫他們安裝 Groovy,所以您也許想要做一個小工具讓您的朋友們能夠用它編譯您的 Groovy 指令碼並且產生一個可執行檔 Jar 包。本文將介紹如何製作一個小工具讓您的 Groovy 指令碼能夠在沒有安裝 Groovy 的環境下也能被即時編譯和打包成為可執行檔 Jar,並通過此過程介紹 Groovy 的一些有趣的特性。
在本文中,我們將設計並實現一個命令列工具,並通過它來完成對 Groovy 指令碼的即時編譯和打包。我們將使用 CliBuilder 來實現程式對命令列的處理;使用 AntBuilder 來處理打包的問題;使用 FileSystemCompiler 類來實現對 Groovy 指令檔的編譯。
目標
通過本樣本瞭解 Groovy 中 CliBuilder 的使用方法,以及 AntBuilder 在 Groovy 中的應用。
先決條件
- Eclipse IDE
- Groovy plugin for Eclipse
- Apache Ant Java library(您可以在這裡找到 http://ant.apache.org/bindownload.cgi)
系統要求
由於 Eclipse IDE 和 Groovy 語言都是跨平台的,所以您可以在任何平台上編寫本樣本中的程式,並將它運行在任何平台上。
回頁首
利用 CliBuilder 設計命令列程式
用 Groovy 來編寫指令碼是開發跨平台工具的一個不錯的途徑。隨著指令碼複雜程度的不斷增長,您可能需要在您的指令碼中處理命令列選項。而處理這些選項或參數並且根據情況顯示相應的協助資訊可能會是件麻煩事。 Groovy 捆綁了 Apache Commons CLI 庫作為它的一部分,然而它同時也提供了一個使用起來簡單得多的 CliBuilder。那麼,接下來我們看看該如何使用它。
為程式設定命令列選項清單 1. 建立 CliBuilder 執行個體及定義命令列選項
// 建立 CliBuilder 執行個體,並定義命令列選項 def cmdline = new CliBuilder(usage: ‘GroovyPackman -[chflms] [date] [prefix]‘) cmdline.h( longOpt: ‘help‘, required: false, ‘show usage information‘ ) cmdline.d( longOpt: ‘destfile‘, argName: ‘destfile‘, required: false, args: 1, ‘jar destintation filename‘ ) cmdline.m( longOpt: ‘mainclass‘, argName: ‘mainclass‘, required: true, args: 1, ‘fully qualified main class‘ ) cmdline.c( longOpt: ‘groovyc‘, required: false, ‘Run groovyc‘ ) cmdline.s( longOpt: ‘sourcepath‘, argName: ‘sourcepath‘, required: true, args: 1, ‘fully path to the mainclass. Must be specified with .groovy file path when -c option used.‘)
在以上程式碼片段中,我們首先建立 CliBuilder 的執行個體。CliBuilder 擁有一個叫做 Usage 的屬性,可以用它來顯示程式的使用說明等資訊。在這裡,它作為參數傳遞給建構函式將可以告訴程式的使用者有哪些選項可以使用。接著,我們使用名字為一個字母的方法來定義命令列選項,longOpt 屬性允許選項被指定為 -h 或 --help 均可。argName 屬性用來指定在使用說明中顯示的選項的參數名。而 args 屬性用來指定選項所需的參數個數。required 屬性用來告訴命令列解析器當前選項是否是必須的。函數中最後一個參數用來指定該選項的描述資訊。
Groovy 語言中閉包(closure)是一個非常重要的概念,它更象是一個“代碼塊”或者方法指標,代碼在某處被定義然後在其後的調用處執行。關於閉包本文不多做介紹,更多詳細內容請參考以下文章:
實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
GroovyDoc 中對 with(Object self, Closure closure) 方法的描述
Groovy 為我們提供了 with 方法,它允許閉包被對象的引用本身所調用,這是通過把對象賦值給閉包的 delegate 屬性並且作為參數傳遞給閉包來實現的。使用 with 方法有時可以協助我們減少代碼量,在此我們看看使用 with() 方法定義各項參數與代碼清單 1 所使用的方法有何不同。
清單 2. 使用 with 方法後的代碼
def cmdline = new CliBuilder(usage: ‘GroovyPackman -[chflms] [date] [prefix]‘) // 使用 Object 上的 with 方法省去 cmdline 對象的限定 cmdline.with { h longOpt: ‘help‘, required: false, ‘show usage information‘; d longOpt: ‘destfile‘, argName: ‘destfile‘, optionalArg: true, args: 1, ‘jar destintation filename‘; m longOpt: ‘mainclass‘, argName: ‘mainclass‘, required: true, args: 1, ‘fully name of main class‘; c longOpt: ‘groovyc‘, required: false, ‘Run groovyc‘; s longOpt: ‘sourcepath‘, argName: ‘sourcepath‘, required: true, args: 1, ‘fully path to the mainclass. Must be specified with .groovy file path when -c option used.‘; }
這樣,我們為程式設定了命令列入口。其中:
- 使用“-h “或”--help”來顯示使用協助資訊。
- 使用”-d”或”--destfile”來目標檔案名用來作為產生的 .jar 檔案名稱,這是一個可選項。
- 用”-m”或”--mainclass”來指定主類的全名,未指定”-d”選項時將被作為目標 Jar 包的檔案名稱。
- 通過使用"-c"選項來控制是否執行編譯過程,是一個可選項。
- 當命令列中指定了"-c"時,"-s"或"--sourcepath"需指向要被編譯的指令碼(包)所在的目錄;未指定"-c"時需指向 .class 檔案所在的目錄。
在本文的最後一章中您將看到通過這個命令列工具(在本文中被命名為:GroovyPackman)編譯打包指令碼的執行個體。
解析命令列輸入清單 3. 使用 parse 方法解析命令列選項
def opt = cmdline.parse(args) if (!opt) { return } if (opt.h) { cmdline.usage() return }
Parse() 方法用來解析本程式執行時命令列輸入的參數。通過 Parser 解析命令列參數後,我們可以得到 OptionAccessor 對象的一個執行個體,當輸入的參數不合法時,這個對象為空白。
進一步處理命令列參數
通過 opt 對象我們可以輕鬆的擷取到所有已經在命令列中指定的選項,我們將處理各個選項的值,使其符合我們的要求。
清單 4. 擷取命令列選項並賦值給變數
// 將命令列選項賦值給變數 def mainClass = opt.m def sourcePath = opt.s def destFile = mainClass + ‘.jar‘ if (!(sourcePath.endsWith("\\"))||!(sourcePath.endsWith("/")))( sourcePath = sourcePath + "/" ) if (opt.d) { destFile = opt.d }
回頁首
使用 AntBuilder 進行 Ant 指令碼編程
在處理完程式的命令列選項之後,我們將進入本樣本另一個重點:使用 AntBuilder 實現指令碼的編譯和打包。首先,我們來認識一下產生器。
產生器 (Builder)
產生器 (Builder) 是 Groovy 中的一個特性,它可以很方便地在 Groovy 中構造如 XML 文檔一般的樹形資料結構。而 AntBuilder 就是眾多產生器中的一員,通過它您可以毫不費力地構造 Ant 基於 XML 結構的構建檔案 (build.xml),不需要處理 XML 就可以執行構建。更加令人興奮的是,與以往費力地用複製粘貼來建立 build.xml 檔案不同,您不但可以編寫迴圈、條件陳述式,甚至可以利用物件導向語言的優勢。
以下程式碼片段展示了使用 AntBuilder 的一個非常簡單的例子。在本樣本中,當使用者在命令列指定了 -c 選項時將在命令列視窗輸出其指定需要編譯的 Groovy 指令檔。
清單 5. 建立 AntBuilder 執行個體以及處理編譯對象
def ant = new AntBuilder() if (opt.c) { // 檢查指令檔是否存在 def scriptBase = mainClass.replace( ‘.‘, ‘/‘ ) def scriptFile = new File(sourcePath + scriptBase +‘.groovy‘ ) if (!scriptFile.canRead()) { println "Cannot read script file: ‘${scriptFile}‘" return } ant.echo( "Compiling ${scriptFile}" ) }調用 FileSystemCompiler 即時編譯 Groovy 指令碼
在本樣本中,使用者在命令列中使用 -c 選項指定了有效 Groovy 指令檔之後,程式將對其進行編譯。我們將使用 FileSystemCompiler 類來實現指令檔的編譯,程式碼片段 6 展示了這一過程。
清單 6. 用 GroovyClassLoader 的執行個體實現編譯過程
try{ FileSystemCompiler fc= new FileSystemCompiler() fc.commandLineCompile( [ scriptFile ] as String[] ) } catch (org.codehaus.groovy.control.MultipleCompilationErrorsException e){ println e.getMessage() println "*** Possible solution: Please copy GroovyPackman.jar to dir \"${sourcePath}\" and try again. ***" return }
FileSystemCompiler 類的執行個體相當於是 Groovy 的命令列編譯器 (groovyc)。在 Groovy 中,您可以直接調用 Groovy 編譯器來編譯指令檔,其命令一般為 groovyc *.groovy。這個過程將產生一個或多個 *.class 檔案,這些檔案可以使用 java 命令執行(但在執行 Groovy 產生的 .class 檔案時,需保證 ClassPath 中指向了 Groovy 的庫檔案,例如:goovy-1.x.x.jar 等)。
在安裝有 Groovy 的系統中您也可使用命令 groovy *.groovy,同時完成編譯和運行 Groovy 指令碼。與 groovyc 命令不同的是,groovy 命令不是在檔案系統而是在記憶體中產生 *.class 檔案並立即執行。
使用 Groovy 中的 Ant 任務來進行流量控制
在實現了對 Groovy 指令檔編譯的功能之後,接下來將利用 Groovy 中的 Ant 任務進行打包工作。
清單 7. 通過產生器的文法來構建類似於 XML 的結構
def GROOVY_SUPPORT = (System.getenv(‘GROOVY_SUPPORT‘))? (new File( System.getenv(‘GROOVY_SUPPORT‘))) : "" // 如果系統中安裝了 Groovy,打包時我們將從系統中擷取程式運行所必須的 Jar 包 if (GROOVY_SUPPORT!=""){ if (GROOVY_SUPPORT.canRead()) { // 構建可執行檔 Jar 包並指定 mainclass ant.echo("Processing *.class files in ${sourcePath}...") ant.jar( destfile: destFile, compress: true ) { fileset( dir: sourcePath, includes: ‘**/*.class‘ ) zipgroupfileset( dir: GROOVY_SUPPORT, includes: ‘groovy-all-*.jar‘ ) zipgroupfileset( dir: GROOVY_SUPPORT, includes: ‘commons*.jar‘ ) zipgroupfileset( dir: GROOVY_SUPPORT, includes: ‘ant*.jar‘ ) // 您可以在此添加更多的 Jar 包,這取決於您的需求 manifest { attribute( name: ‘Main-Class‘, value: mainClass ) attribute( name: ‘Class-Path‘, value: ‘.‘ ) } } } } // 如果系統未檢測到 Groovy 環境,打包時將使用 GroovyPackman.jar 中擷取程式運行所必須的 Jar 包 else { ant.echo( "Missing environment variable GROOVY_SUPPORT: ‘${GROOVY_SUPPORT}‘" ) def PACKMAN = URLDecoder.decode( GroovyPackman.class.getProtectionDomain().getCodeSource().getLocation().getFile(), "UTF-8") PACKMAN = PACKMAN.toString().replaceFirst("/", "") ant.echo( "Using Packman: ‘${PACKMAN}‘" ) ant.jar( destfile: destFile, compress: true) { fileset( dir: sourcePath, includes: ‘**/*.class‘ ) zipfileset( excludes: ‘GroovyPackman*.*,org/apache/tools/**,images/**‘, src: PACKMAN) // 您可以根據具體需要增加更多的 Jar manifest { attribute( name: ‘Main-Class‘, value: mainClass ) attribute( name: ‘Class-Path‘, value: ‘.‘ ) } } }
在代碼清單 7 中,首先檢查環境變數 GROOVY_SUPPORT 是否存在,該環境變數通常在安裝 Groovy Eclipse Plugin 之後會被設定,指向包含 Groovy 運行庫檔案所在的目錄。接著再分別針對 GROOVY_SUPPORT 存在與否來完成不同的打包過程。當 GROOVY_SUPPORT 可用時我們將從 GROOVY_SUPPORT 指向的目錄中拾取相關的運行庫(如:groovy-all-*.jar,commons*.jar,ant*.jar 等);而當 GROOVY_SUPPORT 不可用即系統中未安裝 Groovy 時,程式將從自身拾取相應的運行庫並將其與編譯後的指令碼一同打包。
最後,編譯我們的程式
第一步,我們需要在 Eclipse 中編譯本指令檔,產生 *.class。要在 Eclipse 中編譯 Groovy 指令碼,可以通過點擊菜單 Run -> Run As -> Groovy Script。
圖 1. 通過 Eclipse 編譯後產生的 .class 檔案
為了便於分享同時執行起來更加方便,第二步,我們將用 Java 命令執行產生的 .class 檔案並將其自身打包成可執行檔 Jar 包。
清單 8. 運行 GroovyPackman.class 將其自身打包
C:\groovy\GroovyPackman\bin>set GROOVY_SUPPORT= d:\eclipse\plugins\org.codehaus.groovy_1.7.5.20101020-2100-e35-release\lib C:\groovy\GroovyPackman\bin>java -cp .\;%groovy_support%\;%groovy_support%\ groovy-all-1.7.5.jar;%groovy_support%\commons-cli-1.2.jar;%groovy_support%\ant.jar; %groovy_support%\ant-launcher.jar GroovyPackman -m GroovyPackman -s c:\Groovy\GroovyPackman\bin
圖 2. 執行 GroovyPackman.class 將其自身打包
回頁首
看看這個小程式的效果如何即時編譯和打包一組樣本 Groovy 指令碼
在這個例子中,我們使用一個非常簡單的 helloworld 程式來做示範,看看如何用我們剛才製作的小工具來進行編譯和打包。這個 helloworld 程式包含兩個 *.groovy 指令檔,本身為三層目錄結構的包, 3 所示:
圖 3. 示範程式中所包含的兩個 groovy 指令碼
在這裡,我們在命令列中執行以下命令:
清單 9. 測試 GroovyPackman.jar
java -jar GroovyPackman.jar
測試一下之前打包好的小工具能否正常運行。 4 所示:
圖 4. GroovyPackman.jar 在命令列中執行
如命令列中的輸出結果,GroovyPackman.jar 運行正常。接下來開始編譯和打包 hellowworld 樣本程式。
將 GroovyPackman.jar 拷貝到 com.test.helloworld 包所在的根目錄。在命令列中執行以下命令:
清單 10. 運行 GroovyPackman.jar 打包 helloworld 程式
java -jar GroovyPackman.jar -m com.test,helloworld.helloworld -s c:\groovy -c
helloworld 將被自動編譯並打包, 5 所示:
圖 5. Helloworld 程式被打包
從圖 6 中可以看到,*.groovy 指令檔已經被編譯成多個 *.class 檔案。
圖 6. 打包過程中產生的 .class 檔案
從圖 7 中可以看到已經打包好的 helloworld 程式。
圖 7. 打包後的 helloworld 程式
接下來我們在命令列中運行剛剛產生的 helloworld 程式,結果 8 所示:
圖 8. 打包後的 Helloworld 運行結果
參考資料學習
- 想瞭解更多關於在 Groovy 中使用 Ant 的技巧請參考 用 Gant 構建軟體(developerWorks)。
- 您還可以訪問 精通 Groovy(developerWorks)來學習更多更精彩的 Groovy 知識。
- “演化架構和緊急設計:使用 Groovy 構建 DSL”(developerWorks,2010 年 9 月):內部特定領域語言(DSL)是可行的,但是由於 Java 語言的限制性文法使其使用並不靈活。JVM 中其他語言更適合構建它們。這一期的演化架構和緊急設計涵蓋了許多您可以利用的功能,以及在您使用 Groovy 構建內部 DSL 時將要遇到的問題。
- “函數式思維:Groovy 中的函數式特性,第 1 部分”(developerWorks,2011 年 12 月):隨著時間的推移,語言和運行時為我們處理了越來越多瑣碎的細節。函數式語言在這方面體現了它的趨勢,而且現代的動態語言也採用了許多函數式特性,讓開發人員的工作變得更輕鬆。這期文章將介紹 Groovy 中的一些函數式特性,並將展示如何用遞迴隱藏狀態和構建惰性列表。
- “函數式思維:Groovy 中的函數式特性,第 2 部分”(developerWorks,2012 年 2 月):憑藉 Groovy,元編程 (metaprogramming) 和函數式編程形成了一個強有力的組合。瞭解元編程如何支援您為 整數資料類型添加方法,從而使您能夠利用 Groovy 的內建函數式特性。學習如何使用元編程將 Functional Java 架構的豐富函數式特性集無縫地整合到 Groovy 中。
- “函數式思維:Groovy 中的函數式特性,第 3 部分”(developerWorks,2012 年 3 月):現代的動態語言整合了許多函數式特性,以協助開發人員完成平常的任務。本文將使用 Groovy 探索在函數層級應用緩衝的好處,並將這種緩衝方法與一種命令式方法進行對比。本文將示範兩種類型的緩衝,即方法內緩衝和外部緩衝,還將探討命令式和函數式版本的優缺點。
- developerWorks Java 技術專區:這裡有數百篇關於 Java 編程各個方面的文章。
討論
- 加入 developerWorks 中文社區。查看開發人員推動的部落格、論壇、組和維基,並與其他 developerWorks 使用者交流。
http://www.ibm.com/developerworks/cn/java/j-lo-groovypackage/index.html
http://www.ibm.com/developerworks/cn/java/j-pg06239.html
即時編譯和打包您的 Groovy 指令碼(轉)