在本文中将会涉及到:

  • 使用 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 语言都是跨平台的,所以您可以在任何平台上编写本示例中的程序,并将它运行在任何平台上。


回页首

利用 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) 和函数式编程形成了一个强有力的组合。了解元编程如何支持您为 Integer 数据类型添加方法,从而使您能够利用 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 脚本(转)的更多相关文章

  1. 【java web】java运行预编译Groovy脚本

    在JVM中执行Groovy类有两种方式: 使用Groovy编译全部的*.groovy为java的*.class文件,把这些*.class文件放在java类路径中,通过java类载入器来载入这些类. 通 ...

  2. 通过DOS、SHELL批处理命令加载Lib并编译和打包Java项目(或者运行项目)

    有些时候,需要通过DOS批处理来编译整个项目的JAVA文件:并且编译后还要对Class文件进行打包成jar文件...这还不是最烦的,最烦的是,编译和打包的时候需要依赖其他多个jar文件,困难就这么来了 ...

  3. JAVA嵌入运行Groovy脚本

    摘自: http://shift-alt-ctrl.iteye.com/blog/1938238 . 最近设计一个数据统计系统,系统中上百种数据统计维度,而且这些数据统计的指标可能随时会调整.如果基于 ...

  4. 180807-Quick-Task 动态脚本支持框架之Groovy脚本加载执行

    Quick-Task 动态脚本支持框架之Groovy脚本加载执行 上一篇简答说了如何判断有任务动态添加.删除或更新,归于一点就是监听文件的变化,判断目录下的Groovy文件是否有新增删除和改变,从而判 ...

  5. make和rpm的编译、打包总结

    1  make工具使用 1.1 makefile基本规则 Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作. Makefile的规则: tar ...

  6. 反编译pyinstaller打包的exe安装包

    PyInstaller将Python文件打包为exe后如何反编译(破解源码)以及防止反编译 在这里分享一些技巧和经验给大家.辛苦撰文分享,转载或引用请保留本文作者信息及文章链接. 作者的环境: win ...

  7. Groovy脚本基础全攻略

    1 背景 Groovy脚本基于Java且拓展了Java,所以从某种程度来说掌握Java是学习Groovy的前提,故本文适用于不熟悉Groovy却想快速得到Groovy核心基础干货的Java开发者(注意 ...

  8. 复杂多变场景下的Groovy脚本引擎实战

    一.前言 因为之前在项目中使用了Groovy对业务能力进行一些扩展,效果比较好,所以简单记录分享一下,这里你可以了解: 为什么选用Groovy作为脚本引擎 了解Groovy的基本原理和Java如何集成 ...

  9. 编译安装&打包压缩&定时任务

    内容概要 编译安装 打包压缩 定时任务 内容详细 一.编译安装 1.特点 使用源代码,编译打包软件. ​ 1.可以自定制软件 ​ 2.按需构建软件啊 2.步骤 下载安装包 wget 下载网址 如果没有 ...

随机推荐

  1. 14.4.5 Configuring InnoDB Change Buffering 配置InnoDB Change Buffering

    14.4.5 Configuring InnoDB Change Buffering 配置InnoDB Change Buffering 当INSERT,UPDATE,和删除操作在表上操作, 索引列的 ...

  2. Linux入门基础 #9:管道及重定向

    本文出自   http://blog.csdn.net/shuangde800 ------------------------------------------------------------ ...

  3. Memcahce(MC)系列(一)Memcache介绍、使用、存储、算法、优化

    写在前面:前不久在工作中被问到关于MC一致哈希的问题,由于时隔太久差点儿忘记,特前来恶补一下MC,下面是前几年在工作中学习MC时的一些资料,来历不明,特整理一下,希望对大家的学习也能有帮助. 32 的 ...

  4. MySQL多表查询之外键、表连接、子查询、索引

    MySQL多表查询之外键.表连接.子查询.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为 ...

  5. [置顶] github简单使用

    git的介绍可以看这里   http://zh.wikipedia.org/wiki/GitHub 安装和使用参考的这个    http://www.cnblogs.com/cocowool/arch ...

  6. jQuery 自学笔记—5 遍历

    什么是遍历? jQuery 遍历,意为“移动”,用于根据其相对于其他元素的关系来“查找”(或选取)HTML 元素.以某项选择开始,并沿着这个选择移动,直到抵达您期望的元素为止. 下图展示了一个家族树. ...

  7. LINUX专题之操作系统字符集

    原创作品,出自 "深蓝的blog" 博客,欢迎转载.转载时请务必注明下面出处,否则追究版权法律责任. 深蓝的blog: http://blog.csdn.net/huangyanl ...

  8. Codeforces 461B - Appleman and Tree 树状DP

    一棵树上有K个黑色节点,剩余节点都为白色,将其划分成K个子树,使得每棵树上都仅仅有1个黑色节点,共同拥有多少种划分方案. 个人感觉这题比較难. 如果dp(i,0..1)代表的是以i为根节点的子树种有0 ...

  9. IOS获取来电去电来电归属系统通知达到效果(一)

    这里的原理是获得到呼叫系统通知,然后根据本地呼叫电话号码,相应的电话联系. 一世.   来电显示是不是在地址簿中的联系人, 这是当第一个加入一个临时的联系人在您的电话簿(它是创建一个新的接触,并保存相 ...

  10. [Linux]Centos git报错fatal: HTTP request failed

    在使用git pull.git push.git clone会报类似例如以下的错误: error: The requested URL returned error: 401 Unauthorized ...