Reuse Nashorn ScriptEngine in Servlet [duplicate]

时光总嘲笑我的痴心妄想 提交于 2019-11-27 02:42:47

问题


This question already has an answer here:

  • Should I use a separate ScriptEngine and CompiledScript instances per each thread? 4 answers

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?


回答1:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!