作为研发工程师,在研发系统时,我们总期望可以搭建一个通用的平台,不希望牵扯进任何定制化的业务需求。
作为产品经理,不是很关心系统功能有多强大,能实现当前业务需求的才是好系统。
二者总需要妥协,Groovy、Python 这类脚本性语言提供了解法。底层Java平台功能不牵扯业务,尽量封装丰富的底层组件,一些业务性的逻辑都由脚本语言实现。
通常平台提供脚本编辑窗口,由产品经理来写脚本,实现业务需求。脚本保存发布后,包含定制化业务的平台功能实时生效。具体怎么实现的呢?下面就来介绍。
1. Groovy介绍
Groovy 是一种功能强大且灵活的动态语言,运行在 Java 虚拟机(JVM)上。它结合了动态语言的灵活性和 Java 的强大生态系统,使其成为一种高效的编程语言。
1.1. 语言特性
1. 动态类型
- 动态性:Groovy 是动态类型语言,允许在运行时确定变量类型。这种动态性使得代码更加简洁和灵活。
示例:
def name = "Groovy" println name
2. 简化语法
- 语法糖:Groovy 提供了许多简化的语法特性,例如省略分号、默认导入常用包、支持闭包、内置集合操作等。这使得代码更加简洁和可读。
示例:
def list = [1, 2, 3, 4, 5] list.each { println it }
3. 闭包支持
- 闭包:Groovy 支持闭包,这是可以捕获其定义环境的代码块。闭包在处理集合、事件处理和回调中非常有用。
示例:
def greet = { name -> println "Hello, $name!" } greet("World")
4. 原生集合和正则表达式
- 集合操作:Groovy 对列表、映射等集合提供了强大的原生支持,简化了集合操作。
- 正则表达式:内置支持正则表达式,使用
/pattern/
语法。 示例:
def numbers = [1, 2, 3, 4, 5] def evens = numbers.findAll { it % 2 == 0 } println evens def pattern = ~/Groovy/ println "Hello Groovy" ==~ pattern
1.2. 与 Java 的关系
1. 兼容性
- 语法相似:Groovy 的语法与 Java 非常相似,Java 开发者可以轻松上手。
- Java 互操作性:Groovy 可以直接调用 Java 类库,使用 Java API,这使得 Groovy 可以无缝集成到 Java 项目中。
- JVM 语言:Groovy 运行在 JVM 上,能够利用 Java 的强大生态系统,包括库、工具和框架。
2. 动态编译
Groovy 默认是动态编译的,这意味着变量的类型在运行时确定。这种方式提供了灵活性和简洁性。
动态编译示例
def greet(name) {
println "Hello, $name!"
}
greet("World")
在这个示例中,greet
函数没有指定参数类型,参数 name
的类型在运行时确定。这是 Groovy 动态编译的典型特征。
3. 静态编译
为了提高性能和类型安全性,Groovy 提供了静态编译功能。通过使用 @CompileStatic
注解,可以在编译时确定类型,从而获得更接近 Java 的性能表现。
静态编译示例
import groovy.transform.CompileStatic
@CompileStatic
def greet(String name) {
println "Hello, $name!"
}
greet("World")
在这个示例中,我们使用 @CompileStatic
注解标记 greet
方法,并明确指定参数 name
的类型为 String
。这样,Groovy 会在编译时检查类型并生成优化的字节码。
选择编译模式的考虑:
- 动态编译:适用于需要快速开发、灵活性和简化代码的场景,例如脚本编写和原型设计。动态编译减少了类型声明的样板代码,使得代码更加简洁。
- 静态编译:适用于对性能和类型安全性要求较高的场景,例如生产环境中的关键业务逻辑。静态编译可以提供与 Java 类似的性能,同时在编译时捕获类型错误。
我们是 Java开发,在 Java 代码中如何执行 Groovy 脚本呢?下面介绍一个最简单的方法 GroovyScriptEvaluator
。
2. GroovyScriptEvaluator
GroovyScriptEvaluator
是 Spring Framework 提供的一个类,用于动态执行 Groovy 脚本。它允许你在 Java 应用中运行 Groovy 代码,并通过传递上下文变量与脚本进行交互。下面是 GroovyScriptEvaluator
的详细介绍及使用示例。
2.1. 代码示例
1. 引入依赖
确保你的项目中包含必要的依赖,以便使用 Spring 和 Groovy。以下是 Maven 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.30</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>3.0.10</version> <!-- 请根据需要选择合适的版本 -->
</dependency>
2. 编写 Groovy 脚本
假设你有一个简单的 Groovy 脚本,可以是内联字符串或外部文件。以下是一个简单的内联脚本示例:
// 这个脚本接受一个名字并返回问候语
return "Hello, " + name + "!"
3. 使用 GroovyScriptEvaluator
以下是一个使用 GroovyScriptEvaluator
执行上述 Groovy 脚本的 Java 示例:
import org.springframework.scripting.groovy.GroovyScriptEvaluator;
import org.springframework.scripting.support.StaticScriptSource;
import java.util.HashMap;
import java.util.Map;
public class GroovyScriptEvaluatorExample {
public static void main(String[] args) {
// 创建 GroovyScriptEvaluator 实例
GroovyScriptEvaluator evaluator = new GroovyScriptEvaluator();
// 定义一个简单的 Groovy 脚本
String script = "return 'Hello, ' + name + '!'";
// 使用 StaticScriptSource 传递脚本
StaticScriptSource scriptSource = new StaticScriptSource(script);
// 创建上下文变量
Map<String, Object> variables = new HashMap<>();
variables.put("name", "World");
// 执行脚本并获取结果
try {
Object result = evaluator.evaluate(scriptSource, variables);
System.out.println(result); // 输出: Hello, World!
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. 详细介绍
GroovyScriptEvaluator
:- 这是核心类,用于执行 Groovy 脚本。它提供了
evaluate
方法,可以执行脚本并返回结果。
- 这是核心类,用于执行 Groovy 脚本。它提供了
StaticScriptSource
:- 用于封装脚本内容。在这个示例中,我们直接使用内联字符串作为脚本源。
上下文变量:
- 通过
Map<String, Object>
传递给脚本的变量。脚本中可以使用这些变量进行计算或逻辑处理。
- 通过
异常处理:
- 在执行脚本时,可能会发生异常(例如语法错误或运行时错误),因此使用
try-catch
块来捕获和处理异常。
- 在执行脚本时,可能会发生异常(例如语法错误或运行时错误),因此使用
5. 使用外部脚本文件
如果你想从外部文件加载脚本,可以使用 ResourceScriptSource
:
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.core.io.ClassPathResource;
// 假设脚本文件位于 classpath 下的 scripts/greeting.groovy
ResourceScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("scripts/greeting.groovy"));
2.2. 原理
GroovyScriptEvaluator
是一个用于在运行时动态执行 Groovy 脚本的工具。它的实现通常依赖于 Groovy 的核心机制,如 GroovyShell
或 GroovyClassLoader
,来编译和执行脚本。在动态脚本执行中,理解 GroovyScriptEvaluator
的原理以及它如何处理类文件生成是非常重要的。
1. 原理
动态脚本执行:
GroovyScriptEvaluator
可以在运行时接收一段 Groovy 脚本,并对其进行解析、编译和执行。这个过程通常是由 Groovy 提供的 API(如GroovyShell
)封装的。
编译过程:
- 在执行脚本之前,Groovy 会将脚本代码编译成 Java 字节码。这个编译过程通常是通过
GroovyClassLoader
实现的。GroovyClassLoader
是一个特殊的类加载器,负责将 Groovy 脚本转换成可在 JVM 上执行的类。
- 在执行脚本之前,Groovy 会将脚本代码编译成 Java 字节码。这个编译过程通常是通过
类加载与执行:
- 编译后的字节码被加载到 JVM 中,然后通过反射或直接调用的方式执行。这种动态的类加载和执行机制是 Groovy 能够在运行时处理动态逻辑的核心。
2. 需要缓存机制
在默认情况下,每次执行新的 Groovy 脚本时,GroovyClassLoader
会生成新的类。这是因为每段脚本可能不同,需要独立的类定义。
可以通过实现脚本缓存机制来重用已编译的脚本。这种机制会根据脚本的内容生成一个唯一的键值,并将编译后的类与该键值关联,以便在下次执行相同脚本时直接重用,而不是重新编译。
由于每个脚本都会生成新的类文件,频繁执行大量不同的脚本可能导致内存使用增加。因此,在高频动态执行场景下,合理的缓存和内存管理策略是非常重要的。
既然是通过 GroovyClassLoader
编译生成 Java 字节码的,那么下面就基于 GroovyClassLoader
构建缓存机制。
3. 最佳实践
3.1. 示例代码
1. GroovyScriptManager 类
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import java.util.HashMap;
import java.util.Map;
public class GroovyScriptManager {
// 缓存编译后的脚本类
private final Map<String, Class<?>> classCache = new HashMap<>();
// 缓存脚本实例
private final Map<String, Script> instanceCache = new HashMap<>();
/**
* 编译并缓存 Groovy 脚本。
*
* @param id 脚本的唯一标识符
* @param content 脚本文本内容
*/
public void compileScript(String id, String content) {
try (GroovyClassLoader loader = new GroovyClassLoader()) {
// 编译脚本并获取类
Class<?> scriptClass = loader.parseClass(content);
// 缓存编译后的类和实例
classCache.put(id, scriptClass);
Script scriptInstance = InvokerHelper.createScript((Class<Script>) scriptClass, new Binding());
instanceCache.put(id, scriptInstance);
System.out.println("Script compiled and cached with ID: " + id);
} catch (Exception e) {
System.err.println("Compilation failed for script ID " + id + ": " + e.getMessage());
}
}
/**
* 执行缓存的脚本。
*
* @param id 脚本的唯一标识符
* @param params 脚本执行时的参数,作为变量绑定
* @return 脚本执行的返回值
*/
public Object execute(String id, Map<String, Object> params) {
Script scriptInstance = instanceCache.get(id);
if (scriptInstance == null) {
throw new IllegalArgumentException("No script instance found for ID: " + id);
}
// 创建新的 Binding 并设置参数
Binding binding = new Binding(params);
scriptInstance.setBinding(binding);
// 执行脚本
return scriptInstance.run();
}
}
2. Groovy 脚本
def greet() {
return "Hello, ${name}!"
}
3. main方法
你可以使用 GroovyScriptManager
来编译和执行这个脚本:
public class Main {
public static void main(String[] args) {
GroovyScriptManager manager = new GroovyScriptManager();
String scriptId = "greetScript";
String scriptContent = "def greet() { return \"Hello, ${name}!\" }";
manager.compileScript(scriptId, scriptContent);
Map<String, Object> params = new HashMap<>();
params.put("name", "Alice");
Object result = manager.execute(scriptId, params);
System.out.println(result); // 输出: Hello, Alice!
}
}
3.2. 讲解
GroovyScriptManager
是一个用于管理和执行 Groovy 脚本的工具类。它主要提供两个功能:
- 编译和缓存 Groovy 脚本。
- 执行缓存的脚本,并支持通过
Binding
传递参数。
缓存属性:
classCache
:这是一个Map<String, Class<?>>
,用于缓存编译后的脚本类。键是脚本的唯一标识符(id
),值是编译后的脚本类。这样设计的目的是避免重复编译相同的脚本,从而提高效率。instanceCache
:这是一个Map<String, Script>
,用于缓存脚本实例。每个实例是Script
类的一个具体子类的对象,负责执行脚本的逻辑。
1. 编译方法
public void compileScript(String id, String content) {
try (GroovyClassLoader loader = new GroovyClassLoader()) {
Class<?> scriptClass = loader.parseClass(content);
classCache.put(id, scriptClass);
Script scriptInstance = InvokerHelper.createScript((Class<Script>) scriptClass, new Binding());
instanceCache.put(id, scriptInstance);
System.out.println("Script compiled and cached with ID: " + id);
} catch (Exception e) {
System.err.println("Compilation failed for script ID " + id + ": " + e.getMessage());
}
}
- 功能:编译传入的脚本文本,并将结果缓存。
参数:
id
:脚本的唯一标识符,用于在缓存中存储和检索。content
:脚本文本内容,包含需要编译的 Groovy 代码。
流程:
- 创建 GroovyClassLoader:用于动态加载和编译 Groovy 脚本。
- 编译脚本:调用
parseClass
将脚本文本编译成一个 Java 类。 - 缓存类和实例:将编译后的类缓存到
classCache
,同时创建一个Script
实例并缓存到instanceCache
。 - 异常处理:捕获编译过程中可能发生的异常,并输出错误信息。
2. 执行方法
public Object execute(String id, Map<String, Object> params) {
Script scriptInstance = instanceCache.get(id);
if (scriptInstance == null) {
throw new IllegalArgumentException("No script instance found for ID: " + id);
}
Binding binding = new Binding(params);
scriptInstance.setBinding(binding);
return scriptInstance.run();
}
- 功能:执行缓存的脚本,并通过
Binding
传递参数。 参数:
id
:脚本的唯一标识符,用于从缓存中检索Script
实例。params
:包含脚本执行时需要的参数的Map
。这些参数将作为变量绑定到脚本中。
流程:
- 检索 Script 实例:从
instanceCache
中获取对应的Script
实例。如果实例不存在,抛出IllegalArgumentException
。 - 创建 Binding:使用传入的
params
创建一个新的Binding
对象。 - 设置 Binding:将
Binding
设置到Script
实例中,以便在脚本中使用这些参数。 - 执行脚本:调用
run
方法执行脚本,并返回结果。
- 检索 Script 实例:从
3. 设计选择和改进点
缓存设计:
- 通过缓存编译后的类和实例,减少了重复编译的开销,提高了性能。可以进一步优化缓存策略,如使用 LRU 缓存来限制缓存大小。
异常处理:
- 当前的异常处理仅仅是打印错误信息。在实际应用中,可以考虑使用日志记录系统,并提供更详细的错误信息或采取恢复措施。
灵活性:
- 通过
Binding
传递参数,使得脚本的执行更加灵活。可以考虑扩展支持更多的脚本上下文或环境配置。
- 通过
线程安全:
- 当前实现并未考虑多线程环境。如果需要在多线程环境中使用,可能需要对缓存访问进行同步处理。
这种设计提供了一个简洁而灵活的方式来管理和执行 Groovy 脚本,适合在需要动态脚本执行的场景中使用。编译方法执行的前提是,需要业务上判断脚本内容发生变更了,再调用编译方法,因为编译生成新Class本身总有开销。判断脚本内容发生变更,可以通过md5等比对,或者业务上通过版本控制等机制。
4. 核心类
4.1. Script
是的,Script
是 Groovy 中的一个基类,用于表示一个 Groovy 脚本。每当你编译一个 Groovy 脚本时,Groovy 会为该脚本生成一个类,这个类是 Script
类的子类。通过 InvokerHelper.createScript
方法,你可以创建这个子类的一个实例,这个实例就是你的 Groovy 脚本在 Java 中的对象表示。
1. Script
实例的作用
执行脚本:
Script
实例可以通过调用其run()
方法来执行脚本的内容。这个方法是Script
类中的一个抽象方法,具体的实现由 Groovy 在编译脚本时生成的子类提供。
绑定变量:
Script
实例可以持有一个Binding
对象,用于在脚本中访问和修改变量。你可以在创建Script
实例时传递一个Binding
对象,或者通过setBinding()
方法设置。
访问方法和属性:
- 在 Groovy 脚本中定义的方法和属性会成为
Script
实例的方法和属性。你可以通过调用这些方法或访问这些属性来与脚本进行交互。
- 在 Groovy 脚本中定义的方法和属性会成为
2. 示例
假设你有一个简单的 Groovy 脚本:
def greet(name) {
return "Hello, $name!"
}
println "Script is running"
编译和执行这个脚本的 Java 代码可能如下:
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
public class GroovyScriptExample {
public static void main(String[] args) {
String scriptContent =
"def greet(name) {\n" +
" return \"Hello, $name!\"\n" +
"}\n" +
"println \"Script is running\"";
try (GroovyClassLoader classLoader = new GroovyClassLoader()) {
// 编译脚本并获取类
Class<?> scriptClass = classLoader.parseClass(scriptContent);
// 创建脚本实例
Script scriptInstance = InvokerHelper.createScript((Class<Script>) scriptClass, new Binding());
// 执行脚本的 run 方法
scriptInstance.run();
// 调用脚本中的 greet 方法
Object result = scriptInstance.invokeMethod("greet", "World");
System.out.println(result); // 输出: Hello, World!
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 说明
创建实例:
InvokerHelper.createScript
用于创建 Groovy 脚本的实例,该实例是Script
类的一个具体子类。
执行和交互:
run()
方法用于执行脚本中的所有代码。invokeMethod("greet", "World")
用于调用脚本中定义的greet
方法。
通过这种方式,你可以在 Java 环境中编译和执行 Groovy 脚本,并与之交互。
4.2. GroovyClassLoader
GroovyClassLoader
是 Groovy 提供的一个强大工具,用于动态加载和执行 Groovy 脚本。它提供了灵活的脚本执行能力,使得在 Java 应用中可以实现动态的业务逻辑和插件化架构。在实际应用中,合理使用 GroovyClassLoader
可以显著提升系统的灵活性和扩展性。
1. 核心功能
动态编译和加载:
GroovyClassLoader
可以在运行时编译 Groovy 脚本,并将其作为 Java 类加载到 JVM 中。这使得应用可以动态地执行 Groovy 代码。
脚本缓存:
- 它提供了一种机制,可以缓存已经编译的类,以提高执行效率。
多种输入源:
- 支持从多种来源加载脚本,包括字符串、文件、URL 等。这使得它在处理动态内容时非常灵活。
类路径管理:
- 可以设置自定义的类路径,以便在编译和执行脚本时查找所需的类和资源。
使用场景:
- 动态脚本执行:适用于需要在运行时动态执行脚本的应用,如规则引擎、测试框架等。
- 插件系统:可以用于实现动态插件系统,允许加载和执行外部提供的脚本或模块。
- 原型开发:在开发阶段快速测试和迭代代码,而无需每次都重新编译和部署应用。
2. 示例代码
以下是一个使用 GroovyClassLoader
动态加载和执行 Groovy 脚本的简单示例:
import groovy.lang.GroovyClassLoader;
public class GroovyClassLoaderExample {
public static void main(String[] args) {
// 创建一个 GroovyClassLoader 实例
GroovyClassLoader classLoader = new GroovyClassLoader();
// 定义一个简单的 Groovy 脚本
String script = "class Greeter { String greet(String name) { return 'Hello, ' + name + '!' } }";
try {
// 使用 GroovyClassLoader 加载脚本并获取类对象
Class<?> groovyClass = classLoader.parseClass(script);
// 创建类的实例
Object greeter = groovyClass.newInstance();
// 调用类的方法
String result = (String) groovyClass.getMethod("greet", String.class).invoke(greeter, "World");
// 输出结果
System.out.println(result); // 输出: Hello, World!
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭 GroovyClassLoader 以释放资源
classLoader.close();
}
}
}
3. 详细说明
创建 GroovyClassLoader:
GroovyClassLoader
是一个自定义的类加载器,扩展自 Java 的ClassLoader
。它用于加载和解析 Groovy 脚本。
编译脚本:
- 使用
parseClass
方法将 Groovy 脚本编译为 Java 类。该方法会返回一个Class
对象,可以用于创建实例和调用方法。
- 使用
实例化和方法调用:
- 使用反射机制创建脚本类的实例,并调用其方法。这与 Java 反射 API 的使用方式相似。
资源管理:
- 在使用完
GroovyClassLoader
后,调用close
方法以释放相关资源,避免内存泄漏。
- 在使用完
注意事项:
- 性能:动态编译和加载会带来一定的性能开销,因此在频繁执行的场景中,需要权衡使用的频率和必要性。
- 安全性:在加载和执行外部脚本时,需注意脚本的安全性,防止执行恶意代码。
- 类路径:确保所有需要的类和资源都在类路径中,以避免运行时错误。
4.3. Binding
Binding
是 Groovy 提供的一个类,用于在脚本执行时管理和访问变量的上下文。它是 Groovy 脚本与其外部环境之间的桥梁,允许在脚本中使用外部传入的变量。
Binding 的核心功能:
变量存储:
Binding
充当一个简单的键值存储,允许你将变量绑定到脚本中。这些变量可以在脚本中直接访问和使用。
动态变量管理:
- 你可以在脚本执行之前或执行期间动态地添加、修改和删除变量。这使得脚本的执行非常灵活。
作用域共享:
- 通过
Binding
,不同的脚本实例可以共享相同的变量上下文,或通过不同的Binding
实例来隔离变量作用域。
- 通过
1. 主要方法
构造方法
Binding()
:创建一个空的Binding
实例。Binding(Map<String, Object> variables)
:使用给定的变量集合初始化Binding
。
变量管理方法
setVariable(String name, Object value)
:将一个变量添加到Binding
中,或更新已存在的变量。getVariable(String name)
:从Binding
中获取指定名称的变量值。hasVariable(String name)
:检查Binding
中是否存在指定名称的变量。getVariables()
:返回Binding
中所有变量的Map
。
2. 使用示例
假设我们有一个简单的 Groovy 脚本,使用 Binding
中的变量:
def greet() {
return "Hello, ${name}!"
}
在 Java 中,你可以使用 Binding
来传递变量:
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import java.util.HashMap;
import java.util.Map;
public class GroovyBindingExample {
public static void main(String[] args) {
String scriptContent =
"def greet() {\n" +
" return \"Hello, ${name}!\"\n" +
"}\n";
try (GroovyClassLoader classLoader = new GroovyClassLoader()) {
// 编译脚本并获取类
Class<?> scriptClass = classLoader.parseClass(scriptContent);
// 创建 Binding 并设置变量
Binding binding = new Binding();
binding.setVariable("name", "Alice");
// 创建脚本实例并设置 Binding
Script scriptInstance = InvokerHelper.createScript((Class<Script>) scriptClass, binding);
// 执行脚本
Object result = scriptInstance.invokeMethod("greet", null);
System.out.println(result); // 输出: Hello, Alice!
// 修改 Binding 中的变量
binding.setVariable("name", "Bob");
result = scriptInstance.invokeMethod("greet", null);
System.out.println(result); // 输出: Hello, Bob!
} catch (Exception e) {
e.printStackTrace();
}
}
}
初始化 Binding:
Binding
可以通过无参构造方法创建一个空的实例,或者通过一个Map
初始化变量。
设置变量:
- 使用
setVariable
方法可以将变量存入Binding
,这些变量在脚本中可以通过名称直接访问。
- 使用
获取变量:
getVariable
方法用于检索Binding
中的变量值。
变量作用域:
- 变量在
Binding
中是全局的,即在同一个Script
实例的所有方法中都可以访问到。
- 变量在
动态性:
- 你可以在脚本执行过程中动态地修改
Binding
中的变量,这将直接影响脚本的执行结果。
- 你可以在脚本执行过程中动态地修改
3. 使用场景
- 参数传递:
Binding
常用于在脚本和外部 Java 代码之间传递参数。 - 状态管理:通过
Binding
可以在脚本中管理和维护执行状态。 - 隔离环境:通过不同的
Binding
实例可以为不同的脚本执行提供隔离的变量环境。
4.4. InvokerHelper
InvokerHelper.createScript
的核心在于通过反射机制,将编译后的 Groovy 脚本类与执行上下文(Binding
)结合,生成一个可运行的 Script
实例。这种设计利用了 Java 的反射特性和 Groovy 的动态语言特性,使得 Java 应用程序能够在运行时灵活地加载和执行 Groovy 脚本。
1. 脚本编译与执行的基本流程
脚本编译:
- Groovy 脚本首先通过
GroovyClassLoader
等工具被编译成 Java 字节码。这会生成一个继承自groovy.lang.Script
的 Java 类。 - 这个过程利用了 Groovy 编译器将动态语言代码转换为可以在 JVM 上运行的字节码。
- Groovy 脚本首先通过
创建 Script 实例:
InvokerHelper.createScript
方法的核心是通过反射机制创建一个Script
子类的实例。- 它需要一个
Class<? extends Script>
类型的参数,这个类是由前面的编译步骤生成的。
设置执行上下文:
- 在创建
Script
实例时,Binding
对象作为参数传递给构造函数。Binding
包含了脚本执行时需要的变量和它们的值。 Script
类中有一个setBinding(Binding binding)
方法,用于设置或更新脚本的执行上下文。
- 在创建
执行脚本:
- 创建的
Script
实例可以通过调用其run()
方法来执行脚本的顶级代码,或者使用invokeMethod(String name, Object args)
来调用特定的方法。
- 创建的
2. 实现细节
虽然具体的源码实现可能会因为版本变化而不同,但通常会包含以下步骤:
反射机制:
- 使用 Java 的反射 API,通过调用
Class.newInstance()
方法来实例化编译后的Script
子类。 - 反射机制允许在运行时动态地创建对象,即使在编译时不知道其具体类型。
- 使用 Java 的反射 API,通过调用
绑定上下文:
- 在实例化
Script
对象后,通过setBinding
方法将Binding
对象与Script
实例关联。 - 这一步确保了脚本在执行时可以访问到
Binding
中定义的变量。
- 在实例化
辅助工具:
InvokerHelper
提供了一些静态方法来简化对 Groovy 对象的操作,其中包括createScript
。这些方法隐藏了复杂的反射调用细节,使得 API 更加简洁和易用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。