程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Groovy >> 用Groovy進行Ant腳本編程

用Groovy進行Ant腳本編程

編輯:Groovy

Ant 作為 Java 項目構建工具的普遍性和實用性是無法超越的。即使是 Maven 這個構建領域的新銳工具,也要把自己的許多強大能力歸功於從 Ant 學到的經驗。但是,這兩個工具有共同的不足之處:擴展性。即使 XML 的可移植性在促進 Ant 和 Maven 走向開發前端上扮演了主要角色,XML 作為構建配置格式的角色仍然或多或少地限制了構建過程的表現力。

例如,雖然在 Ant 和 Maven 中都有條件邏輯,但是用 XML 表示有些繁瑣。而且,雖然可以定義定制任務來擴展 Ant 的構建過程,但是這麼做通常會把應用程序的行為局限在有限的沙箱中。因此,在本文中,我將向您演示如何把 Groovy 和 Ant 在 Maven 內部 結合起來,形成更強大的表現力,從而對構建過程進行更好的行為控制。

很快您自己就會看到,Groovy 給 Ant 和 Maven 都帶來了令人矚目的增強,Groovy 的優點在於它常常能恰到好處地彌補 XML 的遺漏!實際上,看起來 Groovy 的創建者肯定也曾經歷過用 XML 實施 Ant 和 Maven 構建過程的痛苦,因為他們引入了 AntBuilder,這是一個強大的新工具,支持在 Groovy 腳本中利用 Ant。

在 實戰 Groovy 的這一期中,我將向您展示不用 XML,轉用 Groovy 作為您的構建配置格式,對構造過程進行增強是多麼容易。閉包(closure)是 Groovy 的一個重要特性,而且是這門語言之所以與眾不同的核心,所以我在轉到下一節之前,先要對閉包做一個快速回顧。

快速回顧閉包

就像 Groovy 自己的一些非常著名的祖先一樣,Groovy 也支持 匿名函數 或 閉包(closure) 的概念。如果您曾經用 Python 或 Jython 編寫過代碼,那麼您可能對閉包比較熟悉,在這兩種語言中,都是用 lambda 關鍵字引入閉包。在 Ruby 中,如果 沒有 利用塊或閉包,那麼您就不得不實際地編寫腳本。即使是 Java 語言,也要通過它的 匿名內部類,對匿名函數提供了有限形式的支持。

在 Groovy 中,閉包是能夠封裝行為的第一級匿名函數。當 Ruby 的締造者 Yukihiro Matsumoto 注意到這些強大的第一類對象可以“傳遞給另一個函數,然後函數可以調用傳入的 [閉包]”時,也開始染指閉包的應用(請參閱 參考資料,查看完整的采訪)。當然,親自操作 Groovy 是了解閉包對於這門美好的令人激動的語言是一筆多麼大的財富的最好方法。

閉包實例

在清單 1 中,我又使用了一些本系列的第 1 篇文章曾經用過的代碼;但是這一次的重點是閉包。如果您已經讀過那篇文章(請參閱 參考資料),那麼您可以回憶起我用來演示用 Groovy 進行單元測試的基於 Java 的包過濾對象。這次,我還用相同的示例開始,但是用閉包對它進行了極大的增強。下面是基於 Java 的 Filter 接口。

清單 1. 還記得這個簡單的 Java Filter 接口嗎?

public interface Filter {
  void setFilter(String fltr);
  boolean applyFilter(String value);
}

上次,在定義了 Filter 類型之後,我接著定義了兩個實現,這兩個實現名為 RegexPackageFilter 和 SimplePackageFilter,它們分別使用了正則表達式和簡單的 String 操作。

對於沒用閉包寫成的代碼來說,到現在為止還算不錯。在清單 2 中,您會開始看到只有一點語法上的變化,代碼就不同了(更好了!)。我將通過定義通用的 Filter 類型(如下所示)開始,但是這次是用 Groovy 定義的。請注意與 Filter 類關聯的 strategy 屬性。這個屬性是閉包的一個實例,在執行 applyFilter 方法的時候可以調用它。

清單 2. 更加 Groovy 的過濾器 —— 使用閉包

class Filter{
  strategy
  boolean applyFilter(str){
    return strategy.call(str)
  }
}

添加閉包意味著我不用像在原來的 Filter 接口中那樣必須定義一個接口類型,或者依賴特定的實現才能得到期望的行為。相反,我可以定義一個通用的 Filter 類型,並給它提供一個閉包,讓它在執行 applyFilter 方法期間應用。

