Java脚本编程:Bindings、上下文与执行机制详解(上)
1. 引言
在Java编程中,脚本编程是一项强大的功能,它允许在Java应用程序中动态执行脚本代码。本文将深入探讨Java脚本编程中的Bindings、ScriptContext、ScriptEngine和ScriptEngineManager等核心概念,以及它们如何协同工作,同时介绍脚本执行的不同隔离级别和eval()方法的返回值处理。
2. 核心组件协同工作
2.1 ScriptEngineManager与Bindings
ScriptEngineManager负责维护一组键值对,存储在Bindings中。它提供了以下方法来操作这些键值对:
-
void put(String key, Object value)
:向Bindings中添加键值对。
-
Object get(String key)
:返回指定键的值,如果键不存在则返回null。
-
void setBindings(Bindings bindings)
:替换ScriptEngineManager的Bindings。
-
Bindings getBindings()
:返回ScriptEngineManager的Bindings引用。
示例代码如下:
ScriptEngineManager manager = new ScriptEngineManager();
manager.put("K1", "V1");
manager.put("K2", "V2");
manager.put("K3", "V3");
2.2 ScriptEngine与ScriptContext
每个ScriptEngine默认都有一个ScriptContext,即其默认上下文。ScriptContext除了包含读写器外,还有两个Bindings:一个在引擎作用域,一个在全局作用域。当创建ScriptEngine时,其引擎作用域的Bindings为空,全局作用域的Bindings引用创建它的ScriptEngineManager的Bindings。
以下代码创建了一个ScriptEngineManager,并使用它创建了三个ScriptEngine实例:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine1 = manager.getEngineByName("Groovy");
ScriptEngine engine2 = manager.getEngineByName("Groovy");
ScriptEngine engine3 = manager.getEngineByName("Groovy");
然后,向ScriptEngineManager的Bindings中添加三个键值对,并向每个ScriptEngine的引擎作用域Bindings中添加两个键值对:
manager.put("K1", "V1");
manager.put("K2", "V2");
manager.put("K3", "V3");
engine1.put("KE11", "VE11");
engine1.put("KE12", "VE12");
engine2.put("KE21", "VE21");
engine2.put("KE22", "VE22");
engine3.put("KE31", "VE31");
engine3.put("KE32", "VE32");
2.3 作用域Bindings的修改
ScriptEngineManager的Bindings可以通过以下方式修改:
- 使用ScriptEngineManager的put()方法。
- 通过ScriptEngineManager的getBindings()方法获取Bindings引用,然后使用put()和remove()方法。
- 通过ScriptEngine的getBindings()方法获取其默认上下文全局作用域的Bindings引用,然后使用put()和remove()方法。
当ScriptEngineManager的Bindings被修改时,由该ScriptEngineManager创建的所有ScriptEngine的默认上下文的全局作用域Bindings都会被修改,因为它们共享相同的Bindings。
以下是向ScriptEngine的引擎作用域Bindings中添加键值对的示例:
ScriptEngine engine1 = null; // 获取一个引擎
engine1.put("engineName", "Engine-1");
获取引擎作用域Bindings中指定键的值:
String eName = (String) engine1.get("engineName");
获取全局作用域Bindings并修改的示例:
Bindings e1Global = engine1.getBindings(ScriptContext.GLOBAL_SCOPE);
e1Global.put("id", 89999);
3. 示例代码演示
以下是一个完整的示例,展示了如何使用全局和引擎作用域的Bindings:
// GlobalBindings.java
package com.jdojo.script;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class GlobalBindings {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
manager.put("n1", 100);
manager.put("n2", 200);
ScriptEngine engine1 = manager.getEngineByName("Groovy");
engine1.put("engineName", "Engine-1");
ScriptEngine engine2 = manager.getEngineByName("Groovy");
engine2.put("engineName", "Engine-2");
String script = """
def sum = n1 + n2
println(engineName + ' - Sum = ' + sum)
""";
try {
engine1.eval(script);
engine2.eval(script);
engine1.put("n2", 1000);
engine2.put("n2", 2000);
engine1.eval(script);
engine2.eval(script);
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
输出结果:
Engine-1 - Sum = 300
Engine-2 - Sum = 300
Engine-1 - Sum = 1100
Engine-2 - Sum = 2100
4. 全局作用域Bindings的复杂性
使用ScriptEngineManager的setBindings()方法或ScriptEngine的setBindings()方法会打破全局作用域的“全局性”。例如:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine1 = manager.getEngineByName("Groovy");
ScriptEngine engine2 = manager.getEngineByName("Groovy");
manager.put("n1", 100);
manager.put("n2", 200);
Bindings newGlobal = new SimpleBindings();
newGlobal.put("n3", 300);
newGlobal.put("n4", 400);
manager.setBindings(newGlobal);
此时,ScriptEngineManager有了新的Bindings,但两个ScriptEngine仍然引用旧的Bindings作为其全局作用域Bindings。
为了保持全局作用域的“全局性”,建议始终使用ScriptEngineManager的put()方法添加键值对,使用getBindings()方法获取Bindings引用并使用remove()方法移除键值对。
5. 引擎作用域Bindings的保留键
引擎作用域Bindings中的某些键是保留的,具有特殊含义。这些键也在ScriptEngine接口中声明为常量。以下是保留键的列表:
| Key | Constant in ScriptEngine Interface | Meaning of the Value of the Key |
| — | — | — |
| “javax.script.argv” | ScriptEngine.ARGV | 用于传递一组位置参数的对象数组。 |
| “javax.script.engine” | ScriptEngine.ENGINE | 脚本引擎的名称。 |
| “javax.script.engine_version” | ScriptEngine.ENGINE_VERSION | 脚本引擎的版本。 |
| “javax.script.filename” | ScriptEngine.FILENAME | 用于传递脚本源文件或资源的名称。 |
作为开发者,不应该使用这些键从Java应用程序向脚本引擎传递参数。
6. 总结
通过本文的介绍,我们了解了Java脚本编程中Bindings、ScriptContext、ScriptEngine和ScriptEngineManager的协同工作方式,以及如何操作不同作用域的Bindings。同时,我们也认识到全局作用域Bindings的复杂性和引擎作用域Bindings的保留键。在实际应用中,合理使用这些概念可以实现更灵活、高效的脚本编程。
Java脚本编程:Bindings、上下文与执行机制详解(下)
7. 使用自定义ScriptContext
7.1 eval()方法的不同版本
ScriptEngine的eval()方法有多个版本,允许使用不同的隔离级别执行脚本:
-
Object eval(String script)
和
Object eval(Reader reader)
:使用ScriptEngine的默认上下文执行脚本。
-
Object eval(String script, Bindings bindings)
和
Object eval(Reader reader, Bindings bindings)
:使用新的ScriptContext,其引擎作用域的Bindings是传入的Bindings,全局作用域的Bindings与默认上下文相同,不影响默认上下文。
-
Object eval(String script, ScriptContext context)
和
Object eval(Reader reader, ScriptContext context)
:使用指定的ScriptContext执行脚本,不影响默认上下文。
以下是一个使用不同隔离级别执行脚本的示例:
// CustomContext.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import static javax.script.SimpleScriptContext.ENGINE_SCOPE;
import static javax.script.SimpleScriptContext.GLOBAL_SCOPE;
public class CustomContext {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
manager.put("n1", 100);
String script = """
def sum = n1 + n2
println(msg + ' n1=' + n1 + ', n2=' + n2 + ', sum=' + sum)
""";
engine.put("n2", 200);
engine.put("msg", "Using the default context:");
engine.eval(script);
Bindings bindings = engine.createBindings();
bindings.put("n2", 300);
bindings.put("msg", "Using a Bindings:");
engine.eval(script, bindings);
ScriptContext ctx = new SimpleScriptContext();
Bindings ctxGlobalBindings = engine.createBindings();
ctx.setBindings(ctxGlobalBindings, GLOBAL_SCOPE);
ctx.setAttribute("n1", 400, GLOBAL_SCOPE);
ctx.setAttribute("n2", 500, ENGINE_SCOPE);
ctx.setAttribute("msg", "Using a ScriptContext:", ENGINE_SCOPE);
engine.eval(script, ctx);
engine.eval(script);
}
}
输出结果:
Using the default context: n1=100, n2=200, sum=300
Using a Bindings: n1=100, n2=300, sum=400
Using a ScriptContext: n1=400, n2=500, sum=900
Using the default context: n1=100, n2=200, sum=300
该程序使用了三个变量
msg
、
n1
和
n2
,通过不同的上下文执行脚本,证明了使用自定义Bindings或ScriptContext不会影响默认上下文的Bindings。
8. eval()方法的返回值
ScriptEngine的eval()方法返回一个Object,即脚本中的最后一个值。如果脚本中没有最后一个值,则返回null。依赖脚本的最后一个值是容易出错且容易混淆的。以下是一些使用eval()方法返回值的示例:
Object result = null;
// Assigns 3 to result
result = engine.eval("1 + 2");
// Assigns 7 to result
result = engine.eval("1 + 2; 3 + 4");
// Assigns 6 to result
result = engine.eval("""1 + 2; 3 + 4; def v = 5; v = 6""");
// Assigns 5 to result
result = engine.eval("""1 + 2; 3 + 4; def v = 5""");
// Assigns null to result
result = engine.eval("println(1 + 2)");
建议不要依赖eval()方法的返回值,而是将一个Java对象作为参数传递给脚本,让脚本将返回值存储在该对象中。以下是一个示例:
// Result.java
package com.jdojo.script;
public class Result {
public int val = -1;
}
// ResultBearingScript.java
package com.jdojo.script;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ResultBearingScript {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
Result result = new Result();
engine.put("result", result);
String script = "3 + 4; result.val = 101";
engine.eval(script);
int returnedValue = result.val;
System.out.println("Returned value is " + returnedValue);
}
}
输出结果:
Returned value is 101
9. 总结
本文详细介绍了Java脚本编程中使用自定义ScriptContext执行脚本的不同隔离级别,以及eval()方法返回值的处理。通过合理使用这些技术,可以提高脚本编程的灵活性和可靠性。在实际开发中,应根据具体需求选择合适的执行方式和返回值处理方法。
整体流程图
graph TD;
A[ScriptEngineManager] --> B[Bindings];
A --> C[ScriptEngine1];
A --> D[ScriptEngine2];
A --> E[ScriptEngine3];
B --> F[Global Scope Bindings];
C --> G[Engine Scope Bindings];
D --> H[Engine Scope Bindings];
E --> I[Engine Scope Bindings];
F --> C;
F --> D;
F --> E;
C --> J[Eval Script];
D --> K[Eval Script];
E --> L[Eval Script];
通过以上内容,我们全面了解了Java脚本编程中核心组件的协同工作、全局和引擎作用域Bindings的操作、自定义ScriptContext的使用以及eval()方法返回值的处理,为实际开发提供了有力的支持。
Java脚本编程:Bindings、上下文与执行机制详解(下)
7. 使用自定义ScriptContext
7.1 eval()方法的不同版本
ScriptEngine的eval()方法有多个版本,允许使用不同的隔离级别执行脚本,具体如下:
-
使用默认上下文
:
-
Object eval(String script)
-
Object eval(Reader reader)
这两个版本使用ScriptEngine的默认上下文执行脚本。
-
使用Bindings
:
-
Object eval(String script, Bindings bindings)
-
Object eval(Reader reader, Bindings bindings)
这两个版本使用新的ScriptContext,其引擎作用域的Bindings是传入的Bindings,全局作用域的Bindings与默认上下文相同,不影响默认上下文。
-
使用ScriptContext
:
-
Object eval(String script, ScriptContext context)
-
Object eval(Reader reader, ScriptContext context)
这两个版本使用指定的ScriptContext执行脚本,不影响默认上下文。
以下是一个使用不同隔离级别执行脚本的示例:
// CustomContext.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import static javax.script.SimpleScriptContext.ENGINE_SCOPE;
import static javax.script.SimpleScriptContext.GLOBAL_SCOPE;
public class CustomContext {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
manager.put("n1", 100);
String script = """
def sum = n1 + n2
println(msg + ' n1=' + n1 + ', n2=' + n2 + ', sum=' + sum)
""";
engine.put("n2", 200);
engine.put("msg", "Using the default context:");
engine.eval(script);
Bindings bindings = engine.createBindings();
bindings.put("n2", 300);
bindings.put("msg", "Using a Bindings:");
engine.eval(script, bindings);
ScriptContext ctx = new SimpleScriptContext();
Bindings ctxGlobalBindings = engine.createBindings();
ctx.setBindings(ctxGlobalBindings, GLOBAL_SCOPE);
ctx.setAttribute("n1", 400, GLOBAL_SCOPE);
ctx.setAttribute("n2", 500, ENGINE_SCOPE);
ctx.setAttribute("msg", "Using a ScriptContext:", ENGINE_SCOPE);
engine.eval(script, ctx);
engine.eval(script);
}
}
输出结果:
Using the default context: n1=100, n2=200, sum=300
Using a Bindings: n1=100, n2=300, sum=400
Using a ScriptContext: n1=400, n2=500, sum=900
Using the default context: n1=100, n2=200, sum=300
该程序使用了三个变量
msg
、
n1
和
n2
,通过不同的上下文执行脚本,证明了使用自定义Bindings或ScriptContext不会影响默认上下文的Bindings。
8. eval()方法的返回值
ScriptEngine的eval()方法返回一个Object,即脚本中的最后一个值。如果脚本中没有最后一个值,则返回null。依赖脚本的最后一个值是容易出错且容易混淆的。以下是一些使用eval()方法返回值的示例:
Object result = null;
// Assigns 3 to result
result = engine.eval("1 + 2");
// Assigns 7 to result
result = engine.eval("1 + 2; 3 + 4");
// Assigns 6 to result
result = engine.eval("""1 + 2; 3 + 4; def v = 5; v = 6""");
// Assigns 5 to result
result = engine.eval("""1 + 2; 3 + 4; def v = 5""");
// Assigns null to result
result = engine.eval("println(1 + 2)");
建议不要依赖eval()方法的返回值,而是将一个Java对象作为参数传递给脚本,让脚本将返回值存储在该对象中。以下是具体的操作步骤:
1. 定义一个用于存储返回值的Java类,例如
Result
类:
// Result.java
package com.jdojo.script;
public class Result {
public int val = -1;
}
-
在主程序中创建
Result对象并传递给脚本:
// ResultBearingScript.java
package com.jdojo.script;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ResultBearingScript {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
Result result = new Result();
engine.put("result", result);
String script = "3 + 4; result.val = 101";
engine.eval(script);
int returnedValue = result.val;
System.out.println("Returned value is " + returnedValue);
}
}
输出结果:
Returned value is 101
9. 总结
本文详细介绍了Java脚本编程中使用自定义ScriptContext执行脚本的不同隔离级别,以及eval()方法返回值的处理。通过合理使用这些技术,可以提高脚本编程的灵活性和可靠性。在实际开发中,应根据具体需求选择合适的执行方式和返回值处理方法。
整体流程图
graph TD;
A[ScriptEngineManager] --> B[Bindings];
A --> C[ScriptEngine1];
A --> D[ScriptEngine2];
A --> E[ScriptEngine3];
B --> F[Global Scope Bindings];
C --> G[Engine Scope Bindings];
D --> H[Engine Scope Bindings];
E --> I[Engine Scope Bindings];
F --> C;
F --> D;
F --> E;
C --> J[Eval Script];
D --> K[Eval Script];
E --> L[Eval Script];
通过以上内容,我们全面了解了Java脚本编程中核心组件的协同工作、全局和引擎作用域Bindings的操作、自定义ScriptContext的使用以及eval()方法返回值的处理,为实际开发提供了有力的支持。
总结回顾
- 核心组件协同 :ScriptEngineManager维护Bindings,ScriptEngine有默认的ScriptContext,包含引擎作用域和全局作用域的Bindings,它们相互协作完成脚本执行。
- 作用域操作 :可以通过多种方式修改全局和引擎作用域的Bindings,但要注意保持全局作用域的一致性。
- 自定义上下文 :利用eval()方法的不同版本,可以使用自定义Bindings或ScriptContext执行脚本,且不影响默认上下文。
- 返回值处理 :避免依赖eval()方法的返回值,可通过传递Java对象来存储脚本结果。
操作步骤总结
操作一:使用全局和引擎作用域Bindings
- 创建ScriptEngineManager。
- 向ScriptEngineManager的Bindings中添加键值对。
- 使用ScriptEngineManager创建ScriptEngine实例。
- 向ScriptEngine的引擎作用域Bindings中添加键值对。
- 执行脚本。
操作二:使用自定义ScriptContext执行脚本
- 创建ScriptEngineManager和ScriptEngine。
- 向ScriptEngineManager的Bindings中添加全局变量。
- 准备脚本。
- 使用不同版本的eval()方法,分别使用默认上下文、自定义Bindings和自定义ScriptContext执行脚本。
操作三:处理eval()方法的返回值
- 定义一个用于存储返回值的Java类。
- 创建该类的对象并传递给脚本。
- 在脚本中设置对象的值。
- 从对象中获取脚本的返回值。
表格总结
| 操作 | 相关方法 | 说明 |
|---|---|---|
| 修改ScriptEngineManager的Bindings | put()、getBindings()、setBindings() | 可通过多种方式修改,修改后影响所有相关ScriptEngine的全局作用域Bindings |
| 向ScriptEngine的引擎作用域Bindings添加键值对 | put() | 每个ScriptEngine的引擎作用域Bindings独立 |
| 获取引擎作用域Bindings的值 | get() | 从引擎作用域Bindings中获取指定键的值 |
| 获取全局作用域Bindings | getBindings(ScriptContext.GLOBAL_SCOPE) | 获取ScriptEngine默认上下文全局作用域的Bindings引用 |
| 执行脚本 | eval() | 有多个版本,可使用不同隔离级别执行脚本 |
| 处理eval()方法的返回值 | 传递Java对象 | 避免依赖脚本的最后一个值,通过Java对象存储返回值 |
超级会员免费看
475

被折叠的 条评论
为什么被折叠?



