1. 前言

自 Groovy 2.0 起,添加了对 JVM invokedynamic 指令的支持。此指令自 Java 7 起受支持,是 JVM 中一种新的字节码指令,可简化动态语言的实现。此指令也将由 JVM 内部使用,用于 Java 8 中的 lambda 支持。

这意味着与 API、AST 转换或语法糖不同,此功能对开发人员或最终用户而言是**不可见**的。它只是一个编译和运行时功能。这意味着,对于用 Groovy 编写的两个程序,您可以选择在支持 invokedynamic 的情况下或不支持 invokedynamic 的情况下进行编译。无论您选择哪种方式,都有其优缺点。

  • 只要您运行 JDK 1.7+,就可以在同一个项目中混合使用支持 invokedynamic 和不支持 invokedynamic 编译的类。

  • 根据 JVM 的不同(即使是 JVM 的不同次要版本),在激活 invokedynamic 支持的情况下,动态 Groovy 的性能可以接近 Java 性能。

2. 发行版

2.1. 两个 Jar 包

Groovy 发行版附带**两个** jar 包

  • groovy-x.y.z.jar:包含使用调用站点缓存编译的 Groovy 源代码

  • groovy-x-y-z-indy.jar:包含使用 invokedynamic 指令编译的 Groovy 源代码

由于 Groovy 核心和 Groovy 模块有时是用 Groovy 编写的,我们目前别无选择,只能发布两个不同的 Groovy 版本。这意味着,如果您选择“正常”jar 包,Groovy 自身的 Groovy 类将使用调用站点缓存(1.6+)进行编译,而如果您使用“indy”jar 包,Groovy 自身的 Groovy 类将使用 invokedynamic 进行编译。

两个 jar 包都包含一个功能齐全的 Groovy 实现,能够使用 invokedynamic 或调用站点缓存编译用户提供的 Groovy 源代码。这些 jar 包是互斥的(不要将两者都放在类路径中),它们之间的主要区别在于构成 Groovy 本身的 Groovy 源文件是如何编译的。

2.2. 命令行与 Indy

如果您下载发行版并使用命令行,则类路径中始终会选择“正常”版本的 Groovy。这意味着无论您使用什么命令(groovygroovycgroovyshgroovyConsole),invokedynamic 支持都无法开箱即用。要使用其 Groovy 源代码使用 invokedynamic 编译的 Groovy 发行版,您必须手动切换 jar 包。发行版使用 lib 目录中的 jar 包,而 indy jar 包在 indy 目录中可用。您需要做三件事:

  • 删除或重命名 lib 目录中的 groovy-*.jar 文件

  • 用 indy 目录中的 indy 版本替换它们

  • 从 jar 名称中删除 -indy 分类符

这是一个可以一次性完成所有操作的 bash 脚本:

$ for f in `ls lib/groovy*.jar | cut -d/ -f2`;do k=`basename $f .jar`; mv lib/$k.jar lib/$k.jar.old; cp indy/$k-indy.jar lib/$k.jar ; done

3. 从命令行运行 Groovy 脚本

从命令行运行脚本的通常方式是使用 groovy foo.groovy,其中 foo.groovy 是源代码形式的 Groovy 程序。要为此使用 indy,您必须使用 indy 编译标志 groovy --indy foo.groovy

4. 编译标志

无论您使用哪个 jar 版本(并且在按照描述交换 jar 包之后),invokedynamic 支持都需要一个特定的编译标志(indy)。如果您想在支持 invokedynamic 的情况下编译您的类,则必须在编译时设置此标志。下表显示了用户编译的类和 Groovy 核心类根据您使用的 jar 包和编译标志发生的情况:

表 1. 用户编译的类
indy 标志 关闭 开启

普通 jar

调用站点缓存

invokedynamic

indy jar

调用站点缓存

invokedynamic

表 2. 核心 Groovy 类
indy 标志 关闭 开启

普通 jar

调用站点缓存

调用站点缓存

indy jar

invokedynamic

invokedynamic

因此,即使您使用 indy jar 包,如果您在编译时没有使用 invokedynamic 标志,那么编译后的类将使用“旧”格式。