下一步是定義兩個閉包。第一個通過對指定參數應用簡單的 String 操作來模擬 SimplePackageFilter(來自前一篇文章)。當創建新的 Filter 類型時,對應的名為 simplefilter 的閉包會被傳遞到構造器中。第二個閉包(在幾個進行代碼驗證的 assert 之後出現)對指定的 String 應用正則表達式。這次我還是要創建一個新的 Filter 類型,向正則表達式傳遞一個名為 rfilter 的閉包,並執行一些 assert,以確保每件事都正常。所有細節如清單 3 所示:

清單 3. 使用 Groovy 閉包的簡單魔術

simplefilter = { str |
  if(str.indexOf("java.") >= 0){
   return true
  }else{
   return false
  }
}

fltr = new Filter(strategy:simplefilter)
assert !fltr.apply("test")
assert fltr.apply("java.lang.String")

rfilter = { istr |
  if(istr =~ "com.vanward.*"){
   return true
  }else{
   return false
  }
}

rfltr = new Filter(strategy:rfilter)
assert !rfltr.apply("java.lang.String")
assert rfltr.apply("com.vanward.sedona.package")

非常令人難忘,對吧?使用閉包,我能夠把對期望行為的定義推遲到運行時 —— 所以不必像在以前的設計中要做的那樣再定義新的 Filter 類型並編譯它。雖然我用 Java 代碼時能用匿名內部類做類似的事情,但是用 Groovy 更容易、神秘性更少一些。

閉包確實是非常強大的家伙。它們還代表處理行為的另外一種方式 —— 而 Groovy(以及它的遠親 Ruby)對它有非常嚴重的依賴。所以閉包會是我們下一個主題的要點,下一個主題是用生成器進行構建。

用生成器進行構建

使 Groovy 中的 Ant 更迷人的核心之處是 生成器。實際上,生成器允許您很方便地在 Groovy 中表示樹形數據結構,例如 XML 文檔。而且,女士們先生們請看,秘密在這:使用生成器,特別是 AntBuilder,您可以毫不費力地構造 Ant 的 XML 構建文件,不必處理 XML 就可以 執行生成的行為。而這並不是在 Groovy 中使用 Ant 的惟一優點。與 XML 不同,Groovy 是非常有表現力的開發環境,在這個環境中,您可以容易地編寫循環結構、條件選擇代碼,甚至可以利用“重用”的威力,而不必像以前那樣,費力地用剪切-粘貼操作來創建新 build.xml 文件。而且您做這些工作時,完全是在 Java 平台中!

生成器的優點,尤其是 Groovy 的 AntBuilder,在於它們的語法表示完全體現了它們所代表的 XML 文件的邏輯進程。被附加在 AntBuilder 實例上的方法與對應的 Ant 任務匹配;同樣的,這些方法可以接收參數(以 map 的形式),參數對應著任務的屬性。而且,嵌套標簽(例如 include 和 fileset)也定義成閉包。

構建塊:示例 1

我要用一個超級簡單的示例向您介紹生成器:一個叫做 echo 的 Ant 任務。在清單 4 中,我創建了一個普通的、每天都會用到的 Ant 的 echo 標記的 XML 版本(用在這不要奇怪):

清單 4. Ant 的 Echo 任務

<echo message="This was set via the message attribute"/>
<echo>Hello World!</echo>

事情在清單 5 中變得更有意思了:我用相同的 Ant 標簽,並在 Groovy 中用 AntBuilder 類型重新定義了它。注意,我可以使用 echo 的屬性 message,也可以只傳遞一個期望的 String。

清單 5. 用 Groovy 表示的 Ant 的 Echo 任務

ant = new AntBuilder()
ant.echo(message:"mapping it via attribute!")
ant.echo("Hello World!")

生成器特別吸引人的地方是它可以讓我把普通的 Groovy 特性與生成器語法混合,從而創建豐富的行為集。在清單 6 中,您應當開始看出可能性是無窮的:

清單 6. 用 Groovy 和 Ant 進行流控制(flow control)

ant = new AntBuilder()
ant.mkdir(dir:"/dev/projects/ighr/binaries/")
try{
   ant.javac(srcdir:"/dev/projects/ighr/src",
    destdir:"/dev/projects/ighr/binaries/" )
}catch(Throwable thr){
   ant.mail(mailhost:"mail.anywhere.com", subject:"build failure"){
    from(address:"[email protected]", name:"buildmaster")
    to(address:"[email protected]", name:"Development Team")
    message("Unable to compile ighr's source.")
   }
}

