-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from terwer/develop
New nashorn Config
- Loading branch information
Showing
17 changed files
with
892 additions
and
324 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.terwergreen.next.utils; | ||
|
||
import jdk.nashorn.api.scripting.NashornScriptEngine; | ||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; | ||
import jdk.nashorn.api.scripting.ScriptObjectMirror; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.script.ScriptContext; | ||
import javax.script.ScriptException; | ||
import javax.script.SimpleScriptContext; | ||
import java.io.File; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.io.Reader; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
|
||
/** | ||
* Nashorn工具类 | ||
* | ||
* @author Terwer | ||
* @version 1.0 | ||
* 2019/1/20 22:35 | ||
**/ | ||
public class NashornUtil { | ||
private static final Logger logger = LoggerFactory.getLogger(NashornUtil.class); | ||
private static NashornUtil nashornUtil; | ||
private final NashornScriptEngine engine; | ||
private static ScriptContext sc = new SimpleScriptContext(); | ||
private static ScheduledExecutorService globalScheduledThreadPool = Executors.newScheduledThreadPool(20); | ||
|
||
/** | ||
* Vue资源文件目录 | ||
*/ | ||
private static final String LIB_DIR = "static/lib"; | ||
private static final String POLYFILL_FILE_NAME = "nashorn-polyfill.js"; | ||
|
||
public static synchronized NashornUtil getInstance() { | ||
if (nashornUtil == null) { | ||
long start = System.currentTimeMillis(); | ||
nashornUtil = new NashornUtil(); | ||
long end = System.currentTimeMillis(); | ||
logger.info("init nashornHelper cost time {}ms", (end - start)); | ||
} | ||
|
||
return nashornUtil; | ||
} | ||
|
||
private NashornUtil() { | ||
// 获取Javascript引擎 | ||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); | ||
engine = (NashornScriptEngine) factory.getScriptEngine(new String[]{"--language=es6"}); | ||
sc.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); | ||
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); | ||
|
||
try { | ||
// 编译nashorn-polyfill | ||
engine.eval(read(LIB_DIR + File.separator + POLYFILL_FILE_NAME)); | ||
// for (String fileName : NashornUtil.VENDOR_FILE_NAME) { | ||
// engine.eval(read(SRC_DIR + File.separator + fileName)); | ||
// } | ||
// engine.eval(read(SRC_DIR + File.separator + "app.js")); | ||
// 编译server | ||
engine.eval(VueUtil.readVueFile("server.js")); | ||
logger.info("Vue app.js编译成功,编译引擎为Nashorn"); | ||
} catch (ScriptException e) { | ||
logger.error("Nashorn引擎Javascript解析错误", e); | ||
} | ||
} | ||
|
||
public NashornScriptEngine getNashornScriptEngine() { | ||
return engine; | ||
} | ||
|
||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,60 @@ | ||
package com.terwergreen.next.vue; | ||
|
||
import com.terwergreen.next.utils.VueUtil; | ||
import jdk.nashorn.api.scripting.NashornScriptEngine; | ||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; | ||
import com.terwergreen.next.utils.NashornUtil; | ||
import jdk.nashorn.api.scripting.ScriptObjectMirror; | ||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
|
||
import javax.script.Bindings; | ||
import javax.script.CompiledScript; | ||
import javax.script.ScriptContext; | ||
import javax.script.ScriptEngineManager; | ||
import javax.script.ScriptException; | ||
import javax.script.SimpleScriptContext; | ||
import java.util.function.Consumer; | ||
|
||
/** | ||
* 渲染Vue | ||
*/ | ||
public class VueRenderer { | ||
private final Log logger = LogFactory.getLog(this.getClass()); | ||
private Object renderServerFunction; | ||
private NashornUtil engine; | ||
|
||
public VueRenderer() { | ||
try { | ||
// 获取Javascript引擎 | ||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); | ||
NashornScriptEngine engine = (NashornScriptEngine) factory.getScriptEngine(new String[]{"--language=es6"}); | ||
// 编译 | ||
CompiledScript compiled = engine.compile(VueUtil.readVueFile("server-bundle.js")); | ||
this.renderServerFunction = compiled.eval(); | ||
logger.info("Vue app.js编译成功,编译引擎为Nashorn"); | ||
} catch (ScriptException e) { | ||
logger.error("Nashorn引擎Javascript解析错误", e); | ||
throw new RuntimeException(e); | ||
private final Object promiseLock = new Object(); | ||
private volatile boolean promiseResolved = false; | ||
private String html = null; | ||
|
||
private Consumer<Object> fnResolve = object -> { | ||
synchronized (promiseLock) { | ||
html = (String) object; | ||
promiseResolved = true; | ||
} | ||
}; | ||
|
||
public VueRenderer() { | ||
// 获取Javascript引擎 | ||
engine = NashornUtil.getInstance(); | ||
} | ||
|
||
public String renderContent() { | ||
NashornScriptEngine engine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); | ||
try { | ||
ScriptContext newContext = new SimpleScriptContext(); | ||
newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE); | ||
Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE); | ||
engineScope.put("renderServer", this.renderServerFunction); | ||
engine.setContext(newContext); | ||
Object html = engine.invokeFunction("renderServer"); | ||
return String.valueOf(html); | ||
ScriptObjectMirror promise = (ScriptObjectMirror) engine.callRender("renderServer"); | ||
promise.callMember("then", fnResolve); | ||
ScriptObjectMirror nashornEventLoop = engine.getGlobalGlobalMirrorObject("nashornEventLoop"); | ||
// 执行nashornEventLoops.process()使主线程执行回调函数 | ||
nashornEventLoop.callMember("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) { | ||
logger.error("Thread error:", e); | ||
} | ||
totalWaitTime = totalWaitTime + interval; | ||
if (interval < 500) interval = interval * 2; | ||
i = i + 1; | ||
} | ||
return html; | ||
} catch (Exception e) { | ||
throw new IllegalStateException("failed to render vue component", e); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
var self = this; | ||
// 模拟global | ||
var global = this; | ||
|
||
// 模拟process | ||
var process = { | ||
env: { | ||
VUE_ENV: "server", | ||
NODE_ENV: "production" | ||
}, | ||
nextTick: function (fn) { | ||
global.setTimeout(fn, 0) | ||
} | ||
}; | ||
global.process = process; | ||
|
||
// 模拟console | ||
var console = {}; | ||
console.debug = print; | ||
console.warn = print; | ||
console.log = print; | ||
console.error = print; | ||
console.trace = print; | ||
console.assert = print; | ||
global.console = console; | ||
|
||
Object.assign = function (t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
|
||
/* | ||
Source is originated from https://github.com/morungos/java-xmlhttprequest | ||
Articles about Nashorn: | ||
- https://blog.codecentric.de/en/2014/06/project-nashorn-javascript-jvm-polyglott/ | ||
How it work: | ||
in https://github.com/morungos/java-xmlhttprequest, it uses Timer to run setTimeout and setInterval task, | ||
but they are run in a separate thread of the Timer creates that is different with the main JavaScript thread. | ||
This implementation uses ScheduledExecutorService instead of Timer so the threads for task scheduling can be | ||
reused instead of each JavasScript thread create a Timer thread when using Timer. | ||
And most important thing is this adds global.nashornEventLoop and scheduled tasks only add function callback | ||
object in eventLoop (ArrayQueue), and it is main JavaScript thread to run these function callback by calling | ||
`global.nashornEventLoop.process();` at the end of JavaScript Application. It is just like browser or NodeJS | ||
that event loop is called when the main stack is cleared. | ||
When runs on server with Promise, remember to call `nashornEventLoop.process()` when waiting for Promise by | ||
Thread.sleep(), and call `nashornEventLoop.reset()` if server thread (e.g. Servlet thread) decides to be | ||
timeout so that eventLoop will be clean for next request. | ||
*/ | ||
(function nashornEventLoopMain(context) { | ||
'use strict'; | ||
|
||
var Thread = Java.type('java.lang.Thread'); | ||
var Phaser = Java.type('java.util.concurrent.Phaser'); | ||
var ArrayDeque = Java.type('java.util.ArrayDeque'); | ||
var HashMap = Java.type('java.util.HashMap'); | ||
var TimeUnit = Java.type("java.util.concurrent.TimeUnit"); | ||
var Runnable = Java.type('java.lang.Runnable'); | ||
|
||
|
||
var globalTimerId; | ||
var timerMap; | ||
var eventLoop; | ||
var phaser = new Phaser(); | ||
|
||
// __NASHORN_POLYFILL_TIMER__ type is ScheduledExecutorService | ||
var scheduler = context.__NASHORN_POLYFILL_TIMER__; | ||
|
||
resetEventLoop(); | ||
|
||
console.log('main javasript thread ' + Thread.currentThread().getName()); | ||
|
||
function resetEventLoop() { | ||
globalTimerId = 1; | ||
if (timerMap) { | ||
timerMap.forEach(function (key, value) { | ||
value.cancel(true); | ||
}) | ||
} | ||
timerMap = new HashMap(); | ||
eventLoop = new ArrayDeque(); | ||
} | ||
|
||
function waitForMessages() { | ||
phaser.register(); | ||
var wait = !(eventLoop.size() === 0); | ||
phaser.arriveAndDeregister(); | ||
|
||
return wait; | ||
} | ||
|
||
function processNextMessages() { | ||
var remaining = 1; | ||
while (remaining) { | ||
// console.log('eventLoop size ' + eventLoop.size() + 'in thread ' + Thread.currentThread().getName()); | ||
phaser.register(); | ||
var message = eventLoop.removeFirst(); | ||
remaining = eventLoop.size(); | ||
phaser.arriveAndDeregister(); | ||
|
||
var fn = message.fn; | ||
var args = message.args; | ||
|
||
try { | ||
// console.log('processNextMessages in thread ' + Thread.currentThread().getName()); | ||
fn.apply(context, args); | ||
} catch (e) { | ||
console.trace(e); | ||
console.trace(fn); | ||
console.trace(args); | ||
} | ||
} | ||
} | ||
|
||
context.nashornEventLoop = { | ||
process: function () { | ||
console.log('nashornEventLoop.process is called in thread ' + Thread.currentThread().getName()) | ||
while (waitForMessages()) { | ||
processNextMessages() | ||
} | ||
}, | ||
reset: resetEventLoop | ||
}; | ||
|
||
function createRunnable(fn, timerId, args, repeated) { | ||
var Runner = Java.extend(Runnable, { | ||
run: function () { | ||
try { | ||
var phase = phaser.register(); | ||
eventLoop.addLast({ | ||
fn: fn, | ||
args: args | ||
}); | ||
console.log('TimerTask add one event, and eventLoop size is:' + eventLoop.size() + ' in thread ' + Thread.currentThread().getName()); | ||
} catch (e) { | ||
console.trace(e); | ||
} finally { | ||
if (!repeated) timerMap.remove(timerId); | ||
phaser.arriveAndDeregister(); | ||
} | ||
} | ||
}); | ||
return new Runner(); | ||
} | ||
|
||
var setTimeout = function (fn, millis /* [, args...] */) { | ||
var args = [].slice.call(arguments, 2, arguments.length); | ||
|
||
var timerId = globalTimerId++; | ||
var runnable = createRunnable(fn, timerId, args, false); | ||
|
||
var task = scheduler.schedule(runnable, millis, TimeUnit.MILLISECONDS); | ||
timerMap.put(timerId, task); | ||
|
||
return timerId; | ||
}; | ||
|
||
var setImmediate = function (fn /* [, args...] */) { | ||
var args = [].slice.call(arguments, 1, arguments.length); | ||
return setTimeout(fn, 0, args); | ||
} | ||
|
||
var clearImmediate = function (timerId) { | ||
clearTimeout(timerId); | ||
} | ||
|
||
var clearTimeout = function (timerId) { | ||
var task = timerMap.get(timerId); | ||
if (task) { | ||
task.cancel(true); | ||
timerMap.remove(timerId); | ||
} | ||
}; | ||
|
||
var setInterval = function (fn, delay /* [, args...] */) { | ||
var args = [].slice.call(arguments, 2, arguments.length); | ||
|
||
var timerId = globalTimerId++; | ||
var runnable = createRunnable(fn, timerId, args, true); | ||
var task = scheduler.scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS); | ||
timerMap.put(timerId, task); | ||
|
||
return timerId; | ||
}; | ||
|
||
var clearInterval = function (timerId) { | ||
clearTimeout(timerId); | ||
}; | ||
|
||
context.setTimeout = setTimeout; | ||
context.clearTimeout = clearTimeout; | ||
context.setImmediate = setImmediate; | ||
context.clearImmediate = clearImmediate; | ||
context.setInterval = setInterval; | ||
context.clearInterval = clearInterval; | ||
})(typeof global !== "undefined" && global || typeof self !== "undefined" && self || this); |
Oops, something went wrong.