返回首页 Groovy 入门

Getting started

Grape 依赖管理器

1 快速上手

1.1 添加一个依赖项

Grape 是一个内嵌在 Groovy 中的 JAR 依赖项管理器。它能使你在类路径上快速添加 Maven 库依赖项,更易于编写脚本。最简单的用法是在脚本上添加注释(annotation),如下所示:

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

@Grab 也支持简短标记:

@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

注意,这里使用了标记式的导入,推荐采用这种方式。也可以在 mvnrepository.com 中搜索各种依赖项,该网站可提供 pom.xml 项的 @Grab 注释形式。

1.2 指定附加依赖库

并非所有的依赖项都位于 Maven 中心库中,还可以添加新的库:

@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')

1.3 Maven 分类器

有些 Maven 依赖项需要使用分类器(classifier)才能解析,比如像下面这个例子:

@Grab(group='net.sf.json-lib', module='json-lib', version='2.2.3', classifier='jdk15')

1.4 排除传递性依赖

有些时候,因为可能在使用的依赖项版本稍有不同,但具有兼容性,需要排除某些传递性依赖,如下所示:

@Grab('net.sourceforge.htmlunit:htmlunit:2.8')
@GrabExclude('xml-apis:xml-apis')

1.5 JDBC 驱动

依据 JDBC 驱动的加载方式的不同,可能需要配置 Grape,将 JDBC 驱动依赖项添加到系统类加载器中,比如:

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')

1.6 利用 Groovy Shell 来使用 Grape

在 groovysh 上使用方法调用变体:

groovy.grape.Grape.grab(group:'org.springframework', module:'spring', version:'2.5.6')

1.7 代理设置

如果被防火墙阻隔,或者想通过代理服务器来使用 Groovy/Grape,可以通过 http.proxyHosthttp.proxyPort 系统属性在命令行中指定这些设置:

groovy -Dhttp.proxyHost=yourproxy -Dhttp.proxyPort=8080 yourscript.groovy

或者,还可以将这些属性添加到 JAVA_OPTS 环境变量中,从而应用到整个系统中:

JAVA_OPTS = -Dhttp.proxyHost=yourproxy -Dhttp.proxyPort=8080

1.8 日志

如果想查看 Grape 的工作内容,可以将 groovy.grape.report.downloads 设为 true(比如将 -Dgroovy.grape.report.downloads=true 添加到 JAVA_OPTS 环境变量中),Grape 就能将下列信息打印在 System.error 中:

  • 开始解析一个依赖项;
  • 开始下载一个工件;
  • 重试工件下载;
  • 可下载工件的下载尺寸及用时。

2 详细介绍

Grape(Groovy Adaptable Packaging EngineGroovy Advanced Packaging Engine1)是一种能够在 Groovy 中启用 grab() 的基础设施,这组类利用Ivy 为 Groovy 实现由仓库驱动的模式系统。有了它,开发者就可以利用任意的库来编写脚本,并只单纯利用脚本来进行交付。Grape 在运行时会按需要下载这些库,链接上命名库以及所有所需的依赖项,当脚本由已存在的仓库(比如JCenter、Ibiblio 与 java.net)运行时,这些链接内容会构成传递闭包,

1. Groovy 可适应性包装引擎 或 Groovy 先进包装引擎。

Groovy 遵循 Ivy 规范来进行模块版本识别,但在命名上有差别:

  • group 模块所在的模块组。直接转换为 Maven groupId 或一个 Ivy 组织。任何匹配 /groovy[x][\..*]^/ 的组都将得到保留,可能对 Groovy 支持模块有特殊的用途。

  • module 要加载模块的名称。直接转换为 Maven artifactId 标识符或某个 Ivy 工件上。

  • version 要使用的模块的版本。无论是字面版本 '1.1-RC3' 还是 Ivy 范围 '[2.2.1,]',都表示 2.2.1 或更新的版本。

  • classifier 可选择使用的分类器(比如说 jdk15)。

下载的模块会按照 Ivy 的标准机制存储,缓存根目录为 ~/.groovy/grape

3 用法

3.1 注释