在這個示例中,我要捕獲源代碼編譯時的錯誤條件。注意 catch 塊中定義的 mail 對象如何接受定義了 from、 to 和 message 屬性的閉包。

哎呀!Groovy 的功能真多!當然,知道什麼時候應用這麼聰明的特性是問題的關鍵,而我們都在不斷地為之努力。幸運的是,實踐出真知,一旦您開始使用 Groovy(或者為了這個原因使用任何腳本語言),您就會找到許多在實際工作中使用它的機會;從中了解它在哪裡才真正適用。在下一節中,我將查看一個典型的、現實的示例,並在其中使用一些在這裡介紹的特性。

應用 Groovy

對於這個示例,我們假設需要為我的代碼定期建立校驗和(checksum)報告。一旦實現了這個報告,就可以用它在我需要的時候檢驗文件的完整性。下面是校驗和報告的高級技術用例:

編譯所有的源代碼。

對二進制類文件運行 md5 算法。

創建簡單的報告,列出每個類文件及其對應的校驗和。

完全擯棄 Ant 或 Maven,整個構建過程都使用 Groovy,在這個例子中,這樣做有點極端。但實際上,正如我前面解釋的,Groovy 是這些工具的極大 增強,而 不是 替代。所以,用 Groovy 的表現力處理後兩項任務,而把第一步信托給 Ant 或 Maven,這樣做才有意義。

實際上,我們只要假設我在第一步中用的是 Maven,因為坦白地說,它是我個人偏愛的構建平台。在 Maven 中可以很容易地用 java:compile 和 test:compile 目標編譯源文件;所以我保持這部分內容原封不動,讓我的新目標引用前面的目標,把前面的目標作為前提條件。實際就是這樣 —— 使用編譯好的源文件,我准備繼續運行校驗和工具(checksum utility);但是首先還需要做一些簡單的設置。

設置 Md5ReportBuilder

為了通過 Ant 運行這個漂亮的校驗和工具,我需要兩條信息:哪個目錄包含要進行校驗和處理的文件,報告要寫到哪個目錄。對於工具的第一個參數,我希望它是一個用逗號分隔的目錄列表。對後一個參數,我希望是報告目錄。

我決定調用工作類 Md5ReportBuilder。清單 7 中定義了它的 main 方法:

清單 7. Md5ReportBuilder 的 Main 方法

static void main(args) {

  assert args[0] && args[1] != null

  dirs = args[0].split(",")
  todir = args[1]

  report = new Md5ReportBuilder()
  report.runCheckSum(dirs)
  report.buildReport(todir)
}

上述步驟中的第一步是檢查這兩個參數是不是傳遞到工具中。然後我把第一個參數用逗號進行分割,創建了一個數組,保存要在上面運行 Ant 的 checksum 任務的目錄。最後,我創建 Md5ReportBuilder 類的新實例,調用兩個方法處理所需要的功能。

添加校驗和

Ant 包含一個 checksum 任務,調用起來非常容易,但需要傳遞一個 fileset 給它,其中包含目標文件的集合。在這個例子中,目標文件是包含編譯後的源文件的目錄,以及對應的單元測試文件。我可以通過使用 for 循環中的迭代得到這些文件,在這個例子中,是在目錄集合中進行迭代。對於每個目錄,調用 checksum 任務;而且 checksum 任務只在 .class 文件上運行,如清單 8 所示:

清單 8. runCheckSum 方法

/**
* runs checksum task for each dir in collection passed in
*/
runCheckSum(dirs){
  ant = new AntBuilder()
  for(idir in dirs){
   ant.checksum(fileext:".md5.txt" ){
    fileset(dir:idir) {
     include(name:"**/*.class")
    }
  }
}
}

構建報告

從這一點起,構建報告就變成了循環練習。讀取每個新生成的校驗和文件,把該文件對應的信息送到 PrintWriter 方法,然後將 XML 寫入文件 —— 雖然用的是最丑陋的形式,如清單 9 所示:

清單 9. 構建報告

buildReport(bsedir){
  ant = new AntBuilder()
  scanner = ant.fileScanner {
   fileset(dir:bsedir) {
    include(name:"**/*class.md5.txt")
   }
  }
  rdir = bsedir + File.separator + "xml" + File.separator
  file = new File(rdir)
  if(!file.exists()){
   ant.mkdir(dir:rdir)
  }
  nfile = new File(rdir + File.separator + "checksum.xml")
  nfile.withPrintWriter{ pwriter |
   pwriter.println("<md5report>")
   for(f in scanner){
    f.eachLine{ line |
     pwriter.println("<md5 class='" + f.path + "' value='" + line + "'/>")
    }
   }
   pwriter.println("</md5report>")
  }
}

