Grape 依赖管理器

1. 快速入门

1.1. 添加依赖

Grape 是嵌入在 Groovy 中的 JAR 依赖管理器。Grape 允许您快速将 Maven 仓库依赖添加到您的类路径中,使脚本编写更加容易。最简单的用法就是向您的脚本添加一个注解

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

@Grab 也支持简写方式

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

请注意,我们在这里使用了注解导入,这是推荐的方式。您还可以在 mvnrepository.com 上搜索依赖,它将为您提供 pom.xml 条目的 @Grab 注解形式。

1.2. 指定附加仓库

并非所有依赖都位于 Maven Central 中。您可以像这样添加新的依赖

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

1.3. Maven 分类器

某些 Maven 依赖需要分类器才能解析。您可以像这样解决

@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(例如,在调用或 JAVA_OPTS 中添加 -Dgroovy.grape.report.downloads=true),Grape 会将以下信息打印到 System.error

  • 开始解析依赖

  • 开始下载构件

  • 重试下载构件

  • 下载构件的大小和时间

要记录更多详细信息,请增加 Ivy 日志级别(默认为 -1)。例如 -Divy.message.logger.level=4

2. 详情

Grape (Groovy Adaptable Packaging EngineGroovy Advanced Packaging Engine) 是 Groovy 中启用 grab() 调用的基础设施,它是一组利用 Ivy 的类,允许 Groovy 使用基于仓库的模块系统。这使得开发人员可以编写一个脚本,其中包含任意的库需求,并且只分发该脚本。Grape 将在运行时根据需要下载并链接命名的库以及所有在脚本从现有仓库(如 Maven Central)运行时形成传递闭包的依赖项。