可以在任何位置添加一个或多个 groovy.lang.Grab 注释,它们会告诉编译器代码所依赖的某个具体库。这和把库添加到 Groovy 编译器的类路径上的作用是一样的。在脚本中,这种注释会在任何其他类解析之前被发现并执行,所以导入类可以通过 @Grab 正确地得到解析。

import com.jidesoft.swing.JideSplitButton
@Grab(group='com.jidesoft', module='jide-oss', version='[2.2.1,2.3.0)')
public class TestClassAnnotation {
    public static String testMethod () {
        return JideSplitButton.class.name
    }
}

含有类(或者在注释脚本元素情况下的脚本类)的类静态初始化器会正确地调用 grab(…​)

3.2 多重 Grape 注释

为了在同一节点多次使用 Grape 注释,必须使用 @Grapes,如下所示:

@Grapes([
   @Grab(group='commons-primitives', module='commons-primitives', version='1.0'),
   @Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='0.9.7')])
class Example {
// ...
}

否则,就会出现下列错误:

Cannot specify duplicate annotation on the same member

3.3 方法调用

通常对 grab 的调用往往发生在脚本或类初始化这种早期阶段。这是为了确保在 Groovy 代码依赖库代码前,库可以被类加载器所利用。典型的调用如下所示:

import groovy.grape.Grape
// 随机 maven 库
Grape.grab(group:'com.jidesoft', module:'jide-oss', version:'[2.2.0,)')
Grape.grab([group:'org.apache.ivy', module:'ivy', version:'2.0.0-beta1', conf:['default', 'optional']],
     [group:'org.apache.ant', module:'ant', version:'1.7.0'])
  • 在相同上下文中,参数也相同时,多次对 grab 调用应该是幂等(idempotent)的。但如果同样的代码在不同的 ClassLoader 上下文中调用时,解析就会重新运行。
  • 如果传入 grab 中的 args 映射拥有预定为 true 的 noExceptions 属性,则不会抛出异常。
  • grab 需要在调用类的 ClassLoader 链中指定或存在 grabRootLoader。默认,如果这样一种 ClassLoader 无法获得,则会反映在模块解析上,就会抛出异常:

    • 通过 classLoader: 参数传入的类加载器,及其父级类加载器;
    • 作为 referenceObject: 参数传入的类加载器,及其父级类加载器;
    • 发出对 grab 调用的类的类加载器。

3.3.1 grab(HashMap) 参数

  • group:-\<字符串型>-模块来自的模块组。直接转换为 Maven groupId。任何匹配 /groovy(|\..|x|x\..)/ 的模块组都被保留下来,可能对 Groovy 支持模块而言有特殊含义。
  • module:-\<字符串型>-要加载的模块名称。直接转换为 Maven artifactId。
  • version:-\<字符串型>,有可能是\-所要使用模块的版本。无论是字面版本 '1.1-RC3' 还是 Ivy 范围 '[2.2.1,]',都表示 2.2.1 或更新的版本。
  • classifier:-\<字符串型>-解析所利用的 Maven 分类器。
  • conf: -\<字符串型>默认为 'default'-意指想要下载的模块的配置或范围,映射至 runtimemaster 范围。
  • force: -\<布尔类型>默认为 true-用于表明该版本必须用在发生冲突的情况下,与冲突管理器无关。
  • changing:-\<布尔类型>默认为 false-是否无需版本名称改变,就能改变工件。
  • transitive:-\<布尔类型>默认为 true-是否解决该模块的其他依赖项。

grab 有两大主要变体,一个带有单一的映射,另一个则带有一个参数映射及多个依赖映射。调用单一映射的 grab 和调用传入两次映射的 grab 完全相同,所以 grab 的参数和依赖项都可以混合在一个单独的映射中,grab 可以作为带有多个已命名参数的单一方法进行调用。

这些参数有以下这些同义词,然而提交多个会造成运行时异常。

  • group:groupId:organisation:organization:org:

  • module:artifactId:artifact:

  • version:revision:rev:

  • conf:scope:configuration:

3.3.2 参数映射的参数

  • classLoader: -\<GroovyClassLaoder> 或 \<RootClassLoader> - 添加要解析的 jar 的类加载器。
  • refObject: -\<Object>-对象类所用的最近的父级类加载器会被作为,尽管传入的是 classLoader
  • validate:-\<布尔类型>默认为 false-是否验证 pom 或 ivy 文件(true),或者相信缓存(false)。
    • noExceptions:-\<布尔类型>默认为 false-如果类加载器解析或仓库查询失败,是否抛出异常(false)或静默失败(true)。

3.4 命令行工具

Grape 添加了一个命令行可执行的 grape ,从而实现对本地 grape 缓存的检查与管理。

grape install <groupId> <artifactId> [<version>]

该命令会安装指定的 Groovy 模块或 Maven 工件。如果指定的是特定的版本,则会安装该版本;如未指定,则安装最新的版本(就好像我们用的是 *)。

grape list

列出本地安装模块(Groovy 模块则要带上它们的完整的 maven 名称)及版本。

grape resolve (<groupId> <artifactId> <version>)+

这将返回 jar 文件的位置,它代表指定模块及相应传递依赖的工件。还可以选择性地传入 -ant-dos-shell,获取用 ant 脚本、Windows 批处理文件以及 Unix Shell 脚本的格式表达的依赖。也可以传入 -ivy ,查看用类似 ivy 格式表达的依赖。

3.5 高级配置

3.5.1 仓库目录

如果需要改变 grape 用来下载库的目录,可以指定 grape.root 系统属性来改变默认配置(~/.groovy/grape)。

groovy -Dgrape.root=/repo/grape yourscript.groovy

3.5.2 自定义 Ivy 设置

可以通过创建一个 ~/.groovy/grapeConfig.xml 文件来自定义 Grape 所使用的 ivy 设置。如果不存在这种文件,可以参考一下 Grape 使用的默认设置

关于自定义设置的详细信息,可参考 Ivy 文档

3.6 更多范例

使用 Apache Commons 集合库:

// 创建并使用原始数组列表  
import org.apache.commons.collections.primitives.ArrayIntList

@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
def createEmptyInts() { new ArrayIntList() }

def ints = createEmptyInts()
ints.add(0, 42)
assert ints.size() == 1
assert ints.get(0) == 42

使用 TagSoup:

// 寻找 Java 规范的 PDF 链接  
@Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='1.2.1')
def getHtml() {
    def parser = new XmlParser(new org.ccil.cowan.tagsoup.Parser())
    parser.parse("https://docs.oracle.com/javase/specs/")
}
html.body.'**'.a.@href.grep(~/.*\.pdf/).each{ println it }

使用 Google 集合库:

import com.google.common.collect.HashBiMap
@Grab(group='com.google.code.google-collections', module='google-collect', version='snapshot-20080530')
def getFruit() { [grape:'purple', lemon:'yellow', orange:'orange'] as HashBiMap }
assert fruit.lemon == 'yellow'
assert fruit.inverse().yellow == 'lemon'

启动 Jetty 服务器为 Groovy 模板提供服务:

@Grapes([
    @Grab(group='org.eclipse.jetty.aggregate', module='jetty-server', version='8.1.7.v20120910'),
    @Grab(group='org.eclipse.jetty.aggregate', module='jetty-servlet', version='8.1.7.v20120910'),
    @Grab(group='javax.servlet', module='javax.servlet-api', version='3.0.1')])

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.*
import groovy.servlet.*

def runServer(duration) {
    def server = new Server(8080)
    def context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
    context.resourceBase = "."
    context.addServlet(TemplateServlet, "*.gsp")
    server.start()
    sleep duration
    server.stop()
}

runServer(10000)

一加载脚本,Grape 就会下载 Jetty 及其依赖,并予以缓存。我们在 8080 端口处创建新的 Jetty 服务器,然后在上下文的根目录暴露 Groovy 的 TemplateServlet —— Groovy 自带一个功能强大的模板引擎机制。启动服务器,让它运行一段时间。每当点击 http://localhost:8080/somepage.gsp 时,就会显示 somepage.gsp 模板。这些模板页面应该和服务器脚本同位于一个目录。