那麼,如何處理這個報告呢?首先,我用 FileScanner 找到清單 8 中的 checksum 方法創建的每個校驗和文件。然後,我創建一個新目錄,在這個目錄中再創建一個新文件。(如果我能通過一個簡單的 if 語言檢查目錄是否已經存在,會不會更好一些?)然後我打開對應的文件,並用一個漂亮的閉包從 scanner 集合讀取每個對應的 File。示例最後用寫入 XML 元素的報告內容進行總結。

我敢打賭您立刻就注意到在 File 的那些實例上的 withPrintWriter 方法是多麼強大。我不需要考慮異常或關閉文件,因為它替我處理了每件事。我只是把我希望的行為通過閉包傳遞給它,然後就准備就緒了!

運行工具

這個 Groovy 巡演中的下一站是把它裝配進構建過程,特別是放在我的 maven.xml 文件中。非常幸運的是,這是這個相對容易的練習中最容易的部分,如清單 10 所示:

清單 10. 在 Maven 中運行 Md5ReportBuilder

<goal name="gmd5:run" prereqs="java:compile,test:compile">
  <path id="groovy.classpath">
   <ant:pathelement path="${plugin.getDependencyClasspath()}"/>
   <ant:pathelement location="${plugin.dir}"/>
   <ant:pathelement location="${plugin.resources}"/>
  </path>
  <java classname="groovy.lang.GroovyShell" fork="yes">
   <classpath refid="groovy.classpath"/>
   <arg value="${plugin.dir}/src/groovy/com/vanward/md5builder/Md5ReportBuilder.groovy"/>
   <arg value="${maven.test.dest},${maven.build.dest}"/>
   <arg value="${maven.build.dir}/md5-report"/>
  </java>
</goal>

正如前面所解釋過的,我已經規定必須在完整的編譯之前運行校驗和工具,所以,我的目標有兩個前提條件: java:compile 和 test:compile。類路徑 Classpath 總是很重要,所以我專門為了讓 Groovy 運行特別創建了一個正確的 classpath。最後清單 10 所示的目標調用 Groovy 的外殼,把要運行的腳本和腳本對應的兩個參數傳給外殼 —— 第一個是要在其上運行校驗和(用逗號分隔的)目錄,第二個是報告要寫入的目錄。

最後一步

完成對 maven.xml 文件的編碼之後,就差不多完成了所有要做的事,但是還有最後一步:我需要用所需的依賴關系來更新 project.xml 文件。沒什麼好奇怪的,Maven 需要具有一些必要的 Groovy 依賴項才能工作。這些依賴項是匯編代碼、字節碼操縱庫、公共命令行(commons-cli-處理命令行解析)Ant、以及對應的 ant 啟動器。這個示例的依賴項如清單 11 所示:

清單 11. Groovy 必需的依賴項

<dependencies>
  <dependency>
   <groupId>groovy</groupId>
   <id>groovy</id>
   <version>1.0-beta-6</version>
  </dependency>
  <dependency>
   <groupId>asm</groupId>
   <id>asm</id>
   <version>1.4.1</version>
  </dependency>
  <dependency>
   <id>commons-cli</id>
   <version>1.0</version>
  </dependency>
  <dependency>
   <groupId>ant</groupId>
   <artifactId>ant</artifactId>
   <version>1.6.1</version>
  </dependency>
  <dependency>
   <groupId>ant</groupId>
   <artifactId>ant-launcher</artifactId>
   <version>1.6.1</version>
  </dependency>
</dependencies>

結束語

在 實戰 Groovy 的第 2 期中,您看到了 Groovy 的表現力和靈活性與 Ant 和 Maven 無與倫比的應用結合在一起時發生了什麼。對於任何一個工具,Groovy 都提供了有吸引力的替代 XML 的構建格式。它讓您用循環構造和條件邏輯控制程序流,極大地增強了構建過程。

在向您展示 Groovy 的實用一面的同時,這個系列還專門介紹了它最適當的應用。應用任何技術的要點之一是認真思考它要應用的環境。在這個案例中,我向您展示了如何用 Groovy 增強 而不是 替換 已經非常強大的工具:Ant。

下個月,我要介紹 Groovy 的另外一項依賴閉包的特性。GroovySql 是個超級方便的小工具,可以使數據庫查詢、更新、插入以及所有對應的邏輯管理起來特別容易!下期再見!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved