Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reuse Nashorn ScriptEngine in Servlet [duplicate]

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?

like image 628
Gregor Avatar asked Dec 30 '14 18:12

Gregor


People also ask

What can I use instead of nashorn?

The Nashorn engine has been deprecated in JDK 11 as part of JEP 335 and and has been removed from JDK15 as part of JEP 372. GraalVM can step in as a replacement for JavaScript code previously executed on the Nashorn engine. GraalVM provides all the features for JavaScript previously provided by Nashorn.

Is Scriptengine thread safe?

The engine implementation is not thread safe, and cannot be used to execute scripts concurrently on multiple threads.

What is the use of jjs tool in java 8?

If you wonder what jjs stands for, it stands for Java JavaScript. The command is located in the JDK_HOME\bin directory. The command can be used to run scripts in files or scripts entered on the command-line in interactive mode. It can also be used to execute shell scripts.

Which is new command-line tool for the Nashorn JavaScript engine in java 8?

jjs. For Nashorn engine, JAVA 8 introduces a new command line tool, jjs, to execute javascript codes at console.


1 Answers

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);
    }
  }
}
like image 181
vanje Avatar answered Oct 05 '22 08:10

vanje