53、Java 脚本编程全解析

Java 脚本编程全解析

1. 更改默认脚本上下文

在 Java 脚本编程中,你可以使用 ScriptEngine getContext() setContext() 方法分别获取和设置默认上下文。以下是具体操作步骤:
1. 创建 ScriptEngineManager ScriptEngine

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
  1. 获取默认上下文:
ScriptContext defaultCtx = engine.getContext();
  1. 创建新的上下文:
ScriptContext ctx = new SimpleScriptContext();
  1. 配置新上下文,这里可以根据需求进行具体配置。
  2. 将新上下文设置为引擎的默认上下文:
engine.setContext(ctx);

需要注意的是,为 ScriptEngine 设置新的默认上下文时,不会使用 ScriptEngineManager Bindings 作为全局作用域的 Bindings 。如果你希望新的默认上下文使用 ScriptEngineManager Bindings ,需要显式设置:

ctx.setBindings(manager.getBindings(), ScriptContext.GLOBAL_SCOPE);
engine.setContext(ctx);

1.1 脚本引擎上下文键值含义

ScriptEngine 接口中的常量 值的含义
“javax.script.language” ScriptEngine.LANGUAGE 脚本引擎支持的语言名称
“javax.script.language_version” ScriptEngine.LANGUAGE_VERSION 引擎支持的脚本语言版本
“javax.script.name” ScriptEngine.NAME 脚本语言的简称

2. 将脚本输出发送到文件

你可以自定义脚本执行的输入源、输出目标和错误输出目标。要实现将脚本输出写入文件,需要为用于执行脚本的 ScriptContext 设置适当的读取器和写入器。以下是具体步骤:
1. 创建 FileWriter

FileWriter writer = new FileWriter("output.txt");
  1. 获取引擎的默认上下文:
ScriptContext defaultCtx = engine.getContext();
  1. 为引擎的默认上下文设置输出写入器:
defaultCtx.setWriter(writer);

以下是完整的示例代码:

// CustomScriptOutput.java
package com.jdojo.script;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class CustomScriptOutput {
    public static void main(String[] args) {
        // Get the Groovy engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        // Print the absolute path of the output file
        File outputFile = new File("output.txt");
        System.out.println("Script output will be written to " + outputFile.getAbsolutePath());

        try (FileWriter writer = new FileWriter(outputFile)) {
            // Set a custom output writer for the engine
            ScriptContext defaultCtx = engine.getContext();
            defaultCtx.setWriter(writer);

            // Execute a script
            String script = "println('Hello custom output writer')";
            engine.eval(script);
        } catch (IOException | ScriptException e) {
            e.printStackTrace();
        }
    }
}

2.1 注意事项

设置 ScriptContext 的自定义输出写入器不会影响 Java 应用程序的标准输出目标。要重定向 Java 应用程序的标准输出,需要使用 System.setOut() 方法。

3. 调用脚本中的过程

脚本语言可能允许创建过程、函数和方法,Java 脚本 API 允许从 Java 应用程序中调用这些过程。但并非所有脚本引擎都支持过程调用,Groovy 引擎支持此功能。调用过程的步骤如下:
1. 检查脚本引擎是否支持过程调用:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
if (engine instanceof Invocable) {
    System.out.println("Invoking procedures is supported.");
} else {
    System.out.println("Invoking procedures is not supported.");
}
  1. 将引擎引用转换为 Invocable 类型:
Invocable inv = (Invocable) engine;
  1. 评估包含过程源代码的脚本:
String script = "def add(n1, n2) { n1 + n2 }";
engine.eval(script);
  1. 调用过程或函数:
Object result = inv.invokeFunction("add", 30, 40);

3.1 调用函数示例

以下是一个完整的调用函数的示例代码:

