问题 在Servlet中重用Nashorn ScriptEngine [重复]


这个问题在这里已有答案:


8117
2017-12-30 18:13


起源



答案:


javax.script.ScriptEngineFactory 有一种方法 getParameter(String key)

用特殊的钥匙 THREADING 您可以获得此特定发动机工厂的线程信息。

这个小程序为每个注册的发动机工厂打印出这些信息:

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")));
    }
  }
}

对于Java 7,它是:

Mozilla Rhino (1.7 release 3 PRERELEASE), ECMAScript (1.8), MULTITHREADED

对于Java 8:

Oracle Nashorn (1.8.0_25), ECMAScript (ECMA - 262 Edition 5.1), null

null 表示引擎实现不是线程安全的。

在您的servlet中,您可以使用 ThreadLocal 为每个线程保持一个单独的引擎,允许为同一线程提供的后续请求重用引擎。

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);
    }
  }
}

14
2017-12-30 21:27



谢谢!这回答了我的问题。并感谢提示使用线程本地。 - Gregor
请参见: stackoverflow.com/questions/30140103/... - igr
我想你应该通过 newContext 至 eval 代替 engineScope - gamliela
在Java 8中,您还可以使用ThreadLocal.withInitial(() - > new ScriptEngineManager()。getEngineByName(“nashorn”))来代替创建匿名的ThreadLocal子类; - srborlongan
问题的编辑部分不会有效吗?即如果JS函数是纯函数并且不修改全局状态并且仅对传递的参数进行操作 - faizan


答案:


javax.script.ScriptEngineFactory 有一种方法 getParameter(String key)

用特殊的钥匙 THREADING 您可以获得此特定发动机工厂的线程信息。

这个小程序为每个注册的发动机工厂打印出这些信息:

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")));
    }
  }
}

对于Java 7,它是:

Mozilla Rhino (1.7 release 3 PRERELEASE), ECMAScript (1.8), MULTITHREADED

对于Java 8:

Oracle Nashorn (1.8.0_25), ECMAScript (ECMA - 262 Edition 5.1), null

null 表示引擎实现不是线程安全的。

在您的servlet中,您可以使用 ThreadLocal 为每个线程保持一个单独的引擎,允许为同一线程提供的后续请求重用引擎。

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);
    }
  }
}

14
2017-12-30 21:27



谢谢!这回答了我的问题。并感谢提示使用线程本地。 - Gregor
请参见: stackoverflow.com/questions/30140103/... - igr
我想你应该通过 newContext 至 eval 代替 engineScope - gamliela
在Java 8中,您还可以使用ThreadLocal.withInitial(() - > new ScriptEngineManager()。getEngineByName(“nashorn”))来代替创建匿名的ThreadLocal子类; - srborlongan
问题的编辑部分不会有效吗?即如果JS函数是纯函数并且不修改全局状态并且仅对传递的参数进行操作 - faizan