場景描述
今天在CSDN上偶然看到一個帖子對於一段字符串 “var p=‘xxxx’” 怎么在java里獲得p的值,我想起了以前一個很有意思的場景,我的一位很NB的前同事做了一件很了不起的事,他當時配置acitiviti流程引擎的時候為了做變量控制,把變量控制的條件寫成了一個javascript的表達式,大概類似於groupNumber==1&&hasRead&&ticketType==1這種表達式,然后再JAVA代碼中把這些表達式執行了一下獲取一個布爾值作為流程控制的依據,我當時覺得思想很不錯!
后來,我在同一個項目中遇到了另外一個場景,在計算一個報表的某個值的時候需要使用對象中的一個參數,這個參數是用戶通過頁面配置的,大概是這種樣子100*23或230這種,我在看到了我這位前輩的思想之后,我采用的方式是這樣的,把這個字段作為字符串讓用戶在頁面上輸入,然后通過前輩的思想把這個字符串作為一個JavaScript代碼段執行一下,獲取返回值,用這個返回值作為計算參數參與報表計算.
實現思路
把入參作為JavaScript代碼,通過JavaScript執行引擎執行這個代碼,獲取返回值
技術要點
合理的使用JavaScript解析引擎
代碼實現
package com.hykj.util;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.Map;
import java.util.Set;
/** * java執行javaScript代碼的工具類 * * @author weizj */
public class JavaScriptUtil {
/** 單例的JavaScript解析引擎 */
private static ScriptEngine javaScriptEngine;
static {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine scriptEngine = manager.getEngineByName("js");
if (scriptEngine == null) {
throw new RuntimeException("獲取JavaScript解析引擎失敗");
}
javaScriptEngine = scriptEngine;
}
/** * 執行一段JavaScript代碼 * * @param script JavaScript的代碼 * @return JavaScript代碼運行結果的值 * @throws ScriptException JavaScript代碼運行異常 */
public static Object execute(String script) throws ScriptException {
return javaScriptEngine.eval(script);
}
/** * 運行一個JavaScript代碼段,並獲取指定變量名的值 * * @param script 代碼段 * @param attributeName 已知的變量名 * @return 指定變量名對應的值 * @throws ScriptException JavaScript代碼運行異常 */
public static Object executeForAttribute(String script, String attributeName) throws ScriptException {
javaScriptEngine.eval(script);
return javaScriptEngine.getContext().getAttribute(attributeName);
}
/** * 獲取當前語句運行后第一個有值變量的值 * * @param script 代碼段 * @return 第一個有值變量的值 * @throws ScriptException JavaScript代碼運行異常 */
public static Object executeForFirstAttribute(String script) throws ScriptException {
//這里重新獲取一個JavaScript解析引擎是為了避免代碼中有其他調用工具類的地方的變量干擾
//重新獲取后,這個JavaScript解析引擎只執行了這次傳入的代碼,不會保存其他地方的變量
//全局的解析器中會保存最大200個變量,JavaScript解析引擎本身最大保存100個變量
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine scriptEngine = manager.getEngineByName("js");
if (scriptEngine == null) {
throw new RuntimeException("獲取JavaScript解析引擎失敗");
}
scriptEngine.eval(script);
ScriptContext context = scriptEngine.getContext();
if (context == null) {
return null;
}
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
if (bindings == null) {
return null;
}
Set<Map.Entry<String, Object>> entrySet = bindings.entrySet();
if (entrySet == null || entrySet.isEmpty()) {
return null;
}
for (Map.Entry<String, Object> entry : entrySet) {
if (entry.getValue() != null) {
return entry.getValue();
}
}
return null;
}
}
測試方法
public static void main(String[] args) throws ScriptException {
Integer testExecute = (Integer) execute("2*3");
String testExecuteForAttribute = (String) executeForAttribute("var value = 'a'+ 'dc'", "value");
Boolean testExecuteForFirstAttribute = (Boolean) executeForFirstAttribute("var a = 6==2*3");
System.out.println(testExecute);
System.out.println(testExecuteForAttribute);
System.out.println(testExecuteForFirstAttribute);
System.out.println("test over ....");
}
運行結果
"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" -agentlib:...
com.hykj.util.JavaScriptUtil
Connected to the target VM, address: '127.0.0.1:56704', transport: 'socket'
6
adc
true
test over ....
Disconnected from the target VM, address: '127.0.0.1:56704', transport: 'socket'
Process finished with exit code 0
從運行結果中可以看到,需求是可以實現的,包含了執行代碼並獲取值
新增的executeForAttribute是為了解答開篇的帖子的問題
后來我又想了一下,可能是運行語句的時候並不知道變量名於是增加了一個不太嚴謹的executeForFirstAttribute方法用來處理一下這個問題,但是自我感覺這個方法並不很合適
改進空間
- 語句中如果包含參數是無法執行的,等有空的時候研究一下如何傳參
- 目前只試驗了
Integer,String,Boolean這三個常用的類型,如果是如Person等復雜類型,不知道代碼運行情況如何 executeForFirstAttribute方法不知道如何改進才能滿足大多數情況
