從Java 8開始(nashorn在Java 15中去掉了),可以使用nashorn來執行Javascript代碼,使用方法如下:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.;
Nashorn提供了非常簡單的方式讓用戶可以在JS代碼中引用Java的方法
public class NashornTest {
@Test
public void test() throws ScriptException {
String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.;
}
public static void printName(String name) {
System.out.println("name is " + name);
}
}
Nashorn雖然強大,但如果在生產環境中使用Nashorn來執行用戶自己寫的JS代碼還是得考慮以下問題:
1,如何防止用戶在JS中使用比較危險的Java方法,如System.exit()?
2,如何防止用戶的JS占用過多的內存和CPU資源?
3,如何提升JS執行的并發性能?
delight.nashornsandbox項目為開發者提供了Nashorn沙箱環境來執行JS代碼,解決以上問題
1,可以在初始化Nashorn沙箱時定義一個白名單,只允許JS引用白名單中的Java類
public class NashornTest {
public static void printName(String name) {
System.out.println("name is " + name);
}
@Test
public void testSandbox() throws ScriptCPUAbuseException, ScriptException {
String js = "var test = Java.type('test.NashornTest'); test.printName('abc')";
NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
// 允許JS中引用NashornTest類
sandbox.allow(NashornTest.class);
sandbox.;
}
}
2,設置線程每次執行JS使用的CPU和內存上限,需要指定ExecutorService作為JS的線程執行器
ExecutorService executor = Executors.newCachedThreadPool();
NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
// 允許JS中引用NashornTest類
sandbox.allow(NashornTest.class);
// 設置CPU時間上限為3000毫秒,在JS中sleep并不占用這個時間
sandbox.setMaxCPUTime(3000);
// 設置內存上限為5MB
sandbox.setMaxMemory(5 * 1024 * 1024);
sandbox.allowNoBraces(false);
sandbox.allowLoadFunctions(true);
sandbox.allowPrintFunctions(true);
// 設置緩存JS代碼數量,JS預編譯后緩存起來
sandbox.setMaxPreparedStatements(50);
sandbox.setExecutor(executor);
如果設置了CPU和內存上限,Nashorn沙箱在執行JS的時候,就會額外啟動一個監控線程,這個監控線程就會循環去調用ThreadMXBean.getThreadAllocatedBytes(執行線程Id) 和ThreadMXBean.getThreadCpuTime(執行線程Id) 這兩個方法來判斷CPU和內存是否達到上限,如果其中一個達到上限,則會中斷JS的執行并拋出異常。
需要注意的是,ThreadMXBean.getThreadAllocatedBytes并不是返回指定線程所創建的對象目前占用的堆內存大小,而是返回指定線程已經分配的內存大小(已分配的內存可能已經被釋放)
3,并發調用的性能問題,
1)可以創建一個Nashorn沙箱池,省去頻繁創建和銷毀沙箱的開銷
2)設置緩存JS代碼數量,提高JS執行速度,sandbox.setMaxPreparedStatements(maxCacheSize);
public class NashornSandboxFactory implements PoolObjectFactory<NashornSandbox> {
private ExecutorService executor;
private long maxCpuTime;
private long maxMemorySize;
private int maxCacheSize;
/**
* @param executor
* @param maxCpuTime 最大CPU時間,毫秒
* @param maxMemorySize 最大內存,byte
* @param maxCacheSize 最大腳本緩存個數
*/
public NashornSandboxFactory(ExecutorService executor, long maxCpuTime, long maxMemorySize, int maxCacheSize) {
this.executor = executor;
this.maxCpuTime = maxCpuTime;
this.maxMemorySize = maxMemorySize;
this.maxCacheSize = maxCacheSize;
}
@Override
public NashornSandbox create() {
NashornSandbox sandbox = NashornSandboxes.create("--language=es6");
// 沙箱初始化
return sandbox;
}
@Override
public boolean readyToTake(NashornSandbox obj) {
return true;
}
@Override
public boolean readyToRestore(NashornSandbox obj) {
return true;
}
@Override
public void destroy(NashornSandbox obj) {
// do nothing
}
PoolService<NashornSandbox> pool = new ConcurrentPool<>(new ConcurrentLinkedQueueCollection<>(), factory, coreSize, maxSize, false);
NashornSandbox sandbox = pool.tryTake(timeout, TimeUnit.MILLISECONDS);
sandbox.;
雖然Java 15已經去掉了nashorn,但可以在pom.xml中單獨引入
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.3</version>
</dependency>