This question already has an answer here:
I want to execute a JavaScript within a servlet. Is it possible to reuse the same Scripting Engine across all servlet invocations? Servlet instances are shared by multiple threads. Does this require to create a new Scripting Engine per request? That would be a unacceptable performance penalty. As an example, is the following code save?
public class MyServlet extends HttpServlet {
private ScriptEngineManager factory;
private ScriptEngine engine;
@Override
public void init() throws ServletException {
factory = new ScriptEngineManager();
engine = factory.getEngineByName("nashorn");
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
try (PrintWriter writer = res.getWriter()) {
ScriptContext newContext = new SimpleScriptContext();
newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
engineScope.put("writer", writer);
Object value = engine.eval("writer.print('Hello, World!');", engineScope);
writer.close();
} catch (IOException | ScriptException ex) {
Logger.getLogger(AsyncServlet.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
If this is not safe, what would be the best way to avoid creating an engine per request? Using a pool of engines?
Edit: Is it possible to reuse one and the same engine and one and the same JavaScriptObject, which results as the evaluation of a JS-function, for all servlet requests, if the function does not change any shared object but uses only the arguments given with the call? Look at the following adaptaion of the above example:
public class MyServlet extends HttpServlet {
private ScriptEngineManager factory;
private ScriptEngine engine;
private ScriptObjectMirror script;
@Override
public void init() throws ServletException {
try {
factory = new ScriptEngineManager();
engine = factory.getEngineByName("nashorn");
script = (ScriptObjectMirror)engine.eval("function(writer) {writer.print('Hello, World!');}");
} catch (ScriptException ex) {
Logger.getLogger(MyServlet.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
try (PrintWriter writer = res.getWriter()) {
script.call(null, writer);
writer.close();
} catch (IOException ex) {
Logger.getLogger(MyServlet.class.getName()).log(Level.SEVERE, null, ex);
}
}
Is this safe?
In javax.script.ScriptEngineFactory
there is a method getParameter(String key)
.
With the special key THREADING
you get threading information for this specific engine factory.
This small program prints out this information for every registered engine factory:
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
public class ScriptEngineTest {
public static void main(String[] args) {
final ScriptEngineManager mgr = new ScriptEngineManager();
for(ScriptEngineFactory fac: mgr.getEngineFactories()) {
System.out.println(String.format("%s (%s), %s (%s), %s", fac.getEngineName(),
fac.getEngineVersion(), fac.getLanguageName(),
fac.getLanguageVersion(), fac.getParameter("THREADING")));
}
}
}
For Java 7 it is:
Mozilla Rhino (1.7 release 3 PRERELEASE), ECMAScript (1.8), MULTITHREADED
For Java 8:
Oracle Nashorn (1.8.0_25), ECMAScript (ECMA - 262 Edition 5.1), null
null
means the engine implementation is not thread safe.
In your servlet you can use a ThreadLocal
to hold a seperate engine for each thread allowing to reuse the engine for subsequent requests served by the same thread.
public class MyServlet extends HttpServlet {
private ThreadLocal<ScriptEngine> engineHolder;
@Override
public void init() throws ServletException {
engineHolder = new ThreadLocal<ScriptEngine>() {
@Override
protected ScriptEngine initialValue() {
return new ScriptEngineManager().getEngineByName("nashorn");
}
};
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
try (PrintWriter writer = res.getWriter()) {
ScriptContext newContext = new SimpleScriptContext();
newContext.setBindings(engineHolder.get().createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
engineScope.put("writer", writer);
Object value = engineHolder.get().eval("writer.print('Hello, World!');", engineScope);
writer.close();
} catch (IOException | ScriptException ex) {
Logger.getLogger(MyServlet.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
来源:https://stackoverflow.com/questions/27710407/reuse-nashorn-scriptengine-in-servlet