Java 8 Nashorn JavaScript:让脚本与 Java 融合的桥梁
在 Java 8 发布之后,开发者迎来了一项极具前瞻性的功能:Nashorn JavaScript 引擎。它并非简单的“支持 JavaScript”,而是真正将 JavaScript 作为第一公民嵌入 Java 生态系统。对于初学者来说,这可能听起来像“Java 里跑 JS”这种听起来很酷但难以理解的功能;而对于中级开发者,它更像是一个隐藏的工具箱,能让你在不修改 Java 代码的前提下,灵活扩展逻辑。
本文将带你从零开始,一步步掌握 Java 8 Nashorn JavaScript 的核心用法。我们不会讲太多理论,而是通过实际代码和生活化的比喻,让你在实践中理解它的价值。
什么是 Java 8 Nashorn JavaScript?
你可以把 Java 8 Nashorn JavaScript 想象成一个“脚本翻译官”。Java 是个严谨的工程师,写代码必须提前定义类型、结构、接口。而 JavaScript 是个自由奔放的诗人,写代码可以随时改变变量类型、动态执行逻辑。
Nashorn 就是那个能同时听懂工程师和诗人的语言,并且在两者之间自由转换的翻译官。它允许你在 Java 程序中执行 JavaScript 代码,还能把 Java 对象暴露给 JS,反过来也能让 JS 函数被 Java 调用。
注意:Nashorn 从 JDK 11 开始被标记为废弃,后续版本已移除。但如果你仍在维护旧项目,或学习 Java 8 的历史特性,它依然具有学习价值。
快速入门:在 Java 中执行 JavaScript
我们先来一个最简单的例子,看看如何在 Java 代码里运行一段 JavaScript。
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornExample {
public static void main(String[] args) {
// 1. 创建脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2. 获取 Nashorn 引擎(通过名字匹配)
ScriptEngine engine = manager.getEngineByName("nashorn");
// 3. 执行一段 JavaScript 代码
try {
engine.eval("print('Hello from JavaScript!');");
} catch (ScriptException e) {
System.err.println("执行脚本时出错:" + e.getMessage());
}
}
}
代码注释:
ScriptEngineManager是脚本引擎的“调度中心”,它能帮你找到系统中可用的引擎(如 JavaScript、Python 等)。getEngineByName("nashorn")是关键一步,它返回一个支持 JavaScript 的引擎实例。eval()方法用于执行字符串形式的 JavaScript 代码,就像你在浏览器控制台里输入代码一样。print()是 Nashorn 提供的函数,用于输出日志,相当于 Java 的System.out.println()。
运行这段代码,你会看到输出:
Hello from JavaScript!
是不是很像在写前端代码?但这一切都在 Java 程序中完成。
交互数据:Java 与 JavaScript 变量互通
Nashorn 最强大的地方在于,它不只是“运行 JS”,而是能让你在 Java 和 JS 之间传递数据。这就像两个语言在“对话”。
传递 Java 变量给 JavaScript
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class PassDataToJS {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
// 1. 定义一个 Java 变量
String name = "小明";
int age = 25;
// 2. 将 Java 变量绑定到 JS 环境中
engine.put("userName", name);
engine.put("userAge", age);
// 3. 执行 JS 代码,并使用 Java 变量
try {
engine.eval("print('用户姓名:' + userName + ',年龄:' + userAge);");
} catch (ScriptException e) {
System.err.println("执行失败:" + e.getMessage());
}
}
}
代码注释:
engine.put("userName", name)将 Java 变量name传入 JS 环境,JS 中可以通过userName直接访问。- JS 中的
+操作符用于字符串拼接,和你平时写 JS 一模一样。 - 输出结果为:
用户姓名:小明,年龄:25
这说明 Java 和 JavaScript 的变量可以无缝对接,无需额外转换。
从 JavaScript 返回值给 Java
反过来,你也可以让 JavaScript 执行一个函数,然后把结果“传”回 Java。
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ReturnFromJS {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
// 1. 定义一段 JS 函数,用于计算两个数的和
String jsCode = "function add(a, b) { return a + b; }";
try {
// 2. 执行 JS 代码,定义函数
engine.eval(jsCode);
// 3. 调用 JS 函数,并获取返回值
Object result = engine.invokeFunction("add", 10, 20);
// 4. 将返回值转换为整数并打印
System.out.println("10 + 20 = " + result);
} catch (ScriptException | NoSuchMethodException e) {
System.err.println("执行失败:" + e.getMessage());
}
}
}
代码注释:
engine.eval(jsCode)执行 JS 代码,定义了一个add函数。invokeFunction("add", 10, 20)是关键方法,它调用 JS 中的add函数,并传入参数。- 返回值类型为
Object,因为 JS 是动态类型语言,返回值可能是数字、字符串、对象等。 - 最终输出:
10 + 20 = 30
这表明,Java 可以像调用普通方法一样调用 JS 函数,实现“跨语言调用”。
实际应用场景:动态规则引擎
想象你正在开发一个电商系统,订单金额超过一定额度需要自动审批。但审批规则可能会频繁变化,比如“满 500 减 50”、“满 1000 送积分”等。
如果每次改规则都要重新编译 Java 代码,那效率太低。这时,Nashorn 就派上用场了。
示例:动态判断是否满足优惠条件
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class DynamicRuleEngine {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
// 模拟用户订单金额
double orderAmount = 800.0;
// 动态规则:如果订单金额大于等于 500,返回 true
String ruleScript =
"function isEligible(amount) {" +
" return amount >= 500;" +
"}" +
"isEligible(" + orderAmount + ")";
try {
Object result = engine.eval(ruleScript);
boolean eligible = (Boolean) result;
if (eligible) {
System.out.println("✅ 您的订单满足优惠条件!");
} else {
System.out.println("❌ 暂未达到优惠门槛。");
}
} catch (ScriptException e) {
System.err.println("规则执行失败:" + e.getMessage());
}
}
}
代码注释:
- 规则被写成字符串,可以来自数据库、配置文件或前端输入。
isEligible(amount)函数在 JS 中定义,判断金额是否达标。engine.eval(ruleScript)执行规则逻辑,返回true或false。- 最终通过
Boolean类型强制转换,判断是否满足条件。
这种设计让你可以在不重启系统的情况下,动态更新业务规则。比如,明天规则改成“满 600 减 80”,你只需要修改字符串,无需重新编译 Java。
表格对比:Nashorn 与传统 JS 运行方式
| 特性 | Nashorn JavaScript | 浏览器 JS | Node.js |
|---|---|---|---|
| 运行环境 | Java 虚拟机 | 浏览器 | 服务器 |
| 调用 Java 对象 | ✅ 支持 | ❌ 不支持 | ❌ 通常不支持 |
| 动态加载规则 | ✅ 非常适合 | ❌ 不适合 | ✅ 适合 |
| 与 Java 交互 | 无缝集成 | 无法直接交互 | 通过 API 交互 |
| 适用场景 | Java 应用内嵌脚本 | 前端展示 | 后端服务 |
这个表格帮你快速判断:如果你的项目是 Java 为主,需要灵活扩展业务逻辑,Nashorn 是一个非常合适的选择。
最佳实践与注意事项
- 性能考虑:Nashorn 虽然比旧版 Rhino 快,但相比原生 Java 代码仍有性能差距。不要用它处理高并发计算。
- 安全性:运行用户输入的 JS 代码存在安全风险(如执行
eval('rm -rf /'))。务必在可信环境中使用,或加入沙箱限制。 - 版本兼容:Nashorn 从 JDK 11 起被废弃。如果你的项目使用 JDK 11+,建议改用 GraalVM 的 JavaScript 支持。
- 错误处理:始终使用
try-catch包裹eval()和invokeFunction(),避免程序崩溃。
总结:Nashorn JavaScript 的价值
Java 8 Nashorn JavaScript 不是一个“炫技”功能,而是一个实用的桥梁。它让 Java 应用具备了“脚本化”的能力,尤其适合需要动态配置、规则灵活变更的系统。
无论是快速原型开发,还是构建可插拔的业务引擎,Nashorn 都能帮你减少硬编码,提升系统的灵活性和可维护性。
虽然它已不再被新版本 JDK 支持,但理解它,有助于你掌握“脚本与语言融合”的思想。这种思想,正是现代开发中“低代码”、“可配置化”的底层逻辑。
如果你正在维护一个 Java 8 项目,不妨尝试用 Nashorn 实现一个简单的规则引擎。你会发现,原来“让程序自己变聪明”并没有那么难。