如何解决Java8 ScriptEngine内联JS中多个Java方法仅首个调用问题
问题描述
我正在使用Java 8的ScriptEngine求值内联JavaScript表达式。
仅包含单个Java方法调用的表达式可以正常执行:
(_tranxAmount > 100) && (profile.received('daily', 'avg', false) < 100)
其中profile是绑定至全局作用域的Java类对象,received()是其成员方法。通过scriptingEngine.eval(script)执行上述表达式时,profile.received('daily', 'avg', false)(实际绑定逻辑为functionsManager.received('daily', 'avg', false))可被正常调用。
但当JavaScript表达式中包含2个及以上Java方法调用时,ScriptEngine无法正常完成求值,仅第一个Java方法会被调用。
可复现问题的示例表达式:
(_tranxAmount > 100) && (profile.received('daily', 'avg', false) < 100) && (profile.sent('monthly', 'count', false) < 5)
相关代码片段:
- Script Engine定义:
private static ScriptEngine scriptingEngine;
- 主类构造函数内初始化逻辑:
scriptingEngine = new ScriptEngineManager().getEngineByName("JavaScript"); scriptingEngine.getBindings(ScriptContext.GLOBAL_SCOPE).put(PROFILING_FUNCTIONS_PREFIX, functionsManager);
- 表达式求值逻辑:
Bindings bindings = new SimpleBindings(customMapOfVaraibles); CompiledScript compiled = ((Compilable) scriptingEngine).compile(" (_tranxAmount > 100) && (profile.received('daily', 'avg', false) < 100) && (profile.sent('monthly', 'count', false) < 5)"); compiled.eval(bindings);
functionsManager的方法定义:
public BigDecimal received(String intervalType, String statisticsType, boolean considerTranxType) throws InvalidRequirementsException { // 业务计算逻辑 return calculatedValue; } public BigDecimal sent(String intervalType, String statisticsType, boolean considerTranxType) throws InvalidRequirementsException { // 业务计算逻辑 return calculatedValue; }
问题原因
该现象本质是JavaScript短路求值规则与Nashorn引擎类型转换逻辑共同导致的预期外行为,并非引擎不支持单表达式内多次调用Java方法:
- JavaScript中
&&为短路与运算符:只要任意前置子表达式返回假值,后续子表达式会直接跳过、完全不执行。 - Java 8默认搭载的Nashorn JavaScript引擎,不会自动对Java返回的
BigDecimal对象做数值拆箱,当你直接用BigDecimal返回值和JS原生数字做</>比较时,会按照对象引用规则判断而非数值大小比较。绝大多数场景下,哪怕BigDecimal存的数值确实小于100,和原生数字100比较都会返回false,直接触发&&短路,后续的profile.sent()方法根本不会进入执行逻辑,外在表现就是“只有第一个Java方法被调用”。
其余两个可能的诱因:
- 作用域绑定风险:你将
profile绑定到全局作用域,而每次表达式执行使用独立的Engine级绑定,如果是多线程环境共享同一个ScriptEngine实例,全局绑定值可能被意外覆盖。 - 异常静默中断:你的Java方法声明抛出检查型异常
InvalidRequirementsException,如果第一个方法执行时抛出异常被上层逻辑静默捕获,也会中断整个表达式执行,导致后续方法不运行。
解决方案
按优先级依次排查修复即可:
- 优先修复BigDecimal类型比较问题,二选一即可:
- 改Java侧方法返回值:将
received、sent方法的返回值从BigDecimal改为基本类型double/long,从根源避免类型转换问题。 - 改JS表达式显式拆箱:在每个方法调用后显式调用
doubleValue()转成JS可识别的原始数值,修改后的表达式如下:(_tranxAmount > 100) && (profile.received('daily', 'avg', false).doubleValue() < 100) && (profile.sent('monthly', 'count', false).doubleValue() < 5)
- 改Java侧方法返回值:将
- 修正绑定逻辑:不要将业务对象绑定到全局作用域,每次执行表达式前将
profile放入当前执行上下文的Bindings中,避免多线程下全局状态污染:Bindings bindings = new SimpleBindings(customMapOfVaraibles); bindings.put(PROFILING_FUNCTIONS_PREFIX, functionsManager); CompiledScript compiled = ((Compilable) scriptingEngine).compile(script); compiled.eval(bindings); - 兜底排查:给表达式执行逻辑添加异常捕获,打印完整异常栈,确认第一个方法调用时是否存在隐式抛出的异常中断执行。
- 如果业务要求无论逻辑判断结果如何,所有Java方法都必须执行,不要直接把方法调用写在
&&链式判断里,可以提前把所有方法的返回值取出来存为变量,再做逻辑判断:var r = profile.received('daily', 'avg', false).doubleValue(); var s = profile.sent('monthly', 'count', false).doubleValue(); (_tranxAmount > 100) && (r < 100) && (s < 5);
内容的提问来源于stack exchange,提问作者Afridi
相关产品推荐
相关产品推荐

