杰瑞科技汇

Java如何调用JavaScript方法?

Java 提供了多种方式来实现这一目标,主要分为两大类:

Java如何调用JavaScript方法?-图1
(图片来源网络,侵删)
  1. 基于 Java 内置的引擎 (Nashorn):从 Java 8 开始,Nashorn 作为默认的 JavaScript 引擎被引入,它直接集成在 JDK 中,无需额外依赖,性能较好,适合在 JVM 内部执行 JS 代码。
  2. 基于第三方库 (如 Rhino, GraalVM):这些库提供了更强大的功能、更好的性能(特别是 GraalVM)或对特定 JS 运行时(如 Node.js)的集成。

下面我将详细介绍这两种主流方式,并提供完整的代码示例。


使用 Java 内置的 Nashorn 引擎 (推荐用于 Java 8+)

Nashorn 是一个高性能的 JavaScript 运行时,遵循 ECMAScript 5.1 规范,并支持一些 ECMAScript 6 的特性。

核心步骤

  1. 获取 ScriptEngine:通过 ScriptEngineManager 获取一个 Nashorn 引擎实例。
  2. 执行 JavaScript 代码
    • eval(String script): 执行一段 JavaScript 字符串。
    • eval(Reader reader): 执行一个 JavaScript 文件。
    • put(String key, Object value): 将 Java 对象传递给 JavaScript 作用域。
    • get(String key): 从 JavaScript 作用域获取 Java 对象。
  3. 数据类型转换:Nashorn 会自动处理 Java 和 JavaScript 之间的基本类型和集合类型的转换。

示例代码

