52、Java脚本编程:Bindings、上下文与执行机制详解(上)

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;
}
  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
  1. 创建ScriptEngineManager。
  2. 向ScriptEngineManager的Bindings中添加键值对。
  3. 使用ScriptEngineManager创建ScriptEngine实例。
  4. 向ScriptEngine的引擎作用域Bindings中添加键值对。
  5. 执行脚本。
操作二:使用自定义ScriptContext执行脚本
  1. 创建ScriptEngineManager和ScriptEngine。
  2. 向ScriptEngineManager的Bindings中添加全局变量。
  3. 准备脚本。
  4. 使用不同版本的eval()方法,分别使用默认上下文、自定义Bindings和自定义ScriptContext执行脚本。
操作三:处理eval()方法的返回值
  1. 定义一个用于存储返回值的Java类。
  2. 创建该类的对象并传递给脚本。
  3. 在脚本中设置对象的值。
  4. 从对象中获取脚本的返回值。

表格总结

操作 相关方法 说明
修改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对象存储返回值
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值