在本文中將會涉及到:
使用 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 為我們提供了 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 運行結果