Nashorn JavaScript引擎是Java SE 8 的一部分,而且和其它獨立的引擎例如 Google V8(用於Google Chrome和Node.js的引擎)互相競爭。Nashorn經過在JVM上,以原生方式運行動態的JavaScript代碼來擴展Java的功能。javascript
Java代碼中簡單的HelloWorld以下所示:html
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); engine.eval("print('Hello World!');");
爲了在Java中執行JavaScript,你首先要經過javax.script包建立腳本引擎。
JavaScript代碼既能夠經過傳遞JavaScript代碼字符串,也能夠傳遞指向你的JS腳本文件的FileReader
來執行:前端
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); engine.eval(new FileReader("script.js"));
Nashorn JavaScript基於 ECMAScript 5.1,可是它的後續版本會對ES6提供支持:java
Nashorn的當前策略遵循ECMAScript規範。當咱們在JDK8中發佈它時,它將基於ECMAScript 5.1。Nashorn將來的主要發佈基於ECMAScript 6。
Nashorn 支持從Java代碼中直接調用定義在腳本文件中的JavaScript函數。你能夠將Java對象傳遞爲函數參數,而且從函數返回數據來調用Java方法。react
var fun1 = function(name) { print('Hi there from Javascript, ' + name); return "greetings from javascript"; }; var fun2 = function (object) { print("JS Class Definition: " + Object.prototype.toString.call(object)); };
ScriptEngine
內置了invokeFunction
方法,提調用javascript函數並返回結果。webpack
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); engine.eval(new FileReader("script.js")); Object result = invocable.invokeFunction("fun1", "Peter Parker"); System.out.println(result); System.out.println(result.getClass()); // Hi there from Javascript, Peter Parker // greetings from javascript // class java.lang.String
在JavaScript中調用Java方法十分容易。咱們首先須要定義一個Java靜態方法。git
static String fun1(String name) { System.out.format("Hi there from Java, %s", name); return "greetings from java"; }
Java類能夠經過Java.type
API擴展在JavaScript中引用。它就和Java代碼中的import相似。只要定義了Java類型,咱們就能夠天然地調用靜態方法fun1(),而後像sout打印信息。因爲方法是靜態的,咱們不須要首先建立實例。es6
var MyJavaClass = Java.type('my.package.MyJavaClass'); var result = MyJavaClass.fun1('John Doe'); print(result); // Hi there from Java, John Doe // greetings from java
在使用JavaScript原生類型調用Java方法時,Nashorn 如何處理類型轉換?讓咱們經過簡單的例子來弄清楚。
下面的Java方法簡單打印了方法參數的實際類型:github
static void fun2(Object object) { System.out.println(object.getClass()); }
爲了理解背後如何處理類型轉換,咱們使用不一樣的JavaScript類型來調用這個方法:web
MyJavaClass.fun2(123); // class java.lang.Integer MyJavaClass.fun2(49.99); // class java.lang.Double MyJavaClass.fun2(true); // class java.lang.Boolean MyJavaClass.fun2("hi there") // class java.lang.String MyJavaClass.fun2(new Number(23)); // class jdk.nashorn.internal.objects.NativeNumber MyJavaClass.fun2(new Date()); // class jdk.nashorn.internal.objects.NativeDate MyJavaClass.fun2(new RegExp()); // class jdk.nashorn.internal.objects.NativeRegExp MyJavaClass.fun2({foo: 'bar'}); // class jdk.nashorn.internal.scripts.JO4
JavaScript原始類型轉換爲合適的Java包裝類,而JavaScript原生對象會使用內部的適配器類來表示。要記住jdk.nashorn.internal
中的類可能會有所變化,因此不該該在客戶端面向這些類來編程。
在向Java傳遞原生JavaScript對象時,你可使用ScriptObjectMirror
類,它其實是底層JavaScript對象的Java表示。ScriptObjectMirror
實現了Map
接口,位於jdk.nashorn.api
中。這個包中的類能夠用於客戶端代碼。
下面的例子將參數類型從Object
改成ScriptObjectMirror
,因此咱們能夠從傳入的JavaScript對象中得到一些信息。
static void fun3(ScriptObjectMirror mirror) { System.out.println(mirror.getClassName() + ": " + Arrays.toString(mirror.getOwnKeys(true))); }
當向這個方法傳遞對象(哈希表)時,在Java端能夠訪問其屬性:
MyJavaClass.fun3({ foo: 'bar', bar: 'foo' }); // Object: [foo, bar]
咱們也能夠在Java中調用JavaScript的成員函數。讓咱們首先定義JavaScript Person
類型,帶有屬性firstName
和 lastName
,以及方法getFullName
。
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = function() { return this.firstName + " " + this.lastName; } }
JavaScript方法getFullName
能夠經過callMember()
在ScriptObjectMirror
上調用。
static void fun4(ScriptObjectMirror person) { System.out.println("Full Name is: " + person.callMember("getFullName")); }
當向Java方法傳遞新的Person
時,咱們會在控制檯看到預期的結果:
var person1 = new Person("Peter", "Parker"); MyJavaClass.fun4(person1); // Full Name is: Peter Parker
經過使用promise實現服務端渲染。
因爲當前Nashorn基於es5,不支持部分es6對象,咱們須要引入polyfill文件,nashorn-polyfill。
該polyfill經過java與JavaScript的結合使用,使Nashorn支持console
, process
, Blob
, Promise
等對象和setTimeout
, clearTimeout
, setInterval
, clearInterval
等函數。
實例化工具類,經過該類操做Javascript對象。
public class NashornHelper { /** * 用於本類的日誌 */ private static final Logger logger = LoggerFactory.getLogger(NashornHelper.class); private static final String JAVASCRIPT_DIR = "static"; // js文件目錄 private static final String LIB_DIR = JAVASCRIPT_DIR + File.separator + "lib"; private static final String[] VENDOR_FILE_NAME = {"vendor.js"}; // webpack打包的三方庫,如react,lodash private static final String SRC_DIR = JAVASCRIPT_DIR + File.separator + "src"; // 文件目錄 private static final String POLYFILL_FILE_NAME = "nashorn-polyfill.js"; private final NashornScriptEngine engine; private static NashornHelper nashornHelper; private static ScriptContext sc = new SimpleScriptContext(); private static ScheduledExecutorService globalScheduledThreadPool = Executors.newScheduledThreadPool(20); // 單例模式 public static synchronized NashornHelper getInstance() { if (nashornHelper == null) { long start = System.currentTimeMillis(); nashornHelper = new NashornHelper(); long end = System.currentTimeMillis(); logger.error("init nashornHelper cost time {}ms", (end - start)); } return nashornHelper; } private NashornHelper(){ long start = System.currentTimeMillis(); engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); Bindings bindings = new SimpleBindings(); bindings.put("logger", logger); // 向nashorn引擎注入logger對象 sc.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); sc.getBindings(ScriptContext.ENGINE_SCOPE).putAll(bindings); sc.setAttribute("__IS_SSR__", true, ScriptContext.ENGINE_SCOPE); sc.setAttribute("__NASHORN_POLYFILL_TIMER__", globalScheduledThreadPool, ScriptContext.ENGINE_SCOPE); engine.setBindings(sc.getBindings(ScriptContext.ENGINE_SCOPE), ScriptContext.ENGINE_SCOPE); long end = System.currentTimeMillis(); logger.info("init nashornHelper cost time {}ms", (end - start)); try { // 執行js文件 engine.eval(read(LIB_DIR + File.separator + POLYFILL_FILE_NAME)); for (String fileName : NashornHelper.VENDOR_FILE_NAME) { engine.eval(read(SRC_DIR + File.separator + fileName)); } engine.eval(read(SRC_DIR + File.separator + "app.js")); } catch (ScriptException e) { logger.error("load javascript failed.", e); } } // 獲取Nashorn做用域下的對象 public ScriptObjectMirror getGlobalGlobalMirrorObject(String objectName) { return (ScriptObjectMirror) engine.getBindings(ScriptContext.ENGINE_SCOPE).get(objectName); } // 調用全局方法 public Object callRender(String methodName, Object... input) { try { return engine.invokeFunction(methodName, input); } catch (ScriptException e) { logger.error("run javascript failed.", e); return null; } catch (NoSuchMethodException e) { logger.error("no such method.", e); return null; } } // 讀取文件 private Reader read(String path) { InputStream in = getClass().getClassLoader().getResourceAsStream(path); return new InputStreamReader(in); }
實例化工具類
NashornHelper engine = NashornHelper.getInstance();
執行調用javasript,java中獲取promise
對象
ScriptObjectMirror promise = (ScriptObjectMirror) engine.callRender("ssr_render");
執行promise
的then
方法,等待執行完成並回調
promise.callMember("then", fnResolve); ScriptObjectMirror nashornEventLoop = engine.getGlobalGlobalMirrorObject("nashornEventLoop"); nashornEventLoop.callMember("process"); // 執行nashornEventLoops.process()使主線程執行回調函數 int i = 0; int jsWaitTimeout = 1000 * 60; int interval = 200; // 等待時間間隔 int totalWaitTime = 0; // 實際等待時間 while (!promiseResolved && totalWaitTime < jsWaitTimeout) { nashornEventLoop.callMember("process"); try { Thread.sleep(interval); } catch (InterruptedException e) { } totalWaitTime = totalWaitTime + interval; if (interval < 500) interval = interval * 2; i = i + 1; }
回調函數
private Consumer<Object> fnResolve = object -> { synchronized (promiseLock) { html = (String) object; promiseResolved = true; } };
最後結果已字符串形式存在html
中,可將其渲染到頁面中.