// InvokeFunction.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InvokeFunction {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        // Make sure the script engine implements the Invocable interface
        if (!(engine instanceof Invocable)) {
            System.out.println("Invoking procedures is not supported.");
            return;
        }

        // Cast the engine reference to the Invocable type
        Invocable inv = (Invocable) engine;

        try {
            String script = "def add(n1, n2) { n1 + n2 }";
            // Evaluate the script first
            engine.eval(script);

            // Invoke the add function twice
            Object result1 = inv.invokeFunction("add", 30, 40);
            System.out.println("Result1 = " + result1);
            Object result2 = inv.invokeFunction("add", 10, 20);
            System.out.println("Result2 = " + result2);
        } catch (ScriptException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

3.2 调用对象方法

对于面向对象或基于对象的脚本语言,你可以使用 Invocable 接口的 invokeMethod() 方法调用对象的方法。以下是调用对象方法的示例代码:

// InvokeMethod.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InvokeMethod {
    public static void main(String[] args) {
        // Get the Groovy engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        // Make sure the script engine implements the Invocable interface
        if (!(engine instanceof Invocable)) {
            System.out.println("Invoking methods is not supported.");
            return;
        }

        // Cast the engine reference to the Invocable type
        Invocable inv = (Invocable) engine;

        try {
            // Declare a global object with an add() method
            String script = """
                  class Calculator {
                  def add(int n1, int n2){n1 + n2}
                 }
                calculator = new Calculator()
                """;
            // Evaluate the script first
            engine.eval(script);

            // Get the calculator object reference created in the script
            Object calculator = engine.get("calculator");

            // Invoke the add() method on the calculator object
            Object result = inv.invokeMethod(calculator, "add", 30, 40);
            System.out.println("Result = " + result);
        } catch (ScriptException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

3.3 性能提示

使用 Invocable 接口可以重复执行过程、函数和方法。评估包含这些内容的脚本会将中间代码存储在引擎中,从而在重复执行时提高性能。

4. 在脚本中实现 Java 接口

Java 脚本 API 允许在脚本语言中实现 Java 接口。 Invocable 接口的 getInterface() 方法有两个版本,用于获取在脚本中实现的 Java 接口实例:
- <T> T getInterface(Class<T> cls) :用于获取方法在脚本中以顶级过程实现的 Java 接口实例。
- <T> T getInterface(Object obj, Class<T> cls) :用于获取方法在脚本中以对象实例方法实现的 Java 接口实例。

4.1 顶级过程实现接口示例

以下是使用顶级过程在 Groovy 中实现 Java 接口的示例代码:

// Calculator.java
package com.jdojo.script;
public interface Calculator {
    int add (int n1, int n2);
    int subtract (int n1, int n2);
}

// UsingInterfaces.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class UsingInterfaces {
    public static void main(String[] args) {
        // Get the Groovy engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        // Make sure the script engine implements Invocable interface
        if (!(engine instanceof Invocable)) {
            System.out.println("Interface implementation in script is not supported.");
            return;
        }

        // Cast the engine reference to the Invocable type
        Invocable inv = (Invocable) engine;

        // Create the script for add() and subtract() functions
        String script = """
            def add(n1, n2) { n1 + n2 }
            def subtract(n1, n2) { n1 - n2 }
        """;

        try { 
            // Compile the script that will be stored in the engine
            engine.eval(script);

            // Get the interface implementation
            Calculator calc = inv.getInterface(Calculator.class);
            if (calc == null) {
                System.err.println("Calculator interface implementation not found.");
                return;
            }

            int result1 = calc.add(15, 10);
            System.out.println("add(15, 10) = " + result1);
            int result2 = calc.subtract(15, 10);
            System.out.println("subtract(15, 10) = " + result2);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

4.2 对象实例方法实现接口示例

以下是使用对象实例方法在 Groovy 中实现 Java 接口的示例代码:

// ScriptObjectImplInterface.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class ScriptObjectImplInterface {
    public static void main(String[] args) {
        // Get the Groovy engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        // Make sure the engine implements the Invocable interface 
        if (!(engine instanceof Invocable)) {
            System.out.println("Interface implementation in script is not supported.");
            return;
        }

        // Cast the engine reference to the Invocable type
        Invocable inv = (Invocable) engine;

        String script = """
          class GCalculator {
            def add(int n1, int n2){n1 + n2}
            def subtract(int n1, int n2){n1 + n2}
          }
          calculator = new GCalculator()
        """;

        try {
            // Compile and store the script in the engine
            engine.eval(script);

            // Get the reference of the global script object calc
            Object calc = engine.get("calculator");

            // Get the implementation of the Calculator interface
            Calculator calculator = inv.getInterface(calc, Calculator.class);
            if (calculator == null) {
                System.err.println("Calculator interface implementation not found.");
                return;
            }

            int result1 = calculator.add(15, 10);
            System.out.println("add(15, 10) = " + result1);
            int result2 = calculator.subtract(15, 10);
            System.out.println("subtract(15, 10) = " + result2);
        } catch (ScriptException e) {
            e.printStackTrace();
        } 
    }
}

5. 使用编译后的脚本

部分脚本引擎支持编译脚本并重复执行,这可以提高应用程序的性能。支持脚本编译的引擎必须实现 Compilable 接口,Groovy 引擎支持此功能。使用编译脚本的步骤如下:
1. 检查脚本引擎是否支持编译:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("YOUR_ENGINE_NAME");
if (engine instanceof Compilable) {
    System.out.println("Script compilation is supported.");
} else {
    System.out.println("Script compilation is not supported.");
}
  1. 将引擎引用转换为 Compilable 类型:
Compilable comp = (Compilable) engine;
  1. 编译脚本:
CompiledScript cScript = comp.compile(script);
  1. 执行编译后的脚本:
Object result = cScript.eval();

5.1 编译脚本示例

以下是一个完整的使用编译脚本的示例代码:

// CompilableTest.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class CompilableTest {
    public static void main(String[] args) {
        // Get the Groovy engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");

        if (!(engine instanceof Compilable)) {
            System.out.println("Script compilation not supported.");
            return;
        }

        // Cast the engine reference to the Compilable type
        Compilable comp = (Compilable) engine;

        try {
            // Compile a script
            String script = "println(n1 + n2)";
            CompiledScript cScript = comp.compile(script);

            // Store n1 and n2 script variables in a Bindings
            Bindings scriptParams = engine.createBindings();
            scriptParams.put("n1", 2);
            scriptParams.put("n2", 3);
            cScript.eval(scriptParams);

            // Execute the script again with different values for n1 and n2
            scriptParams.put("n1", 9);
            scriptParams.put("n2", 7);
            cScript.eval(scriptParams);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

6. 在脚本语言中使用 Java

脚本语言允许在脚本中使用 Java 类库,不同脚本语言使用 Java 类的语法不同。这里以 Groovy 为例,介绍使用 Java 构造的语法。

6.1 变量声明

在 Groovy 中,使用 def 关键字声明变量。如果省略 def 关键字,变量在脚本范围内可访问,但在脚本内声明的类中不可访问,并且在脚本处理后其值可从 Java 访问。示例代码如下:

// Declare a variable named msg using the def keyword
def msg = "Hello";
// Declare a variable named greeting without using the keyword def.
greeting = "Welcome";

6.2 流程图:调用脚本过程

graph TD;
    A[检查脚本引擎是否支持过程调用] --> B{支持?};
    B -- 是 --> C[将引擎引用转换为 Invocable 类型];
    B -- 否 --> D[结束];
    C --> E[评估包含过程源代码的脚本];
    E --> F[调用过程或函数];

6.3 流程图:使用编译脚本

graph TD;
    A[检查脚本引擎是否支持编译] --> B{支持?};
    B -- 是 --> C[将引擎引用转换为 Compilable 类型];
    B -- 否 --> D[结束];
    C --> E[编译脚本];
    E --> F[执行编译后的脚本];

通过以上内容,你可以全面了解 Java 脚本编程的各种功能和操作方法,包括更改默认脚本上下文、将脚本输出发送到文件、调用脚本中的过程、在脚本中实现 Java 接口、使用编译后的脚本以及在脚本语言中使用 Java 等。希望这些内容对你的 Java 脚本编程学习和实践有所帮助。

7. 总结与操作步骤回顾

7.1 操作步骤总结

为了方便大家更好地理解和应用前面介绍的 Java 脚本编程知识,下面对各项操作的步骤进行总结:
| 操作内容 | 操作步骤 |
| ---- | ---- |
| 更改默认脚本上下文 | 1. 创建 ScriptEngineManager ScriptEngine ;2. 获取默认上下文;3. 创建新的上下文;4. 配置新上下文;5. 将新上下文设置为引擎的默认上下文;若希望新上下文使用 ScriptEngineManager Bindings ,需显式设置。 |
| 将脚本输出发送到文件 | 1. 创建 FileWriter ;2. 获取引擎的默认上下文;3. 为引擎的默认上下文设置输出写入器。 |
| 调用脚本中的过程 | 1. 检查脚本引擎是否支持过程调用;2. 将引擎引用转换为 Invocable 类型;3. 评估包含过程源代码的脚本;4. 调用过程或函数。 |
| 在脚本中实现 Java 接口 | - 顶级过程实现:1. 检查脚本引擎是否实现 Invocable 接口;2. 转换引擎引用;3. 编写脚本;4. 评估脚本;5. 获取接口实现。
- 对象实例方法实现:1. 检查脚本引擎是否实现 Invocable 接口;2. 转换引擎引用;3. 编写脚本;4. 评估脚本;5. 获取脚本对象引用;6. 获取接口实现。 |
| 使用编译后的脚本 | 1. 检查脚本引擎是否支持编译;2. 将引擎引用转换为 Compilable 类型;3. 编译脚本;4. 执行编译后的脚本。 |

7.2 代码示例总结

以下是前面介绍的各项操作的代码示例汇总:

更改默认脚本上下文
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Groovy");
ScriptContext defaultCtx = engine.getContext();
ScriptContext ctx = new SimpleScriptContext();
ctx.setBindings(manager.getBindings(), ScriptContext.GLOBAL_SCOPE);
engine.setContext(ctx);
将脚本输出发送到文件
// CustomScriptOutput.java
package com.jdojo.script;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class CustomScriptOutput {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");
        File outputFile = new File("output.txt");
        System.out.println("Script output will be written to " + outputFile.getAbsolutePath());
        try (FileWriter writer = new FileWriter(outputFile)) {
            ScriptContext defaultCtx = engine.getContext();
            defaultCtx.setWriter(writer);
            String script = "println('Hello custom output writer')";
            engine.eval(script);
        } catch (IOException | ScriptException e) {
            e.printStackTrace();
        }
    }
}
调用脚本中的过程
// InvokeFunction.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InvokeFunction {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");
        if (!(engine instanceof Invocable)) {
            System.out.println("Invoking procedures is not supported.");
            return;
        }
        Invocable inv = (Invocable) engine;
        try {
            String script = "def add(n1, n2) { n1 + n2 }";
            engine.eval(script);
            Object result1 = inv.invokeFunction("add", 30, 40);
            System.out.println("Result1 = " + result1);
            Object result2 = inv.invokeFunction("add", 10, 20);
            System.out.println("Result2 = " + result2);
        } catch (ScriptException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
在脚本中实现 Java 接口(顶级过程实现)
// Calculator.java
package com.jdojo.script;
public interface Calculator {
    int add (int n1, int n2);
    int subtract (int n1, int n2);
}

// UsingInterfaces.java
package com.jdojo.script;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class UsingInterfaces {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");
        if (!(engine instanceof Invocable)) {
            System.out.println("Interface implementation in script is not supported.");
            return;
        }
        Invocable inv = (Invocable) engine;
        String script = """
            def add(n1, n2) { n1 + n2 }
            def subtract(n1, n2) { n1 - n2 }
        """;
        try { 
            engine.eval(script);
            Calculator calc = inv.getInterface(Calculator.class);
            if (calc == null) {
                System.err.println("Calculator interface implementation not found.");
                return;
            }
            int result1 = calc.add(15, 10);
            System.out.println("add(15, 10) = " + result1);
            int result2 = calc.subtract(15, 10);
            System.out.println("subtract(15, 10) = " + result2);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}
使用编译后的脚本
// CompilableTest.java
package com.jdojo.script;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class CompilableTest {
    public static void main(String[] args) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("Groovy");
        if (!(engine instanceof Compilable)) {
            System.out.println("Script compilation not supported.");
            return;
        }
        Compilable comp = (Compilable) engine;
        try {
            String script = "println(n1 + n2)";
            CompiledScript cScript = comp.compile(script);
            Bindings scriptParams = engine.createBindings();
            scriptParams.put("n1", 2);
            scriptParams.put("n2", 3);
            cScript.eval(scriptParams);
            scriptParams.put("n1", 9);
            scriptParams.put("n2", 7);
            cScript.eval(scriptParams);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

7.3 综合流程图:Java 脚本编程操作流程

graph LR;
    A[开始] --> B{选择操作};
    B -- 更改默认脚本上下文 --> C[执行更改步骤];
    B -- 将脚本输出发送到文件 --> D[执行输出步骤];
    B -- 调用脚本中的过程 --> E[执行调用步骤];
    B -- 在脚本中实现 Java 接口 --> F[选择实现方式];
    F -- 顶级过程实现 --> G[执行顶级过程实现步骤];
    F -- 对象实例方法实现 --> H[执行对象实例方法实现步骤];
    B -- 使用编译后的脚本 --> I[执行编译脚本步骤];
    C --> J[结束];
    D --> J;
    E --> J;
    G --> J;
    H --> J;
    I --> J;

8. 注意事项与常见问题解答

8.1 注意事项

  • 上下文设置 :设置新的默认上下文时,不会自动使用 ScriptEngineManager Bindings 作为全局作用域的 Bindings ,如需使用需显式设置。
  • 输出重定向 :设置 ScriptContext 的自定义输出写入器不会影响 Java 应用程序的标准输出目标,要重定向标准输出需使用 System.setOut() 方法。
  • 接口实现 :在使用 getInterface() 方法获取接口实现时,要确保脚本引擎实现了 Invocable 接口,否则可能会导致错误。
  • 脚本编译 :并非所有脚本引擎都支持脚本编译,支持的引擎必须实现 Compilable 接口。

8.2 常见问题解答

问题 1:为什么调用脚本中的过程时提示不支持?

解答:可能是因为所使用的脚本引擎不支持过程调用。在调用前,需要检查脚本引擎是否实现了 Invocable 接口,只有实现了该接口的引擎才支持过程调用。

问题 2:将脚本输出发送到文件后,控制台仍然有输出,如何解决?

解答:设置 ScriptContext 的自定义输出写入器不会影响 Java 应用程序的标准输出目标。若要重定向标准输出,需要使用 System.setOut() 方法。

问题 3:在脚本中实现 Java 接口时,获取的接口实现为 null 怎么办?

解答:这可能是因为脚本编写有误,或者脚本引擎在解析脚本时出现问题。需要检查脚本内容是否正确实现了接口的方法,以及脚本引擎是否正确评估了脚本。

9. 总结与展望

9.1 总结

通过前面的介绍,我们详细了解了 Java 脚本编程的多个方面,包括更改默认脚本上下文、将脚本输出发送到文件、调用脚本中的过程、在脚本中实现 Java 接口、使用编译后的脚本以及在脚本语言中使用 Java 等功能。每个功能都有其特定的操作步骤和注意事项,并且通过代码示例和流程图进行了直观的展示。

9.2 展望

Java 脚本编程为 Java 应用程序提供了更加灵活和动态的功能扩展方式。随着技术的不断发展,脚本引擎的性能和功能也会不断提升。未来,我们可以期待在更多的应用场景中使用 Java 脚本编程,例如在自动化测试、动态配置、规则引擎等领域。同时,对于脚本编程的安全性和性能优化也将成为研究的重点方向。希望大家能够通过本文的学习,掌握 Java 脚本编程的基本技能,并在实际项目中灵活应用。

通过以上内容,我们对 Java 脚本编程有了全面而深入的了解。在实际应用中,大家可以根据具体需求选择合适的操作方法,并注意相关的注意事项,以确保程序的正确性和性能。希望这些知识能够帮助大家在 Java 脚本编程的道路上取得更好的成果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值