如何将Nashorn引擎调试信息输出至TXT并获取执行报告?
我之前做过类似的Nashorn脚本调试和执行报告需求,这里有几个可行的方案,你可以根据自己的场景来选:
方案1:静态代码替换+执行(适合简单场景)
这个思路很直接:先把用户脚本里对context的引用替换成实际的上下文值,执行脚本得到结果后,再把替换后的代码和结果拼接成目标报告。
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class NashornSimpleReport { public static void main(String[] args) throws ScriptException { ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); // 模拟业务上下文变量 Map<String, Object> context = new HashMap<>(); context.put("valueA", 15); context.put("valueB", 10); // 用户提交的原始脚本 String userScript = "var a = context.valueA; var b = context.valueB; return a+b;"; // 替换脚本中的context变量为实际值 String replacedScript = userScript; Pattern contextVarPattern = Pattern.compile("context\\.(\\w+)"); Matcher matcher = contextVarPattern.matcher(userScript); while (matcher.find()) { String varName = matcher.group(1); Object actualValue = context.get(varName); replacedScript = replacedScript.replace(matcher.group(), actualValue.toString()); } // 执行替换后的脚本获取结果 Object result = engine.eval(replacedScript); // 生成最终报告 String executionReport = replacedScript + " == " + result.toString(); System.out.println(executionReport); // 输出结果:var a = 15; var b = 10; return 15+10; == 25 } }
优缺点:
- 优点:代码简单易实现,不需要依赖复杂API,适合逻辑直白的脚本。
- 缺点:只能处理直接引用
context的场景,对于context.valueA * 2这类复杂表达式、或者动态变量引用(比如context[varName])会失效,也无法捕获脚本执行过程中的中间变量变化。
方案2:使用Nashorn调试API(适合复杂场景)
Nashorn自带了调试接口jdk.nashorn.api.scripting.debug.Debugger,可以注册监听器捕获脚本执行的全生命周期事件——包括变量赋值、表达式计算、方法调用等,完全满足你获取执行详情的需求。
import jdk.nashorn.api.scripting.NashornScriptEngine; import jdk.nashorn.api.scripting.debug.Debugger; import jdk.nashorn.api.scripting.debug.DebugListener; import jdk.nashorn.api.scripting.debug.Locator; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.HashMap; import java.util.Map; public class NashornDebugReport { public static void main(String[] args) throws ScriptException { NashornScriptEngine engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); // 注入业务上下文 Map<String, Object> context = new HashMap<>(); context.put("valueA", 15); context.put("valueB", 10); engine.put("context", context); String userScript = "var a = context.valueA; var b = context.valueB; return a+b;"; // 收集执行过程中的变量和结果 Map<String, Object> variableRecords = new HashMap<>(); Object[] resultHolder = new Object[1]; // 初始化调试器并注册监听器 Debugger debugger = engine.getDebugger(); debugger.setListener(new DebugListener() { // 忽略不需要的回调方法 @Override public void breakpointReached(Debugger d, Locator l) {} @Override public void scriptLoaded(Debugger d, String s, Locator l) {} @Override public void scriptUnloaded(Debugger d, String s) {} @Override public void exceptionThrown(Debugger d, String s, Locator l) {} @Override public void sourceParsed(Debugger d, String s, String s1) {} @Override public void methodEntered(Debugger d, String s, Locator l) {} @Override public void methodExited(Debugger d, String s, Locator l) {} @Override public void output(Debugger d, String s) {} @Override public void inputRequested(Debugger d, String s) {} // 捕获变量赋值事件 @Override public void variableUpdated(Debugger debugger, String varName, Object value, Locator locator) { // 过滤Nashorn内部变量(以$开头) if (!varName.startsWith("$")) { variableRecords.put(varName, value); } } }); // 执行脚本 resultHolder[0] = engine.eval(userScript); // 拼接生成报告 StringBuilder reportBuilder = new StringBuilder(); // 先添加变量赋值语句 variableRecords.forEach((key, val) -> reportBuilder.append("var ").append(key).append(" = ").append(val).append("; ")); // 处理返回表达式,替换变量为实际值 String returnExpr = userScript.substring(userScript.lastIndexOf("return") + 6).trim().replace(";", ""); variableRecords.forEach((key, val) -> returnExpr = returnExpr.replace(key, val.toString())); reportBuilder.append("return ").append(returnExpr).append("; == ").append(resultHolder[0]); System.out.println(reportBuilder.toString()); // 输出结果:var a = 15; var b = 10; return 15+10; == 25 } }
优缺点:
- 优点:能深度捕获脚本执行的所有变量变化,支持复杂表达式、动态逻辑甚至嵌套函数的变量跟踪,灵活性极强。
- 缺点:代码相对复杂,需要熟悉Nashorn调试API的回调逻辑,还要注意过滤内部变量避免报告混乱。
方案3:自定义脚本包装(灵活可控)
这个思路是把用户脚本包装在一个自定义的函数里,通过脚本自身的逻辑来收集变量和结果,不需要依赖调试API,定制性很高。
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class NashornWrappedReport { public static void main(String[] args) throws ScriptException { ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); Map<String, Object> context = new HashMap<>(); context.put("valueA", 15); context.put("valueB", 10); engine.put("context", context); String userScript = "var a = context.valueA; var b = context.valueB; return a+b;"; // 包装脚本:添加变量收集和报告生成逻辑 String wrappedScript = "var __report = [];\n" + "(function() {\n" + " var __result;\n" + " // 替换return语句为结果赋值\n" + " " + userScript.replaceFirst("return ", "__result = ") + "\n" + " // 收集当前作用域的变量\n" + " for (var key in this) {\n" + " if (!key.startsWith('$') && key !== '__report' && key !== '__result') {\n" + " __report.push('var ' + key + ' = ' + this[key] + ';');\n" + " }\n" + " }\n" + " // 添加返回表达式和结果\n" + " var returnExpr = '" + userScript.substring(userScript.lastIndexOf("return") + 6).trim().replace(";", "") + "';\n" + " __report.push('return ' + returnExpr + '; == ' + __result);\n" + " return __report.join(' ');\n" + "})();"; // 执行包装后的脚本获取报告 String rawReport = (String) engine.eval(wrappedScript); // 替换context变量为实际值 Pattern contextPattern = Pattern.compile("context\\.(\\w+)"); Matcher matcher = contextPattern.matcher(rawReport); while (matcher.find()) { String varName = matcher.group(1); rawReport = rawReport.replace(matcher.group(), context.get(varName).toString()); } System.out.println(rawReport); // 输出结果:var a = 15; var b = 10; return 15+10; == 25 } }
优缺点:
- 优点:不需要依赖调试API,通过脚本逻辑实现,定制报告格式非常灵活,适合需要自定义输出样式的场景。
- 缺点:需要处理脚本语法兼容性,比如用户脚本中的return语句、作用域问题,对于复杂的嵌套函数场景可能需要调整包装逻辑。
内容的提问来源于stack exchange,提问作者danillosl
相关产品推荐
相关产品推荐