基本执行

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornBasicExample {
    public static void main(String[] args) {
        // 1. 创建 ScriptEngineManager
        ScriptEngineManager engineManager = new ScriptEngineManager();
        // 2. 获取 Nashorn 引擎 (名称为 "nashorn" 或 "js")
        ScriptEngine engine = engineManager.getEngineByName("nashorn");
        if (engine == null) {
            System.out.println("Nashorn engine not found!");
            return;
        }
        try {
            // 3. 执行简单的 JavaScript 代码
            System.out.println("--- 执行简单表达式 ---");
            engine.eval("var x = 10;");
            engine.eval("var y = 20;");
            engine.eval("var sum = x + y;");
            System.out.println("sum 的值是: " + engine.get("sum")); // 输出: sum 的值是: 30
            // 4. 执行多行 JavaScript 代码块
            System.out.println("\n--- 执行多行代码块 ---");
            engine.eval("function greet(name) {\n" +
                        "    return 'Hello, ' + name + '!';\n" +
                        "}");
            String result = (String) engine.eval("greet('World');");
            System.out.println(result); // 输出: Hello, World!
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
}

Java 与 JavaScript 互相传值

Java如何调用JavaScript方法?-图2
(图片来源网络,侵删)
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.HashMap;
import java.util.Map;
public class NashornDataExchangeExample {
    public static void main(String[] args) {
        ScriptEngineManager engineManager = new ScriptEngineManager();
        ScriptEngine engine = engineManager.getEngineByName("nashorn");
        try {
            // --- Java 向 JavaScript 传值 ---
            System.out.println("--- Java 向 JavaScript 传值 ---");
            User user = new User("Alice", 30);
            // 将 Java 对象 'user' 放入 JavaScript 作用域,别名为 'jsUser'
            engine.put("jsUser", user);
            // 在 JavaScript 中访问 Java 对象的属性和方法
            engine.eval("print('从 JS 访问 Java 对象属性: ' + jsUser.name);");
            engine.eval("print('从 JS 调用 Java 对象方法: ' + jsUser.getGreeting());");
            // --- JavaScript 向 Java 传值 ---
            System.out.println("\n--- JavaScript 向 Java 传值 ---");
            // 执行 JS 代码,并获取返回值
            Object result = engine.eval("var myMap = {key1: 'value1', key2: 123}; myMap;");
            // JS 对象会被自动转换为 java.util.LinkedHashMap
            if (result instanceof java.util.Map) {
                Map<?, ?> resultMap = (Map<?, ?>) result;
                System.out.println("从 JS 返回的 Map: " + resultMap);
                System.out.println("key1 的值: " + resultMap.get("key1")); // 输出: value1
                System.out.println("key2 的值: " + resultMap.get("key2")); // 输出: 123
            }
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }
    // 一个简单的 Java 类
    static class User {
        public String name;
        public int age;
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getGreeting() {
            return "Hi, I'm " + this.name + " and I'm " + this.age + " years old.";
        }
    }
}

使用第三方库 (以 GraalVM 为例)

GraalVM 是一个现代的 JDK,它提供了一个名为 Graal.js 的高性能 JavaScript 引擎,相比 Nashorn,GraalVM 提供了更好的性能、对 ES6+ 更完整的支持,并且可以嵌入 Node.js 运行时。

环境准备

  1. 安装 GraalVM: 从 GraalVM 官网 下载并安装。
  2. 安装 JavaScript 插件: 使用 GraalVM Updater (gu) 命令安装 JavaScript 运行时。
    # 在 GraalVM 的 bin 目录下执行
    gu install js
  3. 配置项目:
    • 如果你使用 Maven,在 pom.xml 中添加 GraalVM 的依赖。
    • 重要: 使用 GraalVM 时,需要配置 JVM 参数来启用其原生镜像功能,或者在代码中显式指定要使用的引擎。

Maven 依赖 (pom.xml)

<dependencies>
    <!-- GraalVM JS 引擎 -->
    <dependency>
        <groupId>org.graalvm.js</groupId>
        <artifactId>js</artifactId>
        <version>23.1.0</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- GraalVM JS 插件,用于与 Nashorn 兼容的 API -->
    <dependency>
        <groupId>org.graalvm.js</groupId>
        <artifactId>js-scriptengine</artifactId>
        <version>23.1.0</version>
    </dependency>
</dependencies>

示例代码

GraalVM 提供了与 Nashorn 兼容的 API,因此代码与 Nashorn 示例非常相似,主要区别在于如何获取 ScriptEngine

import org.graalvm.js.scriptengine.GraalJSScriptEngine;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
public class GraalVMExample {
    public static void main(String[] args) {
        // GraalVM 建议通过 GraalJSScriptEngineProvider 来获取引擎,以确保正确初始化
        // 但为了与 Nashorn 示例保持一致,我们也可以使用 ScriptEngineManager
        // 注意:需要配置 JVM 参数: --module-path=path/to/graalvm/lib --add-modules=org.graalvm.js
        // 或者使用 GraalVM 自带的 java
        ScriptEngineManager engineManager = new ScriptEngineManager();
        // GraalVM 的引擎名称也是 "js",但它会优先使用 GraalVM 的实现
        ScriptEngine engine = engineManager.getEngineByName("js");
        if (engine == null) {
            System.err.println("GraalVM JS engine not found! Please ensure GraalVM is installed and configured.");
            return;
        }
        try {
            System.out.println("正在使用 GraalVM JS 引擎...");
            // 执行 JS 文件
            System.out.println("\n--- 执行 JS 文件 ---");
            // 假设你有一个 project 目录下有 a.js 文件
            File jsFile = new File("src/main/resources/a.js");
            engine.eval(new java.io.FileReader(jsFile));
            // Java 与 JS 交互
            System.out.println("\n--- Java 与 JS 交互 ---");
            engine.put("javaMessage", "Hello from Java!");
            // 执行 JS 代码,它会调用 Java 传入的函数
            engine.eval("print('JS 收到消息: ' + javaMessage);");
            // 执行 JS 函数,并获取结果
            engine.eval("function add(a, b) { return a + b; }");
            Object sum = engine.eval("add(5, 7);");
            System.out.println("JS add 函数返回结果: " + sum); // 输出: 12
        } catch (ScriptException | java.io.IOException e) {
            e.printStackTrace();
        }
    }
}

src/main/resources/a.js 文件内容:

// 这是一个简单的 JavaScript 文件
print("这是从 a.js 文件中输出的消息。");
// 定义一个函数,稍后可以被 Java 调用
function compute(a, b, operation) {
    var result;
    switch(operation) {
        case 'multiply':
            result = a * b;
            break;
        case 'divide':
            result = a / b;
            break;
        default:
            result = '未知操作';
    }
    return result;
}
// 将这个函数暴露给 Java 作用域
// 在 Java 中可以通过 engine.get("compute") 来获取这个函数
// 然后作为 Java 的 Invocable 对象来调用

通过进程调用 Node.js (适用于需要完整 Node.js 生态的场景)

如果你的 JavaScript 代码依赖于 Node.js 的特定模块(如 fs, path, axios 等),或者你已经有了一个成熟的 Node.js 服务,那么最直接的方式就是通过 Java 的 ProcessBuilderRuntime 来启动一个 Node.js 进程,然后通过标准输入输出进行通信。

Java如何调用JavaScript方法?-图3
(图片来源网络,侵删)

示例代码

准备 Node.js 脚本 (node_script.js)

// 从标准输入读取数据
process.stdin.setEncoding('utf8');
let data = '';
process.stdin.on('data', (chunk) => {
    data += chunk;
});
process.stdin.on('end', () => {
    try {
        const input = JSON.parse(data);
        const result = {
            originalInput: input,
            processed: input.value * 2,
            message: `Processed by Node.js at ${new Date().toLocaleString()}`
        };
        // 将结果写入标准输出
        console.log(JSON.stringify(result));
    } catch (error) {
        console.error(JSON.stringify({ error: error.message }));
    }
});

Java 代码 (ProcessRunner.java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
public class ProcessRunner {
    public static void main(String[] args) {
        // 准备传递给 Node.js 脚本的数据
        Map<String, Object> inputData = new HashMap<>();
        inputData.put("value", 25);
        inputData.put("description", "A test number");
        try {
            // 1. 创建进程构建器
            ProcessBuilder pb = new ProcessBuilder("node", "node_script.js");
            pb.redirectErrorStream(true); // 合并错误流和输出流
            // 2. 启动进程
            Process process = pb.start();
            // 3. 向进程的标准输入写入数据
            try (OutputStream os = process.getOutputStream()) {
                os.write(inputData.toString().getBytes());
                os.flush();
                os.close(); // 关闭输入流,表示数据发送完毕
            }
            // 4. 从进程的标准输出读取结果
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line);
                }
            }
            // 5. 等待进程结束
            int exitCode = process.waitFor();
            System.out.println("Node.js 进程退出码: " + exitCode);
            if (exitCode == 0) {
                System.out.println("从 Node.js 收到的结果: " + output.toString());
            } else {
                System.err.println("Node.js 脚本执行出错: " + output.toString());
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

总结与对比

特性 Nashorn (JDK 内置) GraalVM.js 通过进程调用 Node.js
依赖 无需额外依赖 (JDK 8+) 需要 GraalVM 和相关库 需要本地安装 Node.js
性能 良好 非常高 进程间通信开销大,首次启动慢
功能 ECMAScript 5.1 + 部分 ES6 ES6+ / ES2025 完整支持 完整 Node.js 生态
集成度 非常高,JVM 内无缝集成 非常高,JVM 内无缝集成 ,通过外部进程和 IO 通信
适用场景 运行简单脚本、配置文件、业务逻辑中的动态脚本 对性能和 JS 版本有高要求的场景 必须使用 Node.js 特有模块或已有 Node.js 服务
Java 8 之后 默认内置 是 Java 的未来方向,需要手动配置 不依赖 Java 版本

如何选择?

  • 如果你使用 Java 8/11/17 等版本,只需要运行一些简单的 JS 逻辑首选 Nashorn,它开箱即用,足够强大。
  • 如果你追求极致性能,或者需要使用最新的 JavaScript 特性 (如 async/await, import/export)选择 GraalVM.js,它是目前最先进的 JVM JS 引擎。
  • 如果你的 JS 代码严重依赖 Node.js 模块(如文件系统、网络请求库等)只能通过进程调用 Node.js,这是唯一可行的方案。
分享:
扫描分享到社交APP
上一篇
下一篇