Grape 遵循 Ivy 的模块版本标识约定,但名称有所更改。

  • group - 模块所属的模块组。直接转换为 Maven 的 groupId 或 Ivy 的 Organization。任何匹配 /groovy[x][\..*]^/ 的组都保留,并且可能对 Groovy 认可的模块具有特殊含义。

  • module - 要加载的模块的名称。直接转换为 Maven 的 artifactId 或 Ivy 的 artifact。

  • version - 要使用的模块版本。可以是字面版本 `1.1-RC3',也可以是 Ivy 范围 `[2.2.1,)'(表示 2.2.1 或任何更高版本)。

  • classifier - 要使用的可选分类器(例如,jdk15

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

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 注解

在 Groovy 的早期版本中,如果您想在同一个节点上多次使用 Grab 注解,您必须使用 @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

但在最新版本中,@Grapes 纯属可选。

技术说明

  • 最初,Groovy 存储 Grab 注解以供运行时访问,并且字节码中不允许重复。在当前版本中,@Grab 仅具有 SOURCE 保留策略,因此多次出现不是问题。

  • Grape 的未来版本可能会支持使用 Grapes 注解来提供某种程度的结构化,例如允许 GrabExclude 或 GrabResolver 注解仅应用于 Grab 注解的子集。

3.3. 方法调用

通常,对 grab 的调用会发生在脚本的早期或类初始化中。这是为了确保在 Groovy 代码依赖库之前,这些库已可用于 ClassLoader。一些典型的调用可能如下所示

import groovy.grape.Grape
// random maven library
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 调用应该是幂等的。但是,如果使用不同的 ClassLoader 上下文调用相同的代码,则可能会重新运行解析。

  • 如果传递给 grab 调用的 args map 中有一个属性 noExceptions 评估为 true,则不会抛出异常。

  • grab 要求指定 RootLoaderGroovyClassLoader,或者它位于调用类的 ClassLoader 链中。默认情况下,如果无法获取此类 ClassLoader,将导致模块解析失败并抛出异常

    • 通过 classLoader: 参数及其父类加载器传入的 ClassLoader。

    • 作为 referenceObject: 参数传入的对象的 ClassLoader 及其父类加载器。

    • 发出 grab 调用的类的 ClassLoader

3.3.1. grab(HashMap) 参数

  • group: - <String> - 模块所属的模块组。直接转换为 Maven 的 groupId。任何匹配 /groovy(|\..|x|x\..)/ 的组都保留,并且可能对 Groovy 认可的模块具有特殊含义。

  • module: - <String> - 要加载的模块的名称。直接转换为 Maven 的 artifactId。

  • version: - <String> 和可选的 <Range> - 要使用的模块版本。可以是字面版本 `1.1-RC3',也可以是 Ivy 范围 `[2.2.1,)'(表示 2.2.1 或任何更高版本)。

  • classifier: - <String> - 用于解析的 Maven 分类器。

  • conf: - <String>,默认为 default' - 要下载模块的配置或范围。默认的 conf 是 `default:,它映射到 Maven 的 runtimemaster 范围。

  • force:- <boolean>,默认为 true - 用于指示在冲突情况下,无论冲突管理器如何,都必须使用此版本

  • 冲突管理器

  • changing: - <boolean>,默认为 false - 构件是否可以在其版本标识不改变的情况下发生更改。

  • transitive: - <boolean>,默认为 true - 是否解析此模块的其他依赖项。

grab 有两种主要变体,一种带有一个 Map,另一种带有一个参数 Map 和多个依赖项 Map。对单个 Map 的 grab 调用等同于调用 grab 两次传入相同的 Map,因此 grab 参数和依赖项可以混合在同一个 Map 中,并且 grab 可以作为带有命名参数的单个方法调用。

这些参数有同义词。提交多个会引发运行时异常。

  • group:, groupId:, organisation:, organization:, org:

  • module:, artifactId:, artifact:

  • version:, revision:, rev:

  • conf:, scope:, configuration:

3.3.2. 参数 Map arguments

  • classLoader: - <GroovyClassLoader> 或 <RootClassLoader> - 要将解析后的 Jar 添加到的 ClassLoader

  • refObject: - <Object> - 对象的类的最近父 ClassLoader 将被视为已作为 classLoader: 传入

  • validate: - <boolean>,默认为 false - 是否应验证 pom 或 ivy 文件(true),或者我们是否应该信任缓存(false)。

  • noExceptions: - <boolean>,默认为 false - 如果 ClassLoader 解析或仓库查询失败,我们应该抛出异常(false)还是静默失败(true)。

3.4. 命令行工具

Grape 添加了一个命令行可执行文件 `grape',允许检查和管理本地 grape 缓存。

grape install [-hv] <group> <module> [<version>] [<classifier>]

这将安装指定的 Groovy 模块或 Maven 构件。如果指定了特定版本,则将安装该特定版本,否则将使用最新版本(如同传入 `*')。

grape list

列出本地安装的模块(对于 Groovy 模块,包括其完整的 Maven 名称)和版本。

grape resolve [-adhisv] (<groupId> <artifactId> <version>)+

这将返回表示指定模块及其相关传递性依赖项的 jar 文件的文件位置。您可以选择传入 -ant、-dos 或 -shell,以分别获取适用于 ant 脚本、windows 批处理文件或 unix shell 脚本的格式表达的依赖项。可以传入 -ivy 来查看以类似 ivy 格式表达的依赖项。

grape uninstall [-hv] <group> <module> <version>

这将卸载特定的 grape:它将非传递性地从 grape 缓存中删除相应的 jar 文件。

3.5. 高级配置

3.5.1. 仓库目录

如果您需要更改 Grape 用于下载库的目录,您可以指定 grape.root 系统属性来更改默认值(默认为 ~/.groovy/grapes)

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

3.5.2. 自定义 Ivy 设置

您可以通过创建 ~/.groovy/grapeConfig.xml 文件来自定义 Grape 使用的 ivy 设置。如果不存在这样的文件,此处是 Grape 使用的默认设置。

有关如何自定义这些设置的更多信息,请参阅 Ivy 文档

3.6. 更多示例

使用 Apache Commons Collections

// create and use a primitive array list
@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
import org.apache.commons.collections.primitives.ArrayIntList

def createEmptyInts() { new ArrayIntList() }

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

使用 TagSoup

// find the PDF links of the Java specifications
@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 Collections

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 模板

@Grab('org.eclipse.jetty.aggregate:jetty-server:8.1.19.v20160209')
@Grab('org.eclipse.jetty.aggregate:jetty-servlet:8.1.19.v20160209')
@Grab('javax.servlet:javax.servlet-api:3.0.1')
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.ServletContextHandler
import groovy.servlet.TemplateServlet

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 及其依赖项,并将其缓存。我们创建一个新的 Jetty 服务器在 8080 端口,然后将 Groovy 的 TemplateServlet 公开在上下文的根目录——Groovy 自带其强大的模板引擎机制。我们启动服务器并让它运行一段时间。每次有人访问 https://:8080/somepage.gsp 时,它都会向用户显示 somepage.gsp 模板——这些模板页面应该与此服务器脚本位于同一目录中。