Повторное использование Nashorn ScriptEngine в сервлете
Я хочу выполнить JavaScript внутри сервлета. Можно ли повторно использовать один и тот же механизм сценариев во всех вызовах сервлета? Экземпляры сервлета используются несколькими потоками. Требуется ли для этого создание нового скриптового движка для каждого запроса? Это было бы недопустимым штрафом за производительность. Например, сохраняется ли следующий код?
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);
}
}
}
Если это небезопасно, что будет лучшим способом избежать создания движка для запроса? Используя пул двигателей?
Редактировать: возможно ли повторно использовать один и тот же движок и один и тот же JavaScriptObject, который получается как оценка JS-функции, для всех запросов сервлета, если функция не изменяет какой-либо общий объект, а использует только заданные аргументы с вызовом? Посмотрите на следующую адаптацию приведенного выше примера:
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);
}
}
Это безопасно?
1 ответ
В 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
означает, что реализация двигателя не является поточно-ориентированной.
В вашем сервлете вы можете использовать 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);
}
}
}