作為研發工程師,在研發系統時,我們總期望可以搭建一個通用的平臺,不希望牽扯進任何定製化的業務需求。
作為產品經理,不是很關心繫統功能有多強大,能實現當前業務需求的才是好系統。
二者總需要妥協,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 更加簡潔